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,462 @@
1
+ // @dexterm/data — the social feed engine (task-1330, lane I-03). One instance per run, over the shared
2
+ // data layer. It owns:
3
+ // • ingest(): firewall → store → dedupe → burst-spam → squat rules, for every RawPost from any source
4
+ // (news RSS and on-chain rows today; X posts from the user's own login in lane I-02; TG/Discord bots)
5
+ // • the keyless venue layer (TrendingVenues) refreshed every VENUE_REFRESH_MS, news every NEWS_REFRESH_MS
6
+ // • trendRows() / narratives() / newsItems() / postsFor(mint) — pure reads over what is in memory
7
+ // It never creates timers: the UI loop calls tick(now) at its 2 Hz data tick and awaits nothing, so a
8
+ // quit never waits on this module. Posts are retained for RETENTION_MS (24 h, design §6 "stored locally
9
+ // for 24 h for dedup/billing only") and pruned on tick.
10
+ import { systemClock } from "../clock.js";
11
+ import { clusterPosts, countsForVelocity, markBurstSpam, markSquats } from "./dedupe.js";
12
+ import { firewallText } from "./firewall.js";
13
+ import { POST_STORED_MAX } from "./model.js";
14
+ import { buildNarratives, buildTrendRow, credibilityOf, rankRows } from "./rank.js";
15
+ import { NewsRss, NEWS_REFRESH_MS, newsTag } from "./rss.js";
16
+ import { TrendingVenues } from "./venues.js";
17
+ export const VENUE_REFRESH_MS = 60_000;
18
+ export const RETENTION_MS = 24 * 3_600_000;
19
+ export const SOCIAL_WINDOW_MS = 3_600_000;
20
+ /** Hard cap on stored posts (memory guard; oldest pruned first). */
21
+ export const POSTS_MAX = 20_000;
22
+ export function socialDeps(dl) {
23
+ return { venues: new TrendingVenues({ http: dl.http, jupiter: dl.jupiter, dexscreener: dl.dexscreener }), news: new NewsRss(dl.http) };
24
+ }
25
+ export class SocialFeed {
26
+ clock;
27
+ now;
28
+ kol;
29
+ venues;
30
+ news;
31
+ board;
32
+ mintsForSymbolFn;
33
+ venueRefreshMs;
34
+ newsRefreshMs;
35
+ onChange;
36
+ posts = new Map();
37
+ byMint = new Map();
38
+ /** Original base58 addresses per post id (squat rule input); not part of the stored post. */
39
+ addresses = new Map();
40
+ observations = new Map();
41
+ labels = new Map();
42
+ dirtyMints = new Set();
43
+ venue = new Map();
44
+ coingecko = [];
45
+ venueFetchedAt = {};
46
+ venueErrors = {};
47
+ newsErrors = {};
48
+ newsFetchedAt = null;
49
+ lastVenueAt = 0;
50
+ lastNewsAt = 0;
51
+ venueBusy = false;
52
+ newsBusy = false;
53
+ closed = false;
54
+ lastPrune = 0;
55
+ /** The X poller (task-1357), attached by the live context when the user is logged in; null = never read X. */
56
+ x = null;
57
+ /** Bumped by every mutation; with the 2 s time bucket it keys the trendRows() memo (renders run at up to 30 fps). */
58
+ version = 0;
59
+ memo = null;
60
+ constructor(opts = {}) {
61
+ this.clock = opts.clock ?? systemClock;
62
+ this.now = opts.now ?? (() => this.clock.now());
63
+ this.kol = opts.kol ?? null;
64
+ this.venues = opts.venues ?? null;
65
+ this.news = opts.news ?? null;
66
+ this.board = opts.board ?? (() => []);
67
+ this.mintsForSymbolFn = opts.mintsForSymbol ?? null;
68
+ this.venueRefreshMs = opts.venueRefreshMs ?? VENUE_REFRESH_MS;
69
+ this.newsRefreshMs = opts.newsRefreshMs ?? NEWS_REFRESH_MS;
70
+ this.onChange = opts.onChange ?? (() => { });
71
+ }
72
+ /** Attach the X poller: the feed ticks it and becomes its sink. Detach with null. */
73
+ attachX(x) {
74
+ if (this.x)
75
+ this.x.sink = null;
76
+ this.x = x;
77
+ if (x)
78
+ x.sink = (raws) => { if (!this.closed)
79
+ this.ingest(raws); };
80
+ this.version += 1;
81
+ this.memo = null;
82
+ this.onChange('social:x');
83
+ }
84
+ /** The X poller's status, or a synthetic logged-out/off one. */
85
+ xStatus() {
86
+ if (this.x)
87
+ return this.x.status();
88
+ return { state: 'logged-out', user: null, detail: 'logged out · dexterm x login (your own X app, your limits)', lastOkAt: null, lastError: null, resumesAt: null, spend: null, windows: {}, posts: 0, requests: 0 };
89
+ }
90
+ // ------------------------------------------------------------------ ingest
91
+ /** Firewall + store + rules. Idempotent per post id (re-ingesting updates metrics only). */
92
+ ingest(raws) {
93
+ const res = { accepted: 0, flagged: { injection: 0, spam: 0, squat: 0, dupes: 0 } };
94
+ const now = this.now();
95
+ for (const r of raws) {
96
+ if (!r || typeof r.id !== 'string' || !r.id || typeof r.text !== 'string')
97
+ continue;
98
+ if (now - r.t > RETENTION_MS)
99
+ continue; // too old to matter (and to keep)
100
+ const existing = this.posts.get(r.id);
101
+ if (existing) {
102
+ existing.metrics = { likes: r.metrics?.likes ?? existing.metrics.likes, reposts: r.metrics?.reposts ?? existing.metrics.reposts, replies: r.metrics?.replies ?? existing.metrics.replies, views: r.metrics?.views ?? existing.metrics.views };
103
+ continue;
104
+ }
105
+ const mints = [...new Set((r.mints ?? []).filter((m) => typeof m === 'string' && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(m)))];
106
+ const fw = firewallText(r.text, { keepMint: mints[0] ?? null, max: POST_STORED_MAX });
107
+ const flags = [];
108
+ if (fw.injection) {
109
+ flags.push('injection');
110
+ res.flagged.injection += 1;
111
+ }
112
+ const author = { id: String(r.author?.id ?? r.author?.handle ?? '?'), handle: cleanHandle(r.author?.handle ?? '?'), tier: r.author?.tier ?? 'none', createdAt: typeof r.author?.createdAt === 'number' ? r.author.createdAt : null };
113
+ // KOL lists decide the tier for X/TG/Discord authors; news domains stay `src`, chain rows keep theirs.
114
+ if (this.kol && (r.platform === 'x' || r.platform === 'telegram' || r.platform === 'discord')) {
115
+ if (this.kol.isMuted(author.handle))
116
+ continue; // muted handle: never stored
117
+ const tier = this.kol.tierOf(author.handle);
118
+ if (tier)
119
+ author.tier = tier;
120
+ }
121
+ const post = {
122
+ id: r.id, t: r.t, platform: r.platform, author,
123
+ text: fw.text, url: safeUrl(r.url), lang: r.lang ?? null,
124
+ metrics: { likes: num(r.metrics?.likes), reposts: num(r.metrics?.reposts), replies: num(r.metrics?.replies), views: r.metrics?.views ?? null },
125
+ mints, cashtags: fw.cashtags, cluster: null, dupCount: 1, hidden: false, flags, collapsed: null,
126
+ provenance: { source: String(r.source), fetchedAt: r.fetchedAt },
127
+ };
128
+ // News headlines are matched to the board by symbol/name (never trusted as the post's claim).
129
+ if (post.platform === 'news') {
130
+ if (post.mints.length === 0)
131
+ post.mints = this.matchBoard(post.text);
132
+ this.newsFetchedAt = Math.max(this.newsFetchedAt ?? 0, r.fetchedAt);
133
+ }
134
+ this.posts.set(post.id, post);
135
+ this.addresses.set(post.id, fw.addresses);
136
+ for (const m of post.mints.length ? post.mints : ['']) {
137
+ const s = this.byMint.get(m) ?? new Set();
138
+ s.add(post.id);
139
+ this.byMint.set(m, s);
140
+ this.dirtyMints.add(m);
141
+ }
142
+ res.accepted += 1;
143
+ }
144
+ if (res.accepted) {
145
+ this.applyRules();
146
+ this.version += 1;
147
+ this.memo = null;
148
+ if (this.posts.size > POSTS_MAX)
149
+ this.prune(now, true);
150
+ this.onChange('social:ingest');
151
+ }
152
+ return res;
153
+ }
154
+ /** Re-run the deterministic rules on the mints touched since the last run. */
155
+ applyRules() {
156
+ if (this.dirtyMints.size === 0)
157
+ return;
158
+ const now = this.now();
159
+ const mintsForSymbol = this.mintsForSymbolFn ?? ((sym) => this.board().filter((b) => b.symbol.toUpperCase() === sym.toUpperCase()).map((b) => b.mint));
160
+ for (const m of this.dirtyMints) {
161
+ const posts = [...(this.byMint.get(m) ?? [])].map((id) => this.posts.get(id)).filter((p) => !!p);
162
+ clusterPosts(posts);
163
+ markBurstSpam(posts, { now });
164
+ markSquats(posts, { mintsForSymbol, addressesOf: (p) => this.addresses.get(p.id) ?? [] });
165
+ }
166
+ this.dirtyMints.clear();
167
+ }
168
+ /** Board tokens whose symbol (as `$SYM` or a whole word) or name appears in the headline. */
169
+ matchBoard(text) {
170
+ const out = [];
171
+ const upper = text.toUpperCase();
172
+ for (const b of this.board()) {
173
+ const sym = b.symbol.toUpperCase();
174
+ if (sym.length < 3)
175
+ continue; // SOL/BTC-length symbols match everything
176
+ const re = new RegExp(`(^|[^A-Z0-9])\\$?${sym.replace(/[^A-Z0-9_]/g, '')}([^A-Z0-9]|$)`);
177
+ if (re.test(upper) || (b.name && b.name.length >= 5 && upper.includes(b.name.toUpperCase())))
178
+ out.push(b.mint);
179
+ }
180
+ return [...new Set(out)].slice(0, 6);
181
+ }
182
+ // ------------------------------------------------------------------ reads
183
+ postsFor(mint, opts = {}) {
184
+ const ids = this.byMint.get(mint);
185
+ if (!ids)
186
+ return [];
187
+ const now = this.now();
188
+ const out = [];
189
+ for (const id of ids) {
190
+ const p = this.posts.get(id);
191
+ if (!p)
192
+ continue;
193
+ if (opts.sinceMs !== undefined && now - p.t > opts.sinceMs)
194
+ continue;
195
+ if (p.hidden && !opts.includeHidden)
196
+ continue;
197
+ if (p.flags.includes('spam') && !opts.includeSpam && !p.collapsed)
198
+ continue;
199
+ if (p.flags.includes('injection') && !opts.includeInjection)
200
+ continue;
201
+ out.push(p);
202
+ }
203
+ return out.sort((a, b) => b.t - a.t);
204
+ }
205
+ /** Every stored post about a mint (rules input): hidden/spam/injection included. */
206
+ allPostsFor(mint) {
207
+ return [...(this.byMint.get(mint) ?? [])].map((id) => this.posts.get(id)).filter((p) => !!p);
208
+ }
209
+ get size() { return this.posts.size; }
210
+ credibilityOfPost(p) {
211
+ return credibilityOf(p.author, this.observations.get(p.author.handle.replace(/^@/, '').toLowerCase()), this.now()).score;
212
+ }
213
+ /** Record a local observation about a handle (early-entry / confirm-rate); the only way credibility grows. */
214
+ observe(handle, obs) {
215
+ const h = handle.replace(/^@/, '').toLowerCase();
216
+ this.observations.set(h, { ...this.observations.get(h), ...obs });
217
+ this.invalidate();
218
+ }
219
+ /** Label a narrative (from the analyst summary or the user); never from a single post. */
220
+ labelNarrative(id, label) { this.labels.set(id, label.slice(0, 24)); this.invalidate(); }
221
+ setVenue(signals, fetchedAt = {}, coingecko = []) {
222
+ this.venue = signals;
223
+ this.venueFetchedAt = { ...this.venueFetchedAt, ...fetchedAt };
224
+ this.coingecko = coingecko;
225
+ this.lastVenueAt = this.now();
226
+ this.version += 1;
227
+ this.memo = null;
228
+ this.onChange('social:venue');
229
+ }
230
+ venueFor(mint) { return this.venue.get(mint) ?? null; }
231
+ symbolOf(mint) {
232
+ return this.venue.get(mint)?.symbol ?? this.board().find((b) => b.mint === mint)?.symbol ?? this.allPostsFor(mint).find((p) => p.cashtags.length)?.cashtags[0] ?? `${mint.slice(0, 4)}…`;
233
+ }
234
+ /** Posts in the chain layer confirm attention (tracked wallet bought) when a kol-buy row exists in the window. */
235
+ chainConfirmed(mint) {
236
+ const now = this.now();
237
+ return this.allPostsFor(mint).some((p) => p.platform === 'chain' && now - p.t < SOCIAL_WINDOW_MS && /tracked wallet|same slot/.test(p.text));
238
+ }
239
+ /** Invalidate the board memo (call after KOL list edits, which live outside this object). */
240
+ invalidate() { this.version += 1; this.memo = null; }
241
+ /** Ranked board rows over every mint with posts or venue signals (news-only mints excluded). Memoised per 2 s. */
242
+ trendRows() {
243
+ const key = `${this.version}:${Math.floor(this.now() / 2000)}`;
244
+ if (this.memo?.key === key)
245
+ return this.memo.rows;
246
+ const rows = this.computeTrendRows();
247
+ const narratives = this.narrativesFor(rows);
248
+ const byMint = new Map();
249
+ for (const n of narratives)
250
+ for (const m of n.tokens)
251
+ byMint.set(m, n);
252
+ for (const r of rows) {
253
+ const n = byMint.get(r.mint);
254
+ if (n)
255
+ r.narrative = n.label ?? n.symbols.slice(0, 3).join(' ');
256
+ }
257
+ this.memo = { key, rows, narratives };
258
+ return rows;
259
+ }
260
+ computeTrendRows() {
261
+ const now = this.now();
262
+ const kolHandles = this.kol?.kolHandles() ?? new Set();
263
+ const mints = new Set(this.venue.keys());
264
+ for (const [m, ids] of this.byMint) {
265
+ if (!m)
266
+ continue;
267
+ for (const id of ids) {
268
+ const p = this.posts.get(id);
269
+ if (p && p.platform !== 'news') {
270
+ mints.add(m);
271
+ break;
272
+ }
273
+ }
274
+ }
275
+ const rows = [];
276
+ for (const mint of mints) {
277
+ const posts = this.allPostsFor(mint).filter((p) => p.platform !== 'news');
278
+ rows.push(buildTrendRow({
279
+ mint, symbol: this.symbolOf(mint), posts, venue: this.venue.get(mint) ?? null,
280
+ credibility: (a) => credibilityOf(a, this.observations.get(a.handle.replace(/^@/, '').toLowerCase()), now).score,
281
+ kolHandles, chainConfirmed: this.chainConfirmed(mint), now,
282
+ }));
283
+ }
284
+ return rankRows(rows);
285
+ }
286
+ narratives() { this.trendRows(); return this.memo?.narratives ?? []; }
287
+ narrativesFor(rows) {
288
+ const posts = [];
289
+ // headlines are context, not signal: a news item naming two tokens never makes a narrative
290
+ for (const r of rows)
291
+ posts.push(...this.allPostsFor(r.mint).filter((p) => p.platform !== 'news'));
292
+ return buildNarratives(rows, posts, this.kol?.kolHandles() ?? new Set(), this.labels, (m) => this.newsItems().filter((n) => n.crossRefs.includes(m)).length);
293
+ }
294
+ /** The NEWS column: firewalled headlines newest first, tagged, with board cross-refs. */
295
+ newsItems(limit = 40) {
296
+ const out = [];
297
+ for (const p of this.posts.values()) {
298
+ if (p.platform !== 'news' || p.hidden || p.flags.includes('injection'))
299
+ continue;
300
+ const tag = p.mints.length ? 'cross-ref' : newsTag(p.text);
301
+ out.push({ post: p, domain: p.author.handle, tag, crossRefs: p.mints });
302
+ }
303
+ return out.sort((a, b) => b.post.t - a.post.t).slice(0, limit);
304
+ }
305
+ /** News matched to one mint (token social panel). */
306
+ newsFor(mint) { return this.newsItems(200).filter((n) => n.crossRefs.includes(mint)); }
307
+ coingeckoTrending() { return this.coingecko; }
308
+ // ------------------------------------------------------------------ refresh
309
+ /** Called by the UI loop at its data tick; kicks the paced refreshes when due. Never throws. */
310
+ tick(now = this.now()) {
311
+ if (this.closed)
312
+ return;
313
+ if (this.venues && !this.venueBusy && now - this.lastVenueAt >= this.venueRefreshMs)
314
+ void this.refreshVenues();
315
+ if (this.news && !this.newsBusy && now - this.lastNewsAt >= this.newsRefreshMs)
316
+ void this.refreshNews();
317
+ if (this.x) {
318
+ try {
319
+ this.x.tick(now);
320
+ }
321
+ catch { /* the poller reports through its status */ }
322
+ }
323
+ if (now - this.lastPrune >= 60_000)
324
+ this.prune(now);
325
+ }
326
+ async refreshVenues() {
327
+ if (!this.venues || this.venueBusy || this.closed)
328
+ return;
329
+ this.venueBusy = true;
330
+ this.lastVenueAt = this.now();
331
+ try {
332
+ const c = await this.venues.collect((m) => this.board().find((b) => b.mint === m)?.symbol ?? null);
333
+ if (this.closed)
334
+ return;
335
+ this.venueErrors = { ...c.errors };
336
+ this.setVenue(c.signals, c.fetchedAt, c.coingecko);
337
+ }
338
+ catch (e) {
339
+ this.venueErrors = { all: (e instanceof Error ? e.message : String(e)).slice(0, 80) };
340
+ }
341
+ finally {
342
+ this.venueBusy = false;
343
+ }
344
+ }
345
+ async refreshNews() {
346
+ if (!this.news || this.newsBusy || this.closed)
347
+ return;
348
+ this.newsBusy = true;
349
+ this.lastNewsAt = this.now();
350
+ try {
351
+ const r = await this.news.fetchAll();
352
+ if (this.closed)
353
+ return;
354
+ this.newsErrors = r.errors;
355
+ if (r.fetchedAt !== null)
356
+ this.newsFetchedAt = r.fetchedAt;
357
+ this.ingest(r.posts);
358
+ this.onChange('social:news');
359
+ }
360
+ finally {
361
+ this.newsBusy = false;
362
+ }
363
+ }
364
+ /** Drop posts past retention (or down to POSTS_MAX when `hard`). */
365
+ prune(now = this.now(), hard = false) {
366
+ this.lastPrune = now;
367
+ let dropped = 0;
368
+ const drop = (p) => {
369
+ this.posts.delete(p.id);
370
+ this.addresses.delete(p.id);
371
+ for (const m of p.mints.length ? p.mints : ['']) {
372
+ const s = this.byMint.get(m);
373
+ if (s) {
374
+ s.delete(p.id);
375
+ if (s.size === 0)
376
+ this.byMint.delete(m);
377
+ }
378
+ }
379
+ dropped += 1;
380
+ };
381
+ for (const p of [...this.posts.values()])
382
+ if (now - p.t > RETENTION_MS)
383
+ drop(p);
384
+ if (dropped) {
385
+ this.version += 1;
386
+ this.memo = null;
387
+ }
388
+ if (hard && this.posts.size > POSTS_MAX) {
389
+ const sorted = [...this.posts.values()].sort((a, b) => a.t - b.t);
390
+ for (const p of sorted.slice(0, this.posts.size - POSTS_MAX))
391
+ drop(p);
392
+ }
393
+ return dropped;
394
+ }
395
+ status() {
396
+ const now = this.now();
397
+ const age = (t) => (typeof t === 'number' ? Math.max(0, now - t) : null);
398
+ const src = (id, label, t, err, on) => ({
399
+ id, label, ageMs: age(t), state: !on ? 'off' : err ? 'error' : t ? 'ok' : 'never', error: err,
400
+ });
401
+ const v = this.venueFetchedAt;
402
+ // a venue counts as "on" when an adapter exists OR signals were fed in (replay / demo via setVenue)
403
+ const on = (t) => !!this.venues || typeof t === 'number';
404
+ const xs = this.xStatus();
405
+ const sources = [
406
+ // x: `ok` with an age while reads flow; `error` carries the degraded reason; `off` when logged out
407
+ { id: 'x', label: 'x-stream', ageMs: age(xs.lastOkAt), state: xs.state === 'logged-out' || xs.state === 'off' ? 'off' : xs.state === 'ok' ? (xs.lastOkAt ? 'ok' : 'never') : 'error', error: xs.state === 'ok' || xs.state === 'logged-out' || xs.state === 'off' ? undefined : xs.detail },
408
+ src('jup', 'jupiter', v.jup ?? v.org, this.venueErrors.jup ?? this.venueErrors.org, on(v.jup ?? v.org)),
409
+ src('gecko', 'gecko', v.gecko, this.venueErrors.gecko, on(v.gecko)),
410
+ src('boost', 'boosts', v.boost, this.venueErrors.boost, on(v.boost)),
411
+ src('cg', 'coingecko', v.cg, this.venueErrors.cg, on(v.cg)),
412
+ src('news', 'news', this.newsFetchedAt, Object.keys(this.newsErrors).length ? Object.values(this.newsErrors)[0] : undefined, !!this.news || this.newsFetchedAt !== null || [...this.posts.values()].some((p) => p.platform === 'news')),
413
+ ];
414
+ let hiddenSpam = 0, hiddenSquats = 0, hiddenInjection = 0;
415
+ for (const p of this.posts.values()) {
416
+ if (p.flags.includes('injection'))
417
+ hiddenInjection += 1;
418
+ else if (p.flags.includes('spam'))
419
+ hiddenSpam += p.collapsed ? p.collapsed.posts : p.hidden ? 0 : p.dupCount;
420
+ else if (p.flags.includes('squat') || p.flags.includes('unverified-cashtag'))
421
+ hiddenSquats += 1;
422
+ }
423
+ return { sources, posts: this.posts.size, hiddenSpam, hiddenSquats, hiddenInjection, xState: xs.state, xDetail: xs.detail, xUser: xs.user, venueErrors: this.venueErrors, newsErrors: this.newsErrors };
424
+ }
425
+ /** Data ages for the summary label (venue + news), ms. */
426
+ ages() {
427
+ const now = this.now();
428
+ const v = this.venueFetchedAt;
429
+ const out = {};
430
+ if (v.jup ?? v.org)
431
+ out.jupiter = now - (v.jup ?? v.org ?? now);
432
+ if (v.gecko)
433
+ out.gecko = now - v.gecko;
434
+ if (v.boost)
435
+ out.dexscreener = now - v.boost;
436
+ if (this.newsFetchedAt)
437
+ out['news-rss'] = now - this.newsFetchedAt;
438
+ const xs = this.xStatus();
439
+ out['x-stream'] = xs.lastOkAt !== null ? now - xs.lastOkAt : null;
440
+ return out;
441
+ }
442
+ close() { this.closed = true; try {
443
+ this.x?.close();
444
+ }
445
+ catch { /* ignore */ } }
446
+ }
447
+ function num(v) { return typeof v === 'number' && Number.isFinite(v) ? v : 0; }
448
+ function cleanHandle(h) {
449
+ const s = firewallText(h, { max: 40, keepUrls: false }).text.replace(/\s+/g, '');
450
+ return s || '?';
451
+ }
452
+ function safeUrl(u) {
453
+ if (!u || typeof u !== 'string')
454
+ return null;
455
+ try {
456
+ const p = new URL(u);
457
+ return p.protocol === 'https:' || p.protocol === 'http:' ? p.toString().slice(0, 300) : null;
458
+ }
459
+ catch {
460
+ return null;
461
+ }
462
+ }
@@ -0,0 +1,110 @@
1
+ // @dexterm/data — prompt firewall on ingest for social text (task-1330; SPEC §5.3 CL-06, design §5.6).
2
+ //
3
+ // `firewallText()` runs on EVERY post, bio and headline before storage: NFC, strip control / bidi /
4
+ // zero-width / ANSI-OSC, collapse whitespace, replace URLs with `[url]` and base58 addresses with `[addr]`
5
+ // (unless the address is the row's own mint), truncate, and detect instruction-like content (imperatives
6
+ // aimed at an assistant, "ignore previous", role-play markers, fake system/tool markup). Detection sets
7
+ // the `injection` flag; the caller withholds such posts from the model entirely (design: a red flag,
8
+ // never a command). Posts never reach the terminal raw: wide glyphs become ▯ so a row can never overflow
9
+ // its cell, and no escape sequence survives.
10
+ //
11
+ // `untrustedBlock()` is the ONLY path by which a post is handed to the analyst: wrapped as
12
+ // `<untrusted source="x:post" author="…">…</untrusted>` with `<`/`>`/`&`/`"` escaped so the text cannot
13
+ // close the block or open a new one.
14
+ import { POST_STORED_MAX } from "./model.js";
15
+ /** Control, DEL, C1, zero-width, bidi overrides/isolates, BOM, and the Unicode line/paragraph separators. */
16
+ // eslint-disable-next-line no-control-regex
17
+ const STRIP_RE = new RegExp('[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u2064\\u2066-\\u2069\\uFEFF\\u2028\\u2029]', 'g');
18
+ /** ANSI CSI / OSC / SS3 sequences that a stripped ESC could have left behind as printable tails. */
19
+ // eslint-disable-next-line no-control-regex
20
+ const ANSI_RE = /\u001B(?:\[[0-?]*[ -\/]*[@-~]|\][^\u0007\u001B]*(?:\u0007|\u001B\\)?|[@-Z\\\-_])/g;
21
+ const URL_RE = /\b(?:https?:\/\/|www\.)[^\s<>"']+/gi;
22
+ const BASE58_RE = /\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/g;
23
+ const CASHTAG_RE = /\$([A-Za-z][A-Za-z0-9_]{0,15})\b/g;
24
+ /**
25
+ * Instruction-like content aimed at an assistant. Deliberately broad (a false positive hides one post
26
+ * from the model; a false negative is a prompt injection), deterministic, and documented by the tests.
27
+ */
28
+ const INJECTION_PATTERNS = [
29
+ /\bignore\s+(?:all\s+|any\s+|the\s+|your\s+|my\s+|these\s+|those\s+)?(?:previous|prior|above|earlier|preceding|system|initial|original)\s+(?:\w+\s+){0,2}?(?:instructions?|rules?|prompts?|messages?|directions?|guidance|guidelines|context|text|constraints?)\b/i,
30
+ /\bdisregard\s+(?:all\s+|any\s+|the\s+|your\s+)?(?:previous|prior|above|earlier|instructions?|rules?)\b/i,
31
+ /\bforget\s+(?:all\s+|your\s+)?(?:previous|prior|earlier)?\s*(?:instructions?|rules?|prompts?|context)\b/i,
32
+ /\b(?:new|updated|revised|override|overriding)\s+(?:system\s+)?(?:instructions?|prompt|rules?|directive)\b/i,
33
+ /\byou\s+are\s+(?:now\s+)?(?:a|an|the)\s+\w+(?:\s+\w+)?\s*(?:assistant|ai|model|bot|agent)\b/i,
34
+ /\bact\s+as\s+(?:if\s+you|though\s+you|an?\s+(?:ai|assistant|bot|chatbot|model|agent|different|unrestricted|unfiltered|evil|jailbroken|developer|system|human)|my\s+|the\s+(?:user|system|assistant|model|ai))\b/i,
35
+ /\bpretend\s+(?:to\s+be|you\s+are|that)\b/i,
36
+ /\b(?:system|assistant|developer|tool|function)\s*(?:prompt|message|role)?\s*:\s/i,
37
+ /<\s*\/?\s*(?:system|assistant|user|tool|untrusted|instructions?|im_start|im_end)\b[^>]*>/i,
38
+ /\[(?:INST|\/INST|SYSTEM|ASSISTANT)\]/i,
39
+ /\b(?:mark|rate|flag|label|report|call|declare|classify|score)\s+(?:this|the|that)\s+(?:token|coin|project|mint)\s+(?:as\s+)?(?:safe|clean|legit|verified|low[- ]risk|a\s+buy|buy)\b/i,
40
+ /\b(?:do\s+not|don'?t|never)\s+(?:mention|report|flag|include|say|tell)\b.{0,40}\b(?:rug|risk|spam|warning|injection|user)\b/i,
41
+ /\b(?:tell|instruct|advise|recommend)\s+(?:the\s+)?(?:user|trader|reader|them)\s+to\s+(?:buy|sell|ape|send|transfer|approve|sign|connect)\b/i,
42
+ /\b(?:call|invoke|use|run|execute)\s+(?:the\s+)?(?:tool|function|swap|sign|transfer|send|open_ticket|add_to_watchlist|show_in_screener|open_token|switch_feed)\b/i,
43
+ /\bprompt\s+injection\b/i,
44
+ /\bjailbreak\b/i,
45
+ /\bDAN\s+mode\b/,
46
+ ];
47
+ /** East Asian wide/fullwidth ranges and emoji blocks: two columns on most terminals. */
48
+ export function isWideCodePoint(cp) {
49
+ return (cp >= 0x1100 && cp <= 0x115f) || (cp >= 0x2e80 && cp <= 0x303e) || (cp >= 0x3041 && cp <= 0x33ff)
50
+ || (cp >= 0x3400 && cp <= 0x4dbf) || (cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0xa000 && cp <= 0xa4cf)
51
+ || (cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff) || (cp >= 0xfe30 && cp <= 0xfe4f)
52
+ || (cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6) || (cp >= 0x1f000 && cp <= 0x1faff)
53
+ || (cp >= 0x2600 && cp <= 0x27bf) || (cp >= 0x20000 && cp <= 0x3fffd);
54
+ }
55
+ /**
56
+ * Make a string safe for the one-column cell canvas: drop combining marks / variation selectors /
57
+ * skin tones, replace wide glyphs with ▯, keep everything else. Idempotent.
58
+ */
59
+ export function terminalSafe(s) {
60
+ let out = '';
61
+ for (const ch of s) {
62
+ const cp = ch.codePointAt(0);
63
+ if ((cp >= 0x0300 && cp <= 0x036f) || (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0x1f3fb && cp <= 0x1f3ff) || cp === 0x200d)
64
+ continue;
65
+ if (cp < 0x20 || (cp >= 0x7f && cp <= 0x9f))
66
+ continue;
67
+ out += isWideCodePoint(cp) ? '▯' : ch;
68
+ }
69
+ return out;
70
+ }
71
+ /** The firewall proper. Total: never throws, always returns a string (possibly empty). */
72
+ export function firewallText(input, opts = {}) {
73
+ const raw = input === null || input === undefined ? '' : String(input);
74
+ const cashtags = [...new Set([...raw.matchAll(CASHTAG_RE)].map((m) => m[1].toUpperCase()))];
75
+ const addresses = [...new Set(raw.match(BASE58_RE) ?? [])];
76
+ let s = raw.normalize('NFC');
77
+ const before = s.length;
78
+ s = s.replace(ANSI_RE, '');
79
+ s = s.replace(STRIP_RE, '');
80
+ const strippedChars = before - s.length;
81
+ s = s.replace(/\s+/g, ' ').trim();
82
+ s = terminalSafe(s);
83
+ // Detect BEFORE the URL/address rewrite so "visit [url] and ignore previous" still reads naturally,
84
+ // and on the de-obfuscated text (zero-width joiners inside "ig​nore" are already gone).
85
+ const rules = [];
86
+ INJECTION_PATTERNS.forEach((re, i) => { if (re.test(s))
87
+ rules.push(i); });
88
+ if (!opts.keepUrls)
89
+ s = s.replace(URL_RE, '[url]');
90
+ const keep = opts.keepMint ?? null;
91
+ s = s.replace(BASE58_RE, (m) => (keep && m === keep ? m : '[addr]'));
92
+ const max = opts.max ?? POST_STORED_MAX;
93
+ let truncated = false;
94
+ if (s.length > max) {
95
+ s = s.slice(0, Math.max(0, max - 1)) + '…';
96
+ truncated = true;
97
+ }
98
+ return { text: s, injection: rules.length > 0, rules, cashtags, addresses, truncated, strippedChars };
99
+ }
100
+ const escapeAttr = (v) => v.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
101
+ /**
102
+ * Wrap already-firewalled text for the analyst. Escapes `<>&"` inside so the content can neither close
103
+ * this block nor open another. `source` is like `x:post`, `news:coindesk.com`, `chain:rule`.
104
+ */
105
+ export function untrustedBlock(text, source, author, extra = {}) {
106
+ const attrs = Object.entries(extra).map(([k, v]) => ` ${k}="${escapeAttr(String(v))}"`).join('');
107
+ return `<untrusted source="${escapeAttr(source)}" author="${escapeAttr(author)}"${attrs}>${escapeAttr(text)}</untrusted>`;
108
+ }
109
+ /** Number of injection rules in the table (tests pin it so a rule change is a deliberate diff). */
110
+ export const INJECTION_RULE_COUNT = INJECTION_PATTERNS.length;
@@ -0,0 +1,11 @@
1
+ // @dexterm/data — social layer barrel (task-1330, lane I-03).
2
+ export * from "./model.js";
3
+ export * from "./firewall.js";
4
+ export * from "./dedupe.js";
5
+ export * from "./rank.js";
6
+ export * from "./kol.js";
7
+ export * from "./venues.js";
8
+ export * from "./rss.js";
9
+ export * from "./chain.js";
10
+ export * from "./summary.js";
11
+ export * from "./feed.js";