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,704 @@
1
+ export const FIELDS = {
2
+ price: (r) => r.price, mcap: (r) => r.mcap, fdv: (r) => r.fdv, liq: (r) => r.liq,
3
+ vol5m: (r) => r.vol5m, vol1h: (r) => r.vol1h, vol24: (r) => r.vol24,
4
+ holders: (r) => r.holders, org: (r) => r.org, top10: (r) => r.top10, dev: (r) => r.dev,
5
+ lplocked: (r) => r.lpLocked, bundled: (r) => r.bundled, snipers: (r) => r.snipers,
6
+ cluster: (r) => r.cluster, larp: (r) => r.larp,
7
+ age: (r) => r.ageMin, m5: (r) => r.chg?.m5, h1: (r) => r.chg?.h1, h6: (r) => r.chg?.h6, h24: (r) => r.chg?.h24,
8
+ buys: (r) => r.buys, sells: (r) => r.sells, bonding: (r) => r.bonding,
9
+ };
10
+ export const FIELD_NAMES = Object.keys(FIELDS);
11
+ const ALIASES = { vol: 'vol24', volume: 'vol24', liquidity: 'liq', cap: 'mcap', mc: 'mcap', holder: 'holders', organic: 'org', top: 'top10', locked: 'lplocked', 'cluster%': 'cluster', clusterpct: 'cluster' };
12
+ const OPS = { '>': (a, b) => a > b, '>=': (a, b) => a >= b, '<': (a, b) => a < b, '<=': (a, b) => a <= b, '=': (a, b) => a === b, '==': (a, b) => a === b, '!=': (a, b) => a !== b };
13
+ const TAGS = {
14
+ launchpad: (r, v) => (r.launchpad ?? '').toLowerCase().replace('.fun', '') === v.toLowerCase().replace('.fun', ''),
15
+ flag: (r, v) => (v === 'clean' ? (r.flags ?? []).length === 0 : (r.flags ?? []).map((f) => f.toLowerCase()).includes(v.toLowerCase())),
16
+ verified: (r, v) => (v === 'yes') === !!r.verified,
17
+ sus: (r, v) => (v === 'yes') === !!r.sus,
18
+ };
19
+ export const TAG_NAMES = Object.keys(TAGS);
20
+ export const ACTIONS = ['alert', 'ticket', 'ask', 'popup', 'tag', 'sound', 'watch'];
21
+ export const SCOPES = ['screener', 'watchlist', 'newpairs', 'positions'];
22
+ export const ALERT_LEVELS = ['up', 'down', 'info', 'warn'];
23
+ export const RULE_POPUP_KINDS = ['chart', 'token', 'bubbles', 'narrative', 'plans', 'feeds'];
24
+ /** The hard ceiling on fires per hour per rule (the schema says the same); the default is 12. */
25
+ export const MAX_PER_HOUR_CAP = 120;
26
+ export const DEFAULT_MAX_PER_HOUR = 12;
27
+ export const DEFAULT_COOLDOWN = '10m';
28
+ export const DEFAULT_WINDOW = '10m';
29
+ /** The biggest ticket size a rule may PROPOSE, in its own unit; the real amount is capped signer-side anyway. */
30
+ export const TICKET_SIZE_CAP = 50;
31
+ const MONEY_KEYS = ['arm', 'armed', 'auto', 'autoarm', 'confirm', 'execute', 'sign', 'send', 'live', 'mandate', 'caps', 'cap', 'gate'];
32
+ export function parseDuration(s) {
33
+ const m = /^(\d+)\s*(s|m|h|d|ticks?)$/.exec(String(s).trim());
34
+ if (!m)
35
+ throw new Error(`bad duration "${s}" (n s|m|h|d|ticks)`);
36
+ const n = Number(m[1]);
37
+ if (m[2].startsWith('tick'))
38
+ return { ticks: n };
39
+ return { ms: n * { s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]] };
40
+ }
41
+ export function parseNumber(s) {
42
+ const m = /^\$?(-?\d+(?:\.\d+)?)\s*(k|m|b|%|s|h|d|w|mo)?$/i.exec(s);
43
+ if (!m)
44
+ return null;
45
+ let n = Number(m[1]);
46
+ const suf = (m[2] ?? '').toLowerCase();
47
+ if (suf === 'k')
48
+ n *= 1e3;
49
+ else if (suf === 'm')
50
+ n *= 1e6;
51
+ else if (suf === 'b')
52
+ n *= 1e9;
53
+ else if (suf === 'h')
54
+ n *= 60;
55
+ else if (suf === 'd')
56
+ n *= 1440;
57
+ else if (suf === 'w')
58
+ n *= 10080;
59
+ else if (suf === 'mo')
60
+ n *= 43200;
61
+ return n;
62
+ }
63
+ function tokenize(src) {
64
+ const out = [];
65
+ // words: fields, keywords, numbers with units ($1.5M, 20%, 2h), base58 mints, skill names. Anything else
66
+ // (backticks, braces, semicolons, pipes…) is refused by position so a rule can never look like shell or code.
67
+ const re = /\s+|("(?:[^"\\]|\\.)*")|(>=|<=|!=|==|>|<|=)|([(),:!])|([A-Za-z0-9_.$%/+-]+)/gy;
68
+ let m;
69
+ let pos = 0;
70
+ while ((m = re.exec(src)) !== null) {
71
+ if (!m[0].length)
72
+ break;
73
+ const at = m.index;
74
+ pos = at + m[0].length;
75
+ if (m[1]) {
76
+ let v;
77
+ try {
78
+ v = JSON.parse(m[1]);
79
+ }
80
+ catch {
81
+ throw new Error(`bad string literal at ${at} (control characters are not allowed)`);
82
+ }
83
+ if (/[\u0000-\u001f\u007f]/.test(v))
84
+ throw new Error(`bad string literal at ${at} (control characters are not allowed)`);
85
+ out.push({ k: 'str', v, at });
86
+ }
87
+ else if (m[2])
88
+ out.push({ k: 'op', v: m[2], at });
89
+ else if (m[3])
90
+ out.push({ k: m[3], v: m[3], at });
91
+ else if (m[4])
92
+ out.push({ k: 'word', v: m[4], at });
93
+ }
94
+ if (pos < src.length)
95
+ throw new Error(`unexpected character "${src[pos]}" at ${pos}`);
96
+ return out;
97
+ }
98
+ const KEYWORDS = new Set(['and', 'or', 'not', 'then', 'for', 'cooldown', 'max', 'scope', 'window', 'name', 'when']);
99
+ /** Parse just a condition (the JSON form stores `when` as text). */
100
+ export function parseExpr(text) { return parseRule(`when ${text} then watch`).when; }
101
+ export function parseRule(text) {
102
+ if (typeof text !== 'string')
103
+ throw new Error('rule text must be a string');
104
+ if (text.length > 400)
105
+ throw new Error('rule text is longer than 400 characters');
106
+ const toks = tokenize(text);
107
+ let i = 0;
108
+ const peek = () => toks[i];
109
+ const next = () => toks[i++];
110
+ const isWord = (w) => peek()?.k === 'word' && peek().v.toLowerCase() === w;
111
+ const parseClause = () => {
112
+ const t = next();
113
+ if (!t)
114
+ throw new Error('unexpected end of rule');
115
+ if (t.k === '(') {
116
+ const e = parseOr();
117
+ if (next()?.k !== ')')
118
+ throw new Error('expected )');
119
+ return e;
120
+ }
121
+ if (t.k === 'word' && t.v.toLowerCase() === 'not')
122
+ return { kind: 'not', expr: parseClause() };
123
+ let not = false;
124
+ let w = t;
125
+ if (t.k === '!') {
126
+ not = true;
127
+ w = next();
128
+ }
129
+ if (!w || w.k !== 'word')
130
+ throw new Error(`expected a field at ${t.at}`);
131
+ if (peek()?.k === ':') {
132
+ next();
133
+ const v = next();
134
+ if (!v || v.k !== 'word')
135
+ throw new Error(`expected a tag value at ${w.at}`);
136
+ const tag = w.v.toLowerCase();
137
+ // r4: an OWN-property lookup — `__proto__:x` / `constructor:x` used to resolve to Object.prototype members (an
138
+ // always-true "tag" that persisted enabled, then a TypeError in evaluate() that took the whole rules tick down)
139
+ if (!Object.hasOwn(TAGS, tag))
140
+ throw new Error(`unknown tag "${tag}" (${TAG_NAMES.join(' | ')})`);
141
+ if (!/^[a-z0-9.-]{1,24}$/i.test(v.v))
142
+ throw new Error(`bad tag value "${v.v}"`);
143
+ return { kind: 'tag', tag, value: v.v, not };
144
+ }
145
+ const op = next();
146
+ if (!op || op.k !== 'op')
147
+ throw new Error(`expected an operator after "${w.v}" at ${w.at}`);
148
+ const val = next();
149
+ if (!val || val.k !== 'word')
150
+ throw new Error(`expected a value after "${op.v}"`);
151
+ const n = parseNumber(val.v);
152
+ if (n === null || !Number.isFinite(n))
153
+ throw new Error(`bad number "${val.v}"`);
154
+ const [base, deriv, extra] = w.v.toLowerCase().split('.');
155
+ // r4: own properties only — `__proto__ > 1` used to parse (inherited lookup) and crash the evaluator at run time
156
+ const field = Object.hasOwn(FIELDS, base) ? base : Object.hasOwn(ALIASES, base) ? ALIASES[base] : undefined;
157
+ if (!field || !Object.hasOwn(FIELDS, field))
158
+ throw new Error(`unknown field "${base}"`);
159
+ if (extra !== undefined)
160
+ throw new Error(`unknown signal "${deriv}.${extra}"`);
161
+ if (deriv && deriv !== 'velocity' && deriv !== 'delta')
162
+ throw new Error(`unknown signal "${deriv}" (velocity | delta)`);
163
+ const c = { kind: 'cmp', field, deriv: deriv ?? null, op: op.v, value: n, raw: `${w.v} ${op.v} ${val.v}` };
164
+ return not ? { kind: 'not', expr: c } : c;
165
+ };
166
+ const parseAnd = () => { let l = parseClause(); while (isWord('and')) {
167
+ next();
168
+ l = { kind: 'and', left: l, right: parseClause() };
169
+ } return l; };
170
+ const parseOr = () => { let l = parseAnd(); while (isWord('or')) {
171
+ next();
172
+ l = { kind: 'or', left: l, right: parseAnd() };
173
+ } return l; };
174
+ const rule = { when: { kind: 'tag', tag: 'flag', value: 'clean', not: false }, for: null, then: [], cooldown: DEFAULT_COOLDOWN, maxPerHour: DEFAULT_MAX_PER_HOUR, scope: 'screener', window: DEFAULT_WINDOW, name: null };
175
+ if (isWord('when'))
176
+ next();
177
+ rule.when = parseOr();
178
+ while (peek()) {
179
+ const t = next();
180
+ if (t.k !== 'word')
181
+ throw new Error(`unexpected "${t.v}" at ${t.at}`);
182
+ const kw = t.v.toLowerCase();
183
+ if (kw === 'for') {
184
+ const n = next();
185
+ if (!n)
186
+ throw new Error('for needs a duration');
187
+ let s = n.v;
188
+ const u = peek();
189
+ if (u?.k === 'word' && /^(s|m|h|ticks?)$/.test(u.v)) {
190
+ s += ' ' + u.v;
191
+ next();
192
+ }
193
+ parseDuration(s);
194
+ rule.for = s.replace(/^(\d+)\s*(\D+)$/, '$1 $2');
195
+ }
196
+ else if (kw === 'then') {
197
+ do {
198
+ const a = next();
199
+ if (!a || a.k !== 'word' || !ACTIONS.includes(a.v.toLowerCase()))
200
+ throw new Error(`unknown action "${a?.v ?? ''}" (${ACTIONS.join(' | ')})`);
201
+ const args = Object.create(null);
202
+ if (peek()?.k === '(') {
203
+ next();
204
+ let pos = 0;
205
+ while (peek() && peek().k !== ')') {
206
+ const v = next();
207
+ if (v.k === ',')
208
+ continue;
209
+ let key = String(pos++);
210
+ let value = v.v;
211
+ if (v.k === 'word' && peek()?.k === 'op' && peek().v === '=') {
212
+ next();
213
+ key = v.v;
214
+ const vv = next();
215
+ if (!vv)
216
+ throw new Error(`${key}= needs a value`);
217
+ value = vv.v;
218
+ if (vv.k !== 'str' && parseNumber(vv.v) !== null && /\d/.test(vv.v))
219
+ value = parseNumber(vv.v);
220
+ }
221
+ else if (v.k === 'word' && parseNumber(v.v) !== null && /\d/.test(v.v))
222
+ value = parseNumber(v.v);
223
+ args[key] = value;
224
+ }
225
+ if (next()?.k !== ')')
226
+ throw new Error(`expected ) after ${a.v}(`);
227
+ }
228
+ rule.then.push(normalizeAction(a.v.toLowerCase(), args));
229
+ if (rule.then.length > 6)
230
+ throw new Error('a rule may have at most 6 actions');
231
+ } while (peek()?.k === 'word' && ACTIONS.includes(peek().v.toLowerCase()));
232
+ }
233
+ else if (kw === 'cooldown') {
234
+ const d = next();
235
+ if (!d)
236
+ throw new Error('cooldown needs a duration');
237
+ const p = parseDuration(d.v);
238
+ if ('ticks' in p)
239
+ throw new Error('cooldown is a time (s|m|h)');
240
+ rule.cooldown = d.v;
241
+ }
242
+ else if (kw === 'window') {
243
+ const d = next();
244
+ if (!d)
245
+ throw new Error('window needs a duration');
246
+ const p = parseDuration(d.v);
247
+ if ('ticks' in p)
248
+ throw new Error('window is a time (s|m|h)');
249
+ rule.window = d.v;
250
+ }
251
+ else if (kw === 'max') {
252
+ const d = next();
253
+ const m = /^(\d+)\/h$/.exec(d?.v ?? '');
254
+ if (!m)
255
+ throw new Error('max needs n/h');
256
+ const n = Number(m[1]);
257
+ if (n < 1 || n > MAX_PER_HOUR_CAP)
258
+ throw new Error(`max n/h must be 1..${MAX_PER_HOUR_CAP}`);
259
+ rule.maxPerHour = n;
260
+ }
261
+ else if (kw === 'scope') {
262
+ const s = next();
263
+ if (!s || s.k !== 'word')
264
+ throw new Error('scope needs a value');
265
+ let v = s.v;
266
+ if (peek()?.k === ':') {
267
+ next();
268
+ const m = next();
269
+ if (!m || m.k !== 'word')
270
+ throw new Error('scope mint: needs an address');
271
+ v += ':' + m.v;
272
+ }
273
+ rule.scope = checkScope(v);
274
+ }
275
+ else if (kw === 'name') {
276
+ const s = next();
277
+ if (!s)
278
+ throw new Error('name needs text');
279
+ rule.name = s.v.slice(0, 80);
280
+ }
281
+ else if (KEYWORDS.has(kw))
282
+ throw new Error(`misplaced "${kw}"`);
283
+ else
284
+ throw new Error(`unexpected "${t.v}" at ${t.at}`);
285
+ }
286
+ if (!rule.then.length)
287
+ throw new Error('a rule needs "then <action>"');
288
+ return rule;
289
+ }
290
+ export function checkScope(v) {
291
+ if (SCOPES.includes(v))
292
+ return v;
293
+ if (/^mint:[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(v))
294
+ return v;
295
+ throw new Error(`scope must be ${SCOPES.join(' | ')} | mint:<address>`);
296
+ }
297
+ /** Canonical action args; positional args get names; nothing money-shaped survives (unknown keys are dropped, named money keys refused). */
298
+ export function normalizeAction(action, args) {
299
+ const a = args && typeof args === 'object' && !Array.isArray(args) ? args : {};
300
+ for (const k of Object.keys(a)) {
301
+ if (k === '__proto__' || k === 'constructor' || k === 'prototype')
302
+ throw new Error(`action args cannot carry "${k}"`);
303
+ if (MONEY_KEYS.includes(k.toLowerCase()))
304
+ throw new Error(`${action} cannot carry "${k}"`);
305
+ }
306
+ const pos = (i) => a[String(i)];
307
+ const str = (v) => (typeof v === 'string' ? v : null);
308
+ switch (action) {
309
+ case 'alert': {
310
+ const lv = str(a.level ?? pos(0));
311
+ const level = ALERT_LEVELS.includes(lv ?? '') ? lv : 'info';
312
+ const text = str(a.text) ?? str(pos(1)) ?? (lv && !ALERT_LEVELS.includes(lv) ? lv : '') ?? '';
313
+ return { action, args: { level, text: text.slice(0, 120) } };
314
+ }
315
+ case 'ticket': {
316
+ const side = String(a.side ?? pos(0) ?? '').toLowerCase();
317
+ if (side !== 'buy' && side !== 'sell')
318
+ throw new Error('ticket(side, size): side must be buy or sell');
319
+ const size = a.size ?? pos(1);
320
+ const unit = String(a.unit ?? pos(2) ?? 'sol').toLowerCase();
321
+ if (typeof size !== 'number' || !Number.isFinite(size) || !(size > 0) || size > TICKET_SIZE_CAP)
322
+ throw new Error(`ticket size must be a number 0 < n ≤ ${TICKET_SIZE_CAP} (the signer caps the real amount)`);
323
+ if (unit !== 'sol' && unit !== 'usd' && unit !== 'pct')
324
+ throw new Error('ticket unit must be sol, usd or pct');
325
+ return { action, args: { side, size, unit } };
326
+ }
327
+ case 'ask': {
328
+ const skill = String(a.skill ?? pos(0) ?? 'token-dossier');
329
+ if (!/^[a-z][a-z0-9-]{1,40}$/.test(skill))
330
+ throw new Error('ask(skill) needs a skill name (lowercase, dashes)');
331
+ return { action, args: { skill } };
332
+ }
333
+ case 'popup': {
334
+ const kind = String(a.kind ?? pos(0) ?? 'chart');
335
+ if (!RULE_POPUP_KINDS.includes(kind))
336
+ throw new Error(`popup(kind): ${RULE_POPUP_KINDS.join(' | ')}`);
337
+ return { action, args: { kind: kind } };
338
+ }
339
+ case 'tag': {
340
+ const label = String(a.label ?? pos(0) ?? '');
341
+ if (!label || label.length > 24 || /[\u0000-\u001f\u007f‮]/.test(label))
342
+ throw new Error('tag("label") needs 1–24 printable characters');
343
+ return { action, args: { label } };
344
+ }
345
+ case 'sound': {
346
+ const name = String(a.name ?? pos(0) ?? 'ping');
347
+ if (!/^[a-z-]{2,16}$/.test(name))
348
+ throw new Error('sound(name)');
349
+ return { action, args: { name } };
350
+ }
351
+ case 'watch': return { action, args: {} };
352
+ default: throw new Error(`unknown action "${action}" (${ACTIONS.join(' | ')})`);
353
+ }
354
+ }
355
+ /** Text → the JSON rule the config stores (schema `ruleJson`). */
356
+ export function ruleToJson(text, opts = {}) {
357
+ const r = parseRule(text);
358
+ const json = {
359
+ id: opts.id ?? slug(r.name ?? text), name: r.name ?? text.slice(0, 80), enabled: opts.enabled ?? true,
360
+ when: exprToText(r.when), then: r.then.map((a) => ({ action: a.action, args: { ...a.args } })), scope: r.scope, cooldown: r.cooldown, maxPerHour: r.maxPerHour, window: r.window, source: opts.source ?? 'user',
361
+ };
362
+ if (r.for)
363
+ json.for = r.for;
364
+ return json;
365
+ }
366
+ export function slug(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/^[^a-z]+/, '').slice(0, 40) || 'rule'; }
367
+ /** The JSON rule back to one line of rule text (what the editor shows). */
368
+ export function ruleToText(r) {
369
+ const parts = [`when ${r.when}`];
370
+ if (r.for)
371
+ parts.push(`for ${r.for}`);
372
+ parts.push('then ' + r.then.map((a) => actionToText(a.action, a.args ?? {})).join(' '));
373
+ if (r.cooldown && r.cooldown !== DEFAULT_COOLDOWN)
374
+ parts.push(`cooldown ${r.cooldown}`);
375
+ if (r.maxPerHour !== undefined && r.maxPerHour !== DEFAULT_MAX_PER_HOUR)
376
+ parts.push(`max ${r.maxPerHour}/h`);
377
+ if (r.scope && r.scope !== 'screener')
378
+ parts.push(`scope ${r.scope}`);
379
+ if (r.window && r.window !== DEFAULT_WINDOW)
380
+ parts.push(`window ${r.window}`);
381
+ if (r.name && r.name !== r.when && !r.name.startsWith('when '))
382
+ parts.push(`name ${JSON.stringify(r.name)}`);
383
+ return parts.join(' ');
384
+ }
385
+ function actionToText(action, args) {
386
+ switch (action) {
387
+ case 'alert': return `alert(${args.level ?? 'info'}${args.text ? `, ${JSON.stringify(args.text)}` : ''})`;
388
+ case 'ticket': return `ticket(${args.side}, ${args.size}, ${args.unit ?? 'sol'})`;
389
+ case 'ask': return `ask(${args.skill})`;
390
+ case 'popup': return `popup(${args.kind})`;
391
+ case 'tag': return `tag(${JSON.stringify(args.label)})`;
392
+ case 'sound': return `sound(${args.name})`;
393
+ default: return action;
394
+ }
395
+ }
396
+ export function exprToText(e) {
397
+ switch (e.kind) {
398
+ case 'cmp': return `${e.field}${e.deriv ? '.' + e.deriv : ''} ${e.op} ${fmtNum(e.value)}`;
399
+ case 'tag': return `${e.not ? '!' : ''}${e.tag}:${e.value}`;
400
+ case 'not': return `not (${exprToText(e.expr)})`;
401
+ case 'and': return `${exprToText(e.left)} and ${exprToText(e.right)}`;
402
+ case 'or': return `(${exprToText(e.left)} or ${exprToText(e.right)})`;
403
+ }
404
+ }
405
+ function fmtNum(n) {
406
+ const a = Math.abs(n);
407
+ if (a >= 1e9 && Number.isInteger(n / 1e9))
408
+ return `${n / 1e9}B`;
409
+ if (a >= 1e6 && Number.isInteger(n / 1e6))
410
+ return `${n / 1e6}M`;
411
+ if (a >= 1e3 && Number.isInteger(n / 1e3))
412
+ return `${n / 1e3}K`;
413
+ return String(n);
414
+ }
415
+ /** A `.rules` file: one rule per line, `#` comments, blank lines ignored. Bad lines are reported with their line number; good ones load. */
416
+ export function parseRulesFile(text, opts = {}) {
417
+ const rules = [];
418
+ const errors = [];
419
+ const seen = new Set();
420
+ text.split('\n').forEach((raw, i) => {
421
+ const line = raw.trim();
422
+ if (!line || line.startsWith('#'))
423
+ return;
424
+ try {
425
+ const r = ruleToJson(line, { source: opts.source });
426
+ if (opts.idPrefix)
427
+ r.id = slug(`${opts.idPrefix}-${r.id}`);
428
+ if (seen.has(r.id))
429
+ throw new Error(`duplicate rule id "${r.id}" (give it a different name "...")`);
430
+ seen.add(r.id);
431
+ rules.push(r);
432
+ }
433
+ catch (e) {
434
+ errors.push({ line: i + 1, msg: e.message });
435
+ }
436
+ });
437
+ return { rules, errors };
438
+ }
439
+ export class RulesEngine {
440
+ rules = [];
441
+ /** mint → samples (one per observe) kept for the largest rule window (≥ 60 s). */
442
+ ring = new Map();
443
+ held = new Map();
444
+ lastFired = new Map();
445
+ fires = new Map();
446
+ tick = 0;
447
+ maxWindowMs = 60_000;
448
+ now = 0;
449
+ /** Problems with rules that did not load (id → message); the others run. */
450
+ errors = [];
451
+ constructor(opts = {}) {
452
+ for (const r of opts.rules ?? []) {
453
+ try {
454
+ this.load(r);
455
+ }
456
+ catch (e) {
457
+ this.errors.push({ id: typeof r === 'string' ? r.slice(0, 40) : String(r?.id ?? '?'), msg: e.message });
458
+ }
459
+ }
460
+ this.now = opts.now ?? 0;
461
+ }
462
+ /** Accepts rule text or the JSON form; returns the compiled rule. Throws on bad text / bad JSON (nothing is loaded then). */
463
+ load(r) {
464
+ const json = typeof r === 'string' ? ruleToJson(r) : r;
465
+ if (!json || typeof json !== 'object' || typeof json.id !== 'string' || !/^[a-z][a-z0-9-]{0,39}$/.test(json.id))
466
+ throw new Error('rule id must be a slug (lowercase letters, digits, dashes)');
467
+ if (typeof json.when !== 'string')
468
+ throw new Error(`${json.id}: "when" must be rule text`);
469
+ if (!Array.isArray(json.then) || !json.then.length || json.then.length > 6)
470
+ throw new Error(`${json.id}: "then" needs 1–6 actions`);
471
+ const cooldown = parseDuration(json.cooldown ?? DEFAULT_COOLDOWN);
472
+ const window = parseDuration(json.window ?? DEFAULT_WINDOW);
473
+ if ('ticks' in cooldown || 'ticks' in window)
474
+ throw new Error(`${json.id}: cooldown and window are times`);
475
+ const maxPerHour = json.maxPerHour ?? DEFAULT_MAX_PER_HOUR;
476
+ if (!Number.isInteger(maxPerHour) || maxPerHour < 1 || maxPerHour > MAX_PER_HOUR_CAP)
477
+ throw new Error(`${json.id}: maxPerHour must be 1..${MAX_PER_HOUR_CAP}`);
478
+ const source = json.source ?? 'user';
479
+ if (!/^(user|pack:[a-z][a-z0-9-]*|jarvis|plugin:[a-z][a-z0-9-]*)$/.test(source))
480
+ throw new Error(`${json.id}: bad source "${source}"`);
481
+ const compiled = {
482
+ id: json.id, name: typeof json.name === 'string' && json.name ? json.name.slice(0, 80) : json.id, enabled: json.enabled !== false,
483
+ when: parseExpr(json.when), whenText: json.when,
484
+ for: json.for ? parseDuration(json.for) : null,
485
+ then: json.then.map((a) => normalizeAction(a.action, a.args ?? {})),
486
+ scope: checkScope(json.scope ?? 'screener'), cooldownMs: cooldown.ms, maxPerHour, windowMs: Math.max(1_000, window.ms), source,
487
+ };
488
+ this.maxWindowMs = Math.max(this.maxWindowMs, compiled.windowMs);
489
+ this.rules = this.rules.filter((x) => x.id !== compiled.id).concat(compiled);
490
+ return compiled;
491
+ }
492
+ remove(id) { this.rules = this.rules.filter((x) => x.id !== id); }
493
+ setEnabled(id, on) { const r = this.rules.find((x) => x.id === id); if (!r)
494
+ return false; r.enabled = on; return true; }
495
+ get(id) { return this.rules.find((x) => x.id === id); }
496
+ /** Record one tick of rows (all scopes) so derived signals have history. Rows without a mint / skeletons are skipped. */
497
+ observe(rows, now) {
498
+ this.now = now;
499
+ this.tick += 1;
500
+ const keep = now - this.maxWindowMs - 1;
501
+ const seen = new Set();
502
+ for (const r of rows) {
503
+ if (!r.mint || r.skeleton || seen.has(r.mint))
504
+ continue;
505
+ seen.add(r.mint);
506
+ const arr = this.ring.get(r.mint) ?? [];
507
+ const sample = { t: now };
508
+ for (const f of FIELD_NAMES)
509
+ sample[f] = FIELDS[f](r);
510
+ arr.push(sample);
511
+ while (arr.length && arr[0].t < keep)
512
+ arr.shift();
513
+ this.ring.set(r.mint, arr);
514
+ }
515
+ // forget mints that stopped appearing a long time ago (ring hygiene for a long session)
516
+ if (this.tick % 50 === 0)
517
+ for (const [mint, arr] of this.ring)
518
+ if (!arr.length || arr[arr.length - 1].t < keep)
519
+ this.ring.delete(mint);
520
+ }
521
+ /** The derived signal for (mint, field): null until two earlier samples exist in the window. */
522
+ signal(mint, field, deriv, windowMs, now, current) {
523
+ const arr = this.ring.get(mint) ?? [];
524
+ const prior = arr.filter((s) => s.t >= now - windowMs && s.t < now && s[field] !== null && s[field] !== undefined);
525
+ if (prior.length < 2)
526
+ return null;
527
+ if (deriv === 'velocity') {
528
+ const mean = prior.reduce((a, s) => a + s[field], 0) / prior.length;
529
+ return mean > 0 ? current / mean : null;
530
+ }
531
+ return current - prior[0][field];
532
+ }
533
+ test(expr, row, rule, now, reasons, stats) {
534
+ switch (expr.kind) {
535
+ case 'and': return this.test(expr.left, row, rule, now, reasons, stats) && this.test(expr.right, row, rule, now, reasons, stats);
536
+ case 'or': return this.test(expr.left, row, rule, now, reasons, stats) || this.test(expr.right, row, rule, now, reasons, stats);
537
+ case 'not': return !this.test(expr.expr, row, rule, now, [], stats);
538
+ case 'tag': {
539
+ const ok = TAGS[expr.tag](row, expr.value);
540
+ return expr.not ? !ok : ok;
541
+ }
542
+ case 'cmp': {
543
+ const cur = FIELDS[expr.field](row);
544
+ if (cur === null || cur === undefined || !Number.isFinite(cur))
545
+ return false; // missing data is false, never true (same as the filter grammar)
546
+ let v = cur;
547
+ if (expr.deriv) {
548
+ v = this.signal(row.mint, expr.field, expr.deriv, rule.windowMs, now, cur);
549
+ if (v === null) {
550
+ if (stats)
551
+ stats.noHistory += 1;
552
+ return false;
553
+ }
554
+ }
555
+ const ok = OPS[expr.op](v, expr.value);
556
+ if (ok)
557
+ reasons.push({ clause: expr.raw, value: Math.round(v * 100) / 100 });
558
+ return ok;
559
+ }
560
+ }
561
+ }
562
+ inScope(rule, row, ctx) {
563
+ if (rule.scope.startsWith('mint:'))
564
+ return rule.scope.slice(5) === row.mint;
565
+ if (rule.scope === 'watchlist')
566
+ return !!ctx.watchlist?.has(row.mint);
567
+ if (rule.scope === 'positions')
568
+ return !!ctx.positions?.has(row.mint);
569
+ if (rule.scope === 'newpairs')
570
+ return (row.ageMin ?? Infinity) <= 120;
571
+ return true;
572
+ }
573
+ /** Evaluate every enabled rule against rows at `now`. Call observe() first. Pure apart from the engine's own counters. */
574
+ evaluate(rows, now, ctx = {}) {
575
+ this.now = now;
576
+ const out = { fired: [], alerts: [], proposals: [], ui: [], annotations: [], suppressed: [] };
577
+ for (const rule of this.rules) {
578
+ if (!rule.enabled)
579
+ continue;
580
+ const recent = (this.fires.get(rule.id) ?? []).filter((t) => t > now - 3_600_000);
581
+ this.fires.set(rule.id, recent);
582
+ const seen = new Set();
583
+ for (const row of rows) {
584
+ if (!row.mint || row.skeleton || seen.has(row.mint))
585
+ continue;
586
+ seen.add(row.mint);
587
+ if (!this.inScope(rule, row, ctx))
588
+ continue;
589
+ const key = `${rule.id}|${row.mint}`;
590
+ const reasons = [];
591
+ const ok = this.test(rule.when, row, rule, now, reasons);
592
+ if (!ok) {
593
+ this.held.delete(key);
594
+ continue;
595
+ }
596
+ let h = this.held.get(key);
597
+ if (!h) {
598
+ h = { since: now, ticks: 0 };
599
+ this.held.set(key, h);
600
+ }
601
+ h.ticks += 1;
602
+ if (rule.for) {
603
+ const enough = 'ticks' in rule.for ? h.ticks >= rule.for.ticks : now - h.since >= rule.for.ms;
604
+ if (!enough) {
605
+ out.suppressed.push({ rule: rule.id, mint: row.mint, symbol: row.symbol, why: 'holding' });
606
+ continue;
607
+ }
608
+ }
609
+ const last = this.lastFired.get(key);
610
+ if (last !== undefined && now - last < rule.cooldownMs) {
611
+ out.suppressed.push({ rule: rule.id, mint: row.mint, symbol: row.symbol, why: 'cooldown' });
612
+ continue;
613
+ }
614
+ if (recent.length >= rule.maxPerHour) {
615
+ out.suppressed.push({ rule: rule.id, mint: row.mint, symbol: row.symbol, why: 'max/h' });
616
+ continue;
617
+ }
618
+ this.lastFired.set(key, now);
619
+ recent.push(now);
620
+ const ev = { rule: rule.id, name: rule.name, source: rule.source, mint: row.mint, symbol: row.symbol, at: now, tick: this.tick, reasons, actions: [] };
621
+ for (const a of rule.then) {
622
+ const o = this.perform(a, row, ev, now);
623
+ ev.actions.push(o);
624
+ if (o.kind === 'alert')
625
+ out.alerts.push(o);
626
+ else if (o.kind === 'proposal')
627
+ out.proposals.push(o);
628
+ else if (o.kind === 'ui')
629
+ out.ui.push(o);
630
+ else
631
+ out.annotations.push(o);
632
+ }
633
+ out.fired.push(ev);
634
+ }
635
+ }
636
+ return out;
637
+ }
638
+ /**
639
+ * "Would fire on N rows right now": test a rule's condition against rows without firing, counting, cooling
640
+ * down or holding. Uses the engine's ring for derived signals (so velocity/delta reflect real history) and
641
+ * never mutates it. Accepts rule text or JSON; a parse error comes back as `error`, never thrown.
642
+ */
643
+ dryRun(rule, rows, now, ctx = {}) {
644
+ let compiled;
645
+ try {
646
+ const json = typeof rule === 'string' ? ruleToJson(rule, { id: 'dry-run' }) : rule;
647
+ const probe = new RulesEngine();
648
+ compiled = probe.load(json);
649
+ }
650
+ catch (e) {
651
+ return { matches: [], considered: 0, noHistory: 0, rule: null, error: e.message };
652
+ }
653
+ const res = { matches: [], considered: 0, noHistory: 0, rule: compiled, error: null };
654
+ const stats = { noHistory: 0 };
655
+ const seen = new Set();
656
+ for (const row of rows) {
657
+ if (!row.mint || row.skeleton || seen.has(row.mint))
658
+ continue;
659
+ seen.add(row.mint);
660
+ if (!this.inScope(compiled, row, ctx))
661
+ continue;
662
+ res.considered += 1;
663
+ const reasons = [];
664
+ const before = stats.noHistory;
665
+ if (this.test(compiled.when, row, compiled, now, reasons, stats))
666
+ res.matches.push({ mint: row.mint, symbol: row.symbol, reasons });
667
+ else if (stats.noHistory > before)
668
+ res.noHistory += 1;
669
+ }
670
+ return res;
671
+ }
672
+ /** Fires in the last hour per rule (the Rules screen's "fires/h"). */
673
+ firesLastHour(id, now = this.now) { return (this.fires.get(id) ?? []).filter((t) => t > now - 3_600_000).length; }
674
+ lastFiredAt(id) { let m = null; for (const [k, t] of this.lastFired)
675
+ if (k.startsWith(`${id}|`) && (m === null || t > m))
676
+ m = t; return m; }
677
+ perform(a, row, ev, now) {
678
+ const why = ev.reasons.map((r) => `${r.clause} (${r.value})`).join(', ');
679
+ const base = { rule: ev.rule, source: ev.source, mint: row.mint, symbol: row.symbol, at: now };
680
+ switch (a.action) {
681
+ case 'alert': return { kind: 'alert', level: a.args.level, text: `${a.args.text || ev.name}: ${why}`, ...base };
682
+ case 'ticket': return { kind: 'proposal', type: 'ticket', side: a.args.side, size: a.args.size, unit: a.args.unit, status: 'proposal', requiresKeypress: true, armedBy: null, why, ...base };
683
+ case 'ask': return { kind: 'proposal', type: 'ask', skill: a.args.skill, status: 'proposal', requiresKeypress: true, armedBy: null, why, ...base };
684
+ case 'watch': return { kind: 'annotation', type: 'watch', ...base };
685
+ case 'tag': return { kind: 'annotation', type: 'tag', label: a.args.label, ...base };
686
+ case 'popup': return { kind: 'ui', type: 'popup', popup: a.args.kind, ...base };
687
+ case 'sound': return { kind: 'ui', type: 'sound', name: a.args.name, ...base };
688
+ }
689
+ }
690
+ }
691
+ /** One-line description of a compiled rule's actions for lists ("alert·ticket buy 0.5 sol·ask token-dossier"). */
692
+ export function describeActions(r) {
693
+ return r.then.map((a) => {
694
+ switch (a.action) {
695
+ case 'alert': return `alert ${a.args.level}`;
696
+ case 'ticket': return `ticket ${a.args.side} ${a.args.size} ${a.args.unit}`;
697
+ case 'ask': return `ask ${a.args.skill}`;
698
+ case 'popup': return `popup ${a.args.kind}`;
699
+ case 'tag': return `tag "${a.args.label}"`;
700
+ case 'sound': return `sound ${a.args.name}`;
701
+ case 'watch': return 'watch';
702
+ }
703
+ }).join(' · ');
704
+ }