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,131 @@
1
+ // `dexterm rules <list|add|remove|enable|disable|check|dry-run|fields> …` (task-1358 Tier 3). No network, no
2
+ // model, never touches a key file. Rules live in ~/.dexterm/config.json `rules[]`; every change goes through the
3
+ // config journal (actor user) so `config.journal.jsonl` records it with its inverse.
4
+ // rules list every rule with enabled / when / then / source (+ the ones that fail to load)
5
+ // rules add "<rule text>" parse (never eval), validate against the schema, append; refuses money-shaped args
6
+ // rules remove|enable|disable <id>
7
+ // rules check "<rule text>" parse + show the JSON the config would store; exit 1 on a refusal
8
+ // rules dry-run "<rule text>" "would match N rows" against the demo fixture rows (flat history: velocity = 1)
9
+ // rules fields the field / signal / tag / action vocabulary
10
+ import { dextermHome, loadConfig, applyAndJournal, RulesEngine, ruleToJson, ruleToText, describeActions, FIELD_NAMES, TAG_NAMES, ACTIONS, SCOPES, RULE_POPUP_KINDS, MAX_PER_HOUR_CAP, TICKET_SIZE_CAP, slug } from '../../custom/src/index.js';
11
+ export const RULES_USAGE = `usage: dexterm rules list | add "<rule>" | remove <id> | enable <id> | disable <id> | check "<rule>" | dry-run "<rule>" | fields
12
+ rule: [when] <cond> [for <n> ticks|<n>s|<n>m] then <action>+ [cooldown <dur>] [max <n>/h] [scope screener|watchlist|newpairs|positions|mint:<addr>] [window <dur>] [name "<s>"]
13
+ e.g.: dexterm rules add 'vol5m.velocity > 3 and cluster < 20 and liq > 25K for 2 ticks then alert(up, "surge") popup(chart) name "surge"'
14
+ actions only alert, annotate, open UI or PROPOSE (ticket/ask cards need your keypress; the paper ticket editor is where you confirm)`;
15
+ export async function rulesCommand(argv, io) {
16
+ const home = dextermHome(io.env);
17
+ const [cmd, arg] = argv;
18
+ const bad = (s) => { io.stderr.write(`dexterm rules: ${s}\n${RULES_USAGE}\n`); return 2; };
19
+ const cfg = loadConfig(home);
20
+ for (const e of cfg.errors)
21
+ io.stderr.write(`dexterm rules: config: ${e}\n`);
22
+ const rules = cfg.config.rules ?? [];
23
+ switch (cmd) {
24
+ case undefined:
25
+ case 'list': {
26
+ io.stdout.write(`${cfg.path}${cfg.missing ? ' (no config: no rules)' : ''} · ${rules.length} rule(s)\n`);
27
+ const e = new RulesEngine({ rules });
28
+ for (const r of e.rules)
29
+ io.stdout.write(` ${(r.enabled ? 'on ' : 'off')} ${r.id.padEnd(24)} when ${r.whenText}${r.for ? ` for ${'ticks' in r.for ? r.for.ticks + ' ticks' : Math.round(r.for.ms / 1000) + 's'}` : ''} → ${describeActions(r)} · ${r.source} · cooldown ${Math.round(r.cooldownMs / 60000)}m · max ${r.maxPerHour}/h · scope ${r.scope}\n`);
30
+ for (const x of e.errors)
31
+ io.stdout.write(` ⚠ ${x.id.padEnd(24)} ${x.msg}\n`);
32
+ return 0;
33
+ }
34
+ case 'add': {
35
+ if (!arg)
36
+ return bad('add needs the rule text in quotes');
37
+ let json;
38
+ try {
39
+ json = ruleToJson(arg);
40
+ }
41
+ catch (e) {
42
+ io.stderr.write(`dexterm rules: refused: ${e.message}\n`);
43
+ return 1;
44
+ }
45
+ const taken = new Set(rules.map((r) => r.id));
46
+ let id = json.id;
47
+ let n = 2;
48
+ while (taken.has(id))
49
+ id = slug(`${json.id}-${n++}`);
50
+ json.id = id;
51
+ try {
52
+ applyAndJournal(home, [{ op: 'add', path: '/rules/-', value: json }], { actor: 'user', why: `dexterm rules add ${id}` });
53
+ }
54
+ catch (e) {
55
+ io.stderr.write(`dexterm rules: not saved: ${e.message}\n`);
56
+ return 1;
57
+ }
58
+ io.stdout.write(`added rule ${id}: ${ruleToText(json)}\n`);
59
+ return 0;
60
+ }
61
+ case 'remove':
62
+ case 'enable':
63
+ case 'disable': {
64
+ if (!arg)
65
+ return bad(`${cmd} needs a rule id`);
66
+ const i = rules.findIndex((r) => r.id === arg);
67
+ if (i < 0) {
68
+ io.stderr.write(`dexterm rules: no rule "${arg}"\n`);
69
+ return 1;
70
+ }
71
+ const ops = cmd === 'remove' ? [{ op: 'remove', path: `/rules/${i}` }] : [{ op: 'replace', path: `/rules/${i}/enabled`, value: cmd === 'enable' }];
72
+ try {
73
+ applyAndJournal(home, ops, { actor: 'user', why: `dexterm rules ${cmd} ${arg}` });
74
+ }
75
+ catch (e) {
76
+ io.stderr.write(`dexterm rules: not changed: ${e.message}\n`);
77
+ return 1;
78
+ }
79
+ io.stdout.write(`${cmd === 'remove' ? 'removed' : cmd + 'd'} rule ${arg}\n`);
80
+ return 0;
81
+ }
82
+ case 'check': {
83
+ if (!arg)
84
+ return bad('check needs the rule text in quotes');
85
+ try {
86
+ const json = ruleToJson(arg);
87
+ const e = new RulesEngine();
88
+ e.load(json);
89
+ io.stdout.write(`ok: ${ruleToText(json)}\n${JSON.stringify(json, null, 2)}\n`);
90
+ return 0;
91
+ }
92
+ catch (e) {
93
+ io.stdout.write(`✗ ${e.message}\n`);
94
+ return 1;
95
+ }
96
+ }
97
+ case 'dry-run': {
98
+ if (!arg)
99
+ return bad('dry-run needs the rule text in quotes');
100
+ const { makeFixtureState } = await import("./state.js");
101
+ const st = makeFixtureState();
102
+ const rows = st.screener.rows.filter((r) => !r.skeleton);
103
+ const e = new RulesEngine();
104
+ const t0 = Date.now() - 30_000;
105
+ for (let i = 0; i < 3; i++)
106
+ e.observe(rows, t0 + i * 10_000); // flat history so velocity/delta are defined (1 / 0)
107
+ const d = e.dryRun(arg, rows, t0 + 30_000);
108
+ if (d.error) {
109
+ io.stdout.write(`✗ ${d.error}\n`);
110
+ return 1;
111
+ }
112
+ io.stdout.write(`would match ${d.matches.length} of ${d.considered} demo rows right now (fixture data, flat history) → ${describeActions(d.rule)}\n`);
113
+ for (const m of d.matches)
114
+ io.stdout.write(` ${m.symbol.padEnd(8)} ${m.reasons.map((r) => `${r.clause} (${r.value})`).join(', ')}\n`);
115
+ if (d.noHistory)
116
+ io.stdout.write(` (${d.noHistory} rows had no history for a derived signal)\n`);
117
+ return 0;
118
+ }
119
+ case 'fields': {
120
+ io.stdout.write(`fields: ${FIELD_NAMES.join(' ')}\n`);
121
+ io.stdout.write(`aliases: vol volume liquidity cap mc holder organic top locked cluster% clusterpct\n`);
122
+ io.stdout.write(`signals: <field>.velocity (current ÷ mean of earlier samples in the window) · <field>.delta (current − oldest in the window)\n`);
123
+ io.stdout.write(`tags: ${TAG_NAMES.map((t) => t + ':').join(' ')} (e.g. launchpad:pump flag:mint flag:clean verified:yes sus:no; prefix ! to negate)\n`);
124
+ io.stdout.write(`values: $1.5M 25K 2B 20% 2h 30m 3d · ops > >= < <= = != · and or not ( )\n`);
125
+ io.stdout.write(`actions: ${ACTIONS.join(' ')} alert(level, "text") ticket(buy|sell, size ≤ ${TICKET_SIZE_CAP}, sol|usd|pct) ask(skill) popup(${RULE_POPUP_KINDS.join('|')}) tag("label") sound(name) watch\n`);
126
+ io.stdout.write(`limits: cooldown (default 10m) · max n/h (default 12, cap ${MAX_PER_HOUR_CAP}) · scope ${SCOPES.join('|')}|mint:<addr> · window (default 10m)\n`);
127
+ return 0;
128
+ }
129
+ default: return bad(`unknown subcommand "${cmd}"`);
130
+ }
131
+ }
@@ -0,0 +1,339 @@
1
+ import { RulesEngine, ruleToJson, ruleToText, slug, applyAndJournal, describeActions, } from '../../custom/src/index.js';
2
+ export const RULE_INPUT_MAX = 400;
3
+ /** The engine observes/evaluates at most this often (the feed itself moves every 10 s; fixtures drift every tick). */
4
+ export const RULES_TICK_MS = 2_000;
5
+ export const MAX_LOG = 60;
6
+ export const MAX_PROPOSALS = 20;
7
+ export function newRulesUi(engine = new RulesEngine()) {
8
+ return {
9
+ engine, errors: [], pane: 'rules', selected: 0, selectedProposal: 0, input: null, editingId: null, dry: null, notice: null,
10
+ log: [], proposals: [], pending: { popups: [], bells: 0, ask: null, ticket: null }, lastTickAt: 0, nextId: 1, fires: 0,
11
+ };
12
+ }
13
+ /**
14
+ * Build the engine from `config.rules` plus the rules of installed packs (task-1358 Tier 4: a pack rule whose id the
15
+ * user already has in config.rules is shadowed by the user's copy). Bad rules are reported, good ones run. Never throws.
16
+ */
17
+ export function initRules(custom, now = Date.now()) {
18
+ const own = custom?.config.rules ?? [];
19
+ const ids = new Set(own.map((r) => r.id));
20
+ const rules = [...own, ...(custom?.packRules ?? []).filter((r) => !ids.has(r.id))];
21
+ const engine = new RulesEngine({ rules, now });
22
+ const ui = newRulesUi(engine);
23
+ ui.errors = engine.errors.map((e) => `rule ${e.id}: ${e.msg}`);
24
+ if (custom)
25
+ for (const e of ui.errors)
26
+ if (!custom.configErrors.includes(e))
27
+ custom.configErrors.push(e);
28
+ return ui;
29
+ }
30
+ /** The rules state for this run, created from the loaded config on first use. */
31
+ export function rulesUiOf(state) {
32
+ if (!state.rulesUi)
33
+ state.rulesUi = initRules(state.custom, state.now.getTime());
34
+ return state.rulesUi;
35
+ }
36
+ /** The rows a rule can see: the screener feed (no skeletons) with the wallet-intel top-cluster share attached when the scan ran. */
37
+ export function ruleRowsOf(state) {
38
+ const out = [];
39
+ const seen = new Set();
40
+ const clusterOf = (mint) => {
41
+ const res = state.live?.intel.entry(mint)?.result;
42
+ if (!res || res.state !== 'ok')
43
+ return null;
44
+ const c = res.analysis.clusters[0];
45
+ return c ? c.pct : null;
46
+ };
47
+ for (const r of state.screener.rows) {
48
+ if (!r.mint || r.skeleton || seen.has(r.mint))
49
+ continue;
50
+ seen.add(r.mint);
51
+ out.push(Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint) }));
52
+ }
53
+ return out;
54
+ }
55
+ /**
56
+ * One engine tick: observe the rows, evaluate, and turn the outputs into app state. Self-throttled to RULES_TICK_MS.
57
+ * Alerts become the first pending toast + log lines; ticket/ask become PROPOSAL cards; watch/tag touch the user's
58
+ * own watchlist (live mode) or just the log; popup/sound queue UI work for the loop. Returns null when throttled.
59
+ */
60
+ export function rulesTick(state, now) {
61
+ const ui = state.rulesUi;
62
+ if (!ui || !ui.engine.rules.some((r) => r.enabled))
63
+ return null;
64
+ if (now - ui.lastTickAt < RULES_TICK_MS)
65
+ return null;
66
+ ui.lastTickAt = now;
67
+ const rows = ruleRowsOf(state);
68
+ if (!rows.length)
69
+ return null;
70
+ ui.engine.observe(rows, now);
71
+ const watch = state.live?.watch.store ?? null;
72
+ const positions = state.live?.positions ?? null;
73
+ const res = ui.engine.evaluate(rows, now, {
74
+ watchlist: watch ? { has: (m) => watch.has(m) } : undefined,
75
+ positions: positions ? { has: (m) => positions.held(m) !== null } : undefined,
76
+ });
77
+ const summary = applyOutputs(state, ui, res, rows, now);
78
+ if (ui.input !== null)
79
+ ui.dry = ui.engine.dryRun(ui.input, rows, now);
80
+ return summary;
81
+ }
82
+ function applyOutputs(state, ui, res, rows, now) {
83
+ const summary = { fired: res.fired.length, alerts: [], proposals: 0, suppressed: res.suppressed.length };
84
+ const rowOf = (mint) => rows.find((r) => r.mint === mint);
85
+ const push = (e) => { ui.log.unshift(e); if (ui.log.length > MAX_LOG)
86
+ ui.log.length = MAX_LOG; };
87
+ ui.fires += res.fired.length;
88
+ for (const f of res.fired) {
89
+ for (const a of f.actions) {
90
+ const base = { at: now, rule: f.rule, name: f.name, source: f.source, mint: f.mint, symbol: f.symbol };
91
+ if (a.kind === 'alert') {
92
+ push({ ...base, kind: 'alert', level: a.level, text: a.text });
93
+ summary.alerts.push(`${f.symbol}: ${a.text}`);
94
+ }
95
+ else if (a.kind === 'proposal') {
96
+ const card = a.type === 'ticket'
97
+ ? { id: ui.nextId++, ...base, type: 'ticket', side: a.side, size: a.size, unit: a.unit, why: a.why, status: 'proposal', requiresKeypress: true, armedBy: null }
98
+ : { id: ui.nextId++, ...base, type: 'ask', skill: a.skill, why: a.why, status: 'proposal', requiresKeypress: true, armedBy: null };
99
+ ui.proposals.unshift(card);
100
+ if (ui.proposals.length > MAX_PROPOSALS)
101
+ ui.proposals.length = MAX_PROPOSALS;
102
+ summary.proposals += 1;
103
+ push({ ...base, kind: 'proposal', level: 'info', text: `PROPOSAL ${describeCard(card)} — ctrl+r, tab, ⏎ to act (you confirm there)` });
104
+ }
105
+ else if (a.kind === 'annotation') {
106
+ const store = state.live?.watch.store ?? null;
107
+ const row = rowOf(f.mint);
108
+ if (a.type === 'watch') {
109
+ if (store && !store.has(f.mint) && row) {
110
+ const r = row;
111
+ store.add({ mint: r.mint, symbol: r.symbol, name: r.name ?? r.symbol, launchpad: r.launchpad ?? 'unknown', entryPrice: r.price ?? null, note: `rule:${f.rule}` });
112
+ push({ ...base, kind: 'annotation', level: 'info', text: `watch: added ${f.symbol} to the watchlist (rule ${f.rule})` });
113
+ }
114
+ else
115
+ push({ ...base, kind: 'annotation', level: 'info', text: store ? `watch: ${f.symbol} already on the watchlist` : `watch: ${f.symbol} (watchlist needs live mode)` });
116
+ }
117
+ else {
118
+ if (store && store.has(f.mint)) {
119
+ store.setNote(f.mint, `rule:${f.rule} ${a.label}`.slice(0, 80));
120
+ push({ ...base, kind: 'annotation', level: 'info', text: `tag "${a.label}" on ${f.symbol} (watchlist note)` });
121
+ }
122
+ else
123
+ push({ ...base, kind: 'annotation', level: 'info', text: `tag "${a.label}" on ${f.symbol}` });
124
+ }
125
+ }
126
+ else if (a.kind === 'ui') {
127
+ if (a.type === 'popup') {
128
+ ui.pending.popups.push({ kind: a.popup, mint: f.mint, symbol: f.symbol, rule: f.rule });
129
+ push({ ...base, kind: 'ui', level: 'info', text: `popup ${a.popup} for ${f.symbol}` });
130
+ }
131
+ else {
132
+ ui.pending.bells += 1;
133
+ push({ ...base, kind: 'ui', level: 'info', text: `sound ${a.name}` });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ if (summary.alerts.length)
139
+ state.pendingToast = summary.alerts.length === 1 ? `⚑ ${summary.alerts[0]}` : `⚑ ${summary.alerts[0]} (+${summary.alerts.length - 1} more · ctrl+r)`;
140
+ else if (summary.proposals)
141
+ state.pendingToast = `⚑ ${summary.proposals} rule proposal${summary.proposals > 1 ? 's' : ''} · ctrl+r then tab to review (nothing armed)`;
142
+ return summary;
143
+ }
144
+ export function describeCard(c) {
145
+ return c.type === 'ticket' ? `ticket ${c.side.toUpperCase()} ${c.size} ${c.unit} on ${c.symbol}` : `ask ${c.skill} about ${c.symbol}`;
146
+ }
147
+ // ------------------------------------------------------------------ editing + persistence
148
+ export function selectedRule(ui) {
149
+ const rules = ui.engine.rules;
150
+ if (!rules.length)
151
+ return null;
152
+ return rules[Math.max(0, Math.min(rules.length - 1, ui.selected))] ?? null;
153
+ }
154
+ export function selectedProposal(ui) {
155
+ if (!ui.proposals.length)
156
+ return null;
157
+ return ui.proposals[Math.max(0, Math.min(ui.proposals.length - 1, ui.selectedProposal))] ?? null;
158
+ }
159
+ /** Re-run the dry-run of the text being typed against the current rows. */
160
+ export function dryRunNow(state) {
161
+ const ui = state.rulesUi;
162
+ if (!ui || ui.input === null)
163
+ return;
164
+ const text = ui.input.trim();
165
+ if (!text) {
166
+ ui.dry = null;
167
+ return;
168
+ }
169
+ ui.dry = ui.engine.dryRun(text, ruleRowsOf(state), state.now.getTime());
170
+ }
171
+ /** Begin typing: a new rule, or the selected rule's text when `edit` is set. */
172
+ export function beginRuleInput(ui, edit) {
173
+ if (edit) {
174
+ const r = selectedRule(ui);
175
+ if (!r) {
176
+ ui.notice = 'no rule selected';
177
+ return;
178
+ }
179
+ ui.editingId = r.id;
180
+ ui.input = ruleToText(compiledToJson(r));
181
+ }
182
+ else {
183
+ ui.editingId = null;
184
+ ui.input = '';
185
+ }
186
+ ui.dry = null;
187
+ ui.notice = null;
188
+ }
189
+ function compiledToJson(r) {
190
+ const json = { id: r.id, name: r.name, enabled: r.enabled, when: r.whenText, then: r.then.map((a) => ({ action: a.action, args: { ...a.args } })), scope: r.scope, cooldown: msToDur(r.cooldownMs), maxPerHour: r.maxPerHour, window: msToDur(r.windowMs), source: r.source };
191
+ if (r.for)
192
+ json.for = 'ticks' in r.for ? `${r.for.ticks} ticks` : msToDur(r.for.ms);
193
+ return json;
194
+ }
195
+ function msToDur(ms) { return ms % 3_600_000 === 0 && ms > 0 ? `${ms / 3_600_000}h` : ms % 60_000 === 0 && ms > 0 ? `${ms / 60_000}m` : `${Math.round(ms / 1000)}s`; }
196
+ /** Persist one change to `config.rules` through the journal (actor: user). Throws with a reason when it cannot. */
197
+ function persist(state, ops, why) {
198
+ const custom = state.custom;
199
+ if (!custom)
200
+ throw new Error('no config loaded (one-shot frame)');
201
+ const { config } = applyAndJournal(custom.home, ops, { actor: 'user', why, now: state.now.getTime() });
202
+ custom.config = config;
203
+ }
204
+ function ruleIndex(state, id) { return (state.custom?.config.rules ?? []).findIndex((r) => r.id === id); }
205
+ /** Save the text being typed: parse → JSON → config (add or replace) → engine. Returns the notice. */
206
+ export function saveRuleInput(state) {
207
+ const ui = rulesUiOf(state);
208
+ const text = (ui.input ?? '').trim();
209
+ const editingId = ui.editingId;
210
+ ui.input = null;
211
+ ui.editingId = null;
212
+ ui.dry = null;
213
+ if (!text)
214
+ return 'nothing saved (empty rule)';
215
+ let json;
216
+ try {
217
+ const existing = editingId ? ui.engine.get(editingId) : null;
218
+ json = ruleToJson(text, { id: editingId ?? undefined, source: existing?.source ?? 'user', enabled: existing ? existing.enabled : true });
219
+ if (!editingId) {
220
+ // a new rule never overwrites another one: bump the slug until it is free
221
+ const taken = new Set([...ui.engine.rules.map((r) => r.id), ...(state.custom?.config.rules ?? []).map((r) => r.id)]);
222
+ let id = json.id;
223
+ let n = 2;
224
+ while (taken.has(id))
225
+ id = slug(`${json.id}-${n++}`);
226
+ json.id = id;
227
+ }
228
+ if (json.source !== 'user')
229
+ json.source = 'user'; // an edited pack/jarvis rule becomes the user's own (it no longer matches the pack)
230
+ const probe = new RulesEngine();
231
+ probe.load(json);
232
+ }
233
+ catch (e) {
234
+ return `not saved: ${e.message}`;
235
+ }
236
+ try {
237
+ if (editingId) {
238
+ const i = ruleIndex(state, editingId);
239
+ persist(state, i >= 0 ? [{ op: 'replace', path: `/rules/${i}`, value: json }] : [{ op: 'add', path: '/rules/-', value: json }], `edit rule ${json.id}`);
240
+ }
241
+ else
242
+ persist(state, [{ op: 'add', path: '/rules/-', value: json }], `add rule ${json.id}`);
243
+ }
244
+ catch (e) {
245
+ const msg = e.message;
246
+ if (!/no config loaded/.test(msg))
247
+ return `not saved: ${msg}`;
248
+ ui.engine.load(json);
249
+ return `rule ${json.id} loaded for this run only (${msg})`;
250
+ }
251
+ ui.engine.load(json);
252
+ ui.selected = Math.max(0, ui.engine.rules.findIndex((r) => r.id === json.id));
253
+ return `${editingId ? 'updated' : 'saved'} rule ${json.id}: ${describeActions(ui.engine.get(json.id))}`;
254
+ }
255
+ export function removeSelectedRule(state) {
256
+ const ui = rulesUiOf(state);
257
+ const r = selectedRule(ui);
258
+ if (!r)
259
+ return 'no rule selected';
260
+ try {
261
+ const i = ruleIndex(state, r.id);
262
+ if (i >= 0)
263
+ persist(state, [{ op: 'remove', path: `/rules/${i}` }], `remove rule ${r.id}`);
264
+ }
265
+ catch (e) {
266
+ if (!/no config loaded/.test(e.message))
267
+ return `not removed: ${e.message}`;
268
+ }
269
+ ui.engine.remove(r.id);
270
+ ui.selected = Math.max(0, Math.min(ui.engine.rules.length - 1, ui.selected));
271
+ return `removed rule ${r.id}`;
272
+ }
273
+ export function toggleSelectedRule(state) {
274
+ const ui = rulesUiOf(state);
275
+ const r = selectedRule(ui);
276
+ if (!r)
277
+ return 'no rule selected';
278
+ const on = !r.enabled;
279
+ let copied = false;
280
+ try {
281
+ const i = ruleIndex(state, r.id);
282
+ if (i >= 0)
283
+ persist(state, [{ op: 'replace', path: `/rules/${i}/enabled`, value: on }], `${on ? 'enable' : 'disable'} rule ${r.id}`);
284
+ else if (r.source.startsWith('pack:')) {
285
+ // task-1358 Tier 4: a pack rule is read-only inside its pack; enabling it makes the USER's copy in config.rules
286
+ // (same id, same source label, the user's enabled flag). The pack's version stays shadowed from then on.
287
+ const packRule = state.custom?.packRules.find((p) => p.id === r.id);
288
+ persist(state, [{ op: 'add', path: '/rules/-', value: { ...(packRule ?? compiledToJson(r)), enabled: on } }], `${on ? 'enable' : 'disable'} pack rule ${r.id} (copied to config.rules)`);
289
+ copied = true;
290
+ }
291
+ }
292
+ catch (e) {
293
+ if (!/no config loaded/.test(e.message))
294
+ return `not changed: ${e.message}`;
295
+ }
296
+ ui.engine.setEnabled(r.id, on);
297
+ return `rule ${r.id} ${on ? 'enabled' : 'disabled'}${copied ? ' (your copy of the pack rule; dexterm rules remove to drop it)' : ''}`;
298
+ }
299
+ /** Dismiss the selected proposal (nothing else happens: it was inert). */
300
+ export function dismissSelectedProposal(state) {
301
+ const ui = rulesUiOf(state);
302
+ const c = selectedProposal(ui);
303
+ if (!c)
304
+ return 'no proposal selected';
305
+ ui.proposals = ui.proposals.filter((p) => p.id !== c.id);
306
+ ui.selectedProposal = Math.max(0, Math.min(ui.proposals.length - 1, ui.selectedProposal));
307
+ ui.log.unshift({ at: state.now.getTime(), rule: c.rule, name: c.name, source: c.source, mint: c.mint, symbol: c.symbol, kind: 'dismissed', level: 'info', text: `dismissed ${describeCard(c)}` });
308
+ return `dismissed ${describeCard(c)}`;
309
+ }
310
+ /**
311
+ * Act on the selected proposal: queue the PAPER ticket editor (the user sizes + confirms there) or the analyst
312
+ * question for the loop. The card is consumed either way; nothing is armed, confirmed or sent here.
313
+ */
314
+ export function actOnSelectedProposal(state) {
315
+ const ui = rulesUiOf(state);
316
+ const c = selectedProposal(ui);
317
+ if (!c)
318
+ return 'no proposal selected';
319
+ const row = state.screener.rows.find((r) => r.mint === c.mint);
320
+ if (c.type === 'ticket') {
321
+ ui.pending.ticket = { mint: c.mint, symbol: c.symbol, name: row?.name ?? c.symbol, side: c.side, size: c.size, unit: c.unit };
322
+ }
323
+ else {
324
+ ui.pending.ask = `${c.skill.replace(/-/g, ' ')} for ${c.symbol} (${c.mint}) — rule "${c.name}" fired: ${c.why}`.slice(0, 400);
325
+ }
326
+ ui.proposals = ui.proposals.filter((p) => p.id !== c.id);
327
+ ui.selectedProposal = Math.max(0, Math.min(ui.proposals.length - 1, ui.selectedProposal));
328
+ ui.log.unshift({ at: state.now.getTime(), rule: c.rule, name: c.name, source: c.source, mint: c.mint, symbol: c.symbol, kind: 'acted', level: 'info', text: c.type === 'ticket' ? `opening the PAPER ticket editor for ${c.symbol} (${c.side} ${c.size} ${c.unit} proposed; you size + confirm)` : `asking the analyst: ${c.skill} for ${c.symbol}` });
329
+ return c.type === 'ticket' ? `PAPER ticket editor for ${c.symbol} — proposed ${c.side} ${c.size} ${c.unit}; nothing is confirmed until you do` : `asking the analyst (${c.skill}) about ${c.symbol}`;
330
+ }
331
+ /** Move the cursor on whichever pane is active. */
332
+ export function rulesMove(ui, delta) {
333
+ if (ui.input !== null)
334
+ return;
335
+ if (ui.pane === 'proposals')
336
+ ui.selectedProposal = Math.max(0, Math.min(Math.max(0, ui.proposals.length - 1), ui.selectedProposal + delta));
337
+ else
338
+ ui.selected = Math.max(0, Math.min(Math.max(0, ui.engine.rules.length - 1), ui.selected + delta));
339
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Parse a typed command: `/scan` (toggle) · `/scan on|start` · `/scan off|stop` · `/scan resume|next`
3
+ * · `/scan status`. The leading slash is optional (the screener's `/` line already ate it). Anything
4
+ * else — including a real screener filter — returns null and is handled as before.
5
+ */
6
+ export function parseScanCommand(text) {
7
+ const m = /^\s*\/?scan(?:\s+(\S+))?\s*$/i.exec(String(text ?? ''));
8
+ if (!m)
9
+ return null;
10
+ const arg = (m[1] ?? '').toLowerCase();
11
+ if (arg === '')
12
+ return 'toggle';
13
+ if (arg === 'on' || arg === 'start')
14
+ return 'on';
15
+ if (arg === 'off' || arg === 'stop')
16
+ return 'off';
17
+ if (arg === 'resume' || arg === 'next')
18
+ return 'resume';
19
+ if (arg === 'status')
20
+ return 'status';
21
+ return { error: `unknown /scan command "${arg.slice(0, 16)}" (on · off · stop · resume · status)` };
22
+ }
23
+ /** `--scan-*` flags → ScanConfig keys (values are numbers). */
24
+ export const SCAN_FLAGS = {
25
+ '--scan-min-score': 'minScore',
26
+ '--scan-min-triggers': 'minTriggers',
27
+ '--scan-min-liq': 'minLiqUsd',
28
+ '--scan-vol-x': 'volX',
29
+ '--scan-flow-share': 'flowShare',
30
+ '--scan-breakout-pct': 'breakoutPct',
31
+ '--scan-liq-jump-pct': 'liqJumpPct',
32
+ '--scan-holders-jump-pct': 'holdersJumpPct',
33
+ '--scan-warmup-s': 'warmupS',
34
+ '--scan-max-ai-per-hour': 'maxAiPerHour',
35
+ '--scan-max-ai-per-coin': 'maxAiPerCoin',
36
+ '--scan-refresh-s': 'refreshS',
37
+ '--scan-move-pct': 'movePct',
38
+ '--scan-cooldown-s': 'coinCooldownS',
39
+ '--scan-post-lock-s': 'postLockS',
40
+ };
41
+ /** Range check for a flag value (non-negative; shares are 0..1; caps are integers ≥ 0). */
42
+ export function checkScanFlag(key, n) {
43
+ if (!Number.isFinite(n) || n < 0)
44
+ return 'expects a non-negative number';
45
+ if (key === 'flowShare' && n > 1)
46
+ return 'expects a share 0..1 (e.g. 0.62)';
47
+ if ((key === 'maxAiPerHour' || key === 'maxAiPerCoin' || key === 'minTriggers') && !Number.isInteger(n))
48
+ return 'expects a whole number';
49
+ return null;
50
+ }
51
+ export const SCAN_HELP = ` --scan start AI SCAN MODE on launch (also: Z key, or /scan on|off|resume in the / line or chat)
52
+ --scan-min-score N lock when a coin scores ≥ N (default 45) with ≥ --scan-min-triggers (default 2) triggers
53
+ --scan-vol-x N volume-spike trigger: 5m volume ≥ N× the coin's own pace (default 3)
54
+ --scan-max-ai-per-hour N analyst turns the scan may start per hour (default 8; per coin --scan-max-ai-per-coin 4)
55
+ --scan-refresh-s N min seconds between reads of the locked coin (default 90; only when data moved)
56
+ more: --scan-min-liq --scan-flow-share --scan-breakout-pct --scan-liq-jump-pct --scan-holders-jump-pct
57
+ --scan-warmup-s --scan-move-pct --scan-cooldown-s --scan-post-lock-s`;