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,269 @@
1
+ // @dexterm/data — KOL lists (task-1330; design §5.4): the user's own lists (lane I-02 will fill
2
+ // `own-x-list` from `list.read`), manual handles, and signed packs. A pack carries HANDLES (and optional
3
+ // wallet addresses with evidence links), never posts; it grants membership, never credibility. Lists
4
+ // persist in the shared SQLite cache (one --db controls everything) under the `social:kol` namespace.
5
+ //
6
+ // Pack format (task-1288 pack conventions, narrowed to one content kind):
7
+ // <dir>/pack.json { name, version, publisher, kind: "kol-list", handles: [{ handle, wallet?, evidence? }] }
8
+ // <dir>/pack.lock { files: { "pack.json": "<sha256 hex>" }, hash: "<sha256 hex of the files listing>" }
9
+ // <dir>/pack.lock.sig optional `ssh-keygen -Y sign -n dexterm-pack` signature over pack.lock
10
+ // Trust: `signed.verified` is true only when the signature verifies against the user's pinned
11
+ // ~/.dexterm/allowed_signers; a tampered hash is refused outright (never imported as "unsigned").
12
+ import { createHash } from 'node:crypto';
13
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { spawnSync } from 'node:child_process';
16
+ import { systemClock } from "../clock.js";
17
+ export const KOL_NS = 'social:kol';
18
+ const FOREVER_MS = 10 * 365 * 86_400_000;
19
+ export const PACK_MAX_HANDLES = 500;
20
+ const HANDLE_RE = /^[A-Za-z0-9_]{1,15}$/;
21
+ const BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
22
+ const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
23
+ export function normalizeHandle(h) {
24
+ return h.trim().replace(/^@/, '').toLowerCase();
25
+ }
26
+ export function isValidHandle(h) {
27
+ return HANDLE_RE.test(normalizeHandle(h));
28
+ }
29
+ export function sha256Hex(data) {
30
+ return createHash('sha256').update(data).digest('hex');
31
+ }
32
+ /** The lock's `hash` is the sha256 of the sorted `name=sha\n` listing. */
33
+ export function packHash(files) {
34
+ const listing = Object.keys(files).sort().map((k) => `${k}=${files[k]}\n`).join('');
35
+ return sha256Hex(listing);
36
+ }
37
+ /** `ssh-keygen -Y verify` against a pinned allowed_signers file. False on any failure, never throws. */
38
+ export function sshKeygenVerifier(allowedSigners) {
39
+ return (lockBytes, signature, publisher) => {
40
+ try {
41
+ if (!existsSync(allowedSigners))
42
+ return false;
43
+ const sigPath = join(process.env.TMPDIR ?? '/tmp', `dexterm-pack-${process.pid}-${Date.now()}.sig`);
44
+ // ssh-keygen wants the signature in a file; the message goes on stdin.
45
+ writeFileSync(sigPath, signature);
46
+ try {
47
+ const r = spawnSync('ssh-keygen', ['-Y', 'verify', '-f', allowedSigners, '-I', publisher, '-n', 'dexterm-pack', '-s', sigPath], { input: lockBytes, timeout: 5000 });
48
+ return r.status === 0;
49
+ }
50
+ finally {
51
+ try {
52
+ unlinkSync(sigPath);
53
+ }
54
+ catch { /* ignore */ }
55
+ }
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ };
61
+ }
62
+ export class PackError extends Error {
63
+ constructor(message) { super(message); this.name = 'PackError'; }
64
+ }
65
+ /** Parse + validate a pack directory. Throws PackError on any shape/hash problem; never imports a tampered pack. */
66
+ export function readPack(dir, verify) {
67
+ const manifestPath = join(dir, 'pack.json');
68
+ const lockPath = join(dir, 'pack.lock');
69
+ if (!existsSync(manifestPath))
70
+ throw new PackError('pack.json missing');
71
+ if (!existsSync(lockPath))
72
+ throw new PackError('pack.lock missing (unlocked packs are refused)');
73
+ const manifestBytes = readFileSync(manifestPath);
74
+ const lockBytes = readFileSync(lockPath);
75
+ let lock;
76
+ try {
77
+ lock = JSON.parse(lockBytes.toString('utf8'));
78
+ }
79
+ catch {
80
+ throw new PackError('pack.lock is not JSON');
81
+ }
82
+ if (!lock || typeof lock !== 'object' || !lock.files || typeof lock.files !== 'object' || typeof lock.hash !== 'string')
83
+ throw new PackError('pack.lock shape');
84
+ for (const k of Object.keys(lock.files))
85
+ if (PROTO_KEYS.has(k) || k.includes('/') || k.includes('\\') || k.startsWith('.'))
86
+ throw new PackError(`pack.lock lists a forbidden path: ${k}`);
87
+ const want = lock.files['pack.json'];
88
+ if (!want || sha256Hex(manifestBytes) !== want.toLowerCase())
89
+ throw new PackError('pack.json does not match pack.lock (tampered or stale)');
90
+ if (packHash(lock.files) !== lock.hash.toLowerCase())
91
+ throw new PackError('pack.lock hash does not match its file listing');
92
+ let manifest;
93
+ try {
94
+ manifest = JSON.parse(manifestBytes.toString('utf8'));
95
+ }
96
+ catch {
97
+ throw new PackError('pack.json is not JSON');
98
+ }
99
+ if (!manifest || typeof manifest !== 'object')
100
+ throw new PackError('pack.json shape');
101
+ if (manifest.kind !== 'kol-list')
102
+ throw new PackError(`pack kind ${JSON.stringify(manifest.kind)} is not kol-list`);
103
+ for (const k of ['name', 'version', 'publisher'])
104
+ if (typeof manifest[k] !== 'string' || !manifest[k].trim() || manifest[k].length > 80)
105
+ throw new PackError(`pack.json ${k}`);
106
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(manifest.name))
107
+ throw new PackError('pack name');
108
+ if (!Array.isArray(manifest.handles))
109
+ throw new PackError('pack.json handles must be an array');
110
+ if (manifest.handles.length === 0)
111
+ throw new PackError('pack has no handles');
112
+ if (manifest.handles.length > PACK_MAX_HANDLES)
113
+ throw new PackError(`pack has ${manifest.handles.length} handles (max ${PACK_MAX_HANDLES})`);
114
+ for (const h of manifest.handles) {
115
+ if (!h || typeof h !== 'object' || typeof h.handle !== 'string' || !isValidHandle(h.handle))
116
+ throw new PackError(`bad handle entry ${JSON.stringify(h?.handle ?? h).slice(0, 40)}`);
117
+ if (h.wallet !== undefined && (typeof h.wallet !== 'string' || !BASE58_RE.test(h.wallet)))
118
+ throw new PackError(`bad wallet for @${h.handle}`);
119
+ if (h.evidence !== undefined && (typeof h.evidence !== 'string' || !/^https?:\/\//.test(h.evidence) || h.evidence.length > 300))
120
+ throw new PackError(`bad evidence link for @${h.handle}`);
121
+ }
122
+ const sigPath = join(dir, 'pack.lock.sig');
123
+ const signed = existsSync(sigPath);
124
+ const verified = signed && !!verify && verify(lockBytes, readFileSync(sigPath, 'utf8'), manifest.publisher);
125
+ return { manifest, lock, signed, verified };
126
+ }
127
+ export class KolStore {
128
+ cache;
129
+ clock;
130
+ verify;
131
+ lists;
132
+ mutedSet;
133
+ constructor(opts) {
134
+ this.cache = opts.cache;
135
+ this.clock = opts.clock ?? systemClock;
136
+ this.verify = opts.verify ?? (opts.allowedSigners ? sshKeygenVerifier(opts.allowedSigners) : undefined);
137
+ this.lists = this.cache.get(KOL_NS, 'lists')?.value ?? [];
138
+ this.mutedSet = new Set(this.cache.get(KOL_NS, 'muted')?.value ?? []);
139
+ }
140
+ save() {
141
+ this.cache.set(KOL_NS, 'lists', this.lists, { ttlMs: FOREVER_MS });
142
+ this.cache.set(KOL_NS, 'muted', [...this.mutedSet], { ttlMs: FOREVER_MS });
143
+ }
144
+ all() { return this.lists.map((l) => ({ ...l, handles: [...l.handles], wallets: [...l.wallets] })); }
145
+ get(id) { return this.lists.find((l) => l.id === id); }
146
+ muted() { return [...this.mutedSet].sort(); }
147
+ isMuted(handle) { return this.mutedSet.has(normalizeHandle(handle)); }
148
+ /** Create a manual list (or return the existing one by name). */
149
+ create(name, source = 'manual') {
150
+ const id = `${source}:${name.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').slice(0, 64)}`;
151
+ const existing = this.lists.find((l) => l.id === id);
152
+ if (existing)
153
+ return existing;
154
+ const list = { id, name: name.trim().slice(0, 80), source, handles: [], wallets: [], signed: null, since: this.clock.now(), enabled: true, tier: source === 'pack' ? 'kol-b' : 'kol-a' };
155
+ this.lists.push(list);
156
+ this.save();
157
+ return list;
158
+ }
159
+ remove(id) {
160
+ const n = this.lists.length;
161
+ this.lists = this.lists.filter((l) => l.id !== id);
162
+ if (this.lists.length !== n)
163
+ this.save();
164
+ return this.lists.length !== n;
165
+ }
166
+ setEnabled(id, enabled) {
167
+ const l = this.get(id);
168
+ if (!l)
169
+ return false;
170
+ l.enabled = enabled;
171
+ this.save();
172
+ return true;
173
+ }
174
+ addHandle(id, handle) {
175
+ const l = this.get(id);
176
+ if (!l)
177
+ return { ok: false, reason: 'no such list' };
178
+ if (l.source === 'pack')
179
+ return { ok: false, reason: 'packs are read-only (edit the pack and re-import)' };
180
+ const h = normalizeHandle(handle);
181
+ if (!isValidHandle(h))
182
+ return { ok: false, reason: 'handle must be 1-15 letters, digits or _' };
183
+ if (l.handles.includes(h))
184
+ return { ok: false, reason: `@${h} already in ${l.name}` };
185
+ if (l.handles.length >= PACK_MAX_HANDLES)
186
+ return { ok: false, reason: `list is full (${PACK_MAX_HANDLES})` };
187
+ l.handles.push(h);
188
+ this.save();
189
+ return { ok: true };
190
+ }
191
+ removeHandle(id, handle) {
192
+ const l = this.get(id);
193
+ if (!l)
194
+ return false;
195
+ const h = normalizeHandle(handle);
196
+ const n = l.handles.length;
197
+ l.handles = l.handles.filter((x) => x !== h);
198
+ if (l.handles.length !== n)
199
+ this.save();
200
+ return l.handles.length !== n;
201
+ }
202
+ mute(handle) { const h = normalizeHandle(handle); if (isValidHandle(h)) {
203
+ this.mutedSet.add(h);
204
+ this.save();
205
+ } }
206
+ unmute(handle) { this.mutedSet.delete(normalizeHandle(handle)); this.save(); }
207
+ /** Seed a fully-formed list (fixtures/demos and tests only; the app path is create()/importPack()). */
208
+ seed(list) {
209
+ this.lists = this.lists.filter((l) => l.id !== list.id);
210
+ this.lists.push({ ...list, handles: list.handles.map(normalizeHandle), wallets: [...list.wallets] });
211
+ this.save();
212
+ }
213
+ /** Import a pack directory as a `pack` list (replacing a previous import of the same pack name). */
214
+ importPack(dir) {
215
+ const { manifest, lock, signed, verified } = readPack(dir, this.verify);
216
+ const id = `pack:${manifest.name.toLowerCase()}`;
217
+ const handles = [...new Set(manifest.handles.map((h) => normalizeHandle(h.handle)))];
218
+ const wallets = [...new Set(manifest.handles.map((h) => h.wallet).filter((w) => !!w))];
219
+ const list = {
220
+ id, name: `${manifest.name}@${manifest.version}`, source: 'pack', handles, wallets,
221
+ signed: { publisher: manifest.publisher, hash: lock.hash.toLowerCase(), verified },
222
+ since: this.clock.now(), enabled: true,
223
+ // A verified signature from a pinned publisher counts like the user's own list; unsigned packs stay kol-b.
224
+ tier: signed && verified ? 'kol-a' : 'kol-b',
225
+ };
226
+ this.lists = this.lists.filter((l) => l.id !== id);
227
+ this.lists.push(list);
228
+ this.save();
229
+ return list;
230
+ }
231
+ /** `kol-a` / `kol-b` for a handle on an enabled, unmuted list (own lists and verified packs win), else null. */
232
+ tierOf(handle) {
233
+ const h = normalizeHandle(handle);
234
+ if (this.mutedSet.has(h))
235
+ return null;
236
+ let best = null;
237
+ for (const l of this.lists) {
238
+ if (!l.enabled || !l.handles.includes(h))
239
+ continue;
240
+ if (l.tier === 'kol-a')
241
+ return 'kol-a';
242
+ best = 'kol-b';
243
+ }
244
+ return best;
245
+ }
246
+ /** Every handle that counts for the trending feed (enabled lists, muted removed), lower-cased. */
247
+ kolHandles() {
248
+ const out = new Set();
249
+ for (const l of this.lists)
250
+ if (l.enabled)
251
+ for (const h of l.handles)
252
+ if (!this.mutedSet.has(h))
253
+ out.add(h);
254
+ return out;
255
+ }
256
+ /** `own(3) · pack "sol-alpha" (41, signed)` style footer summary. */
257
+ summary() {
258
+ const own = this.lists.filter((l) => l.source !== 'pack');
259
+ const packs = this.lists.filter((l) => l.source === 'pack');
260
+ const parts = [];
261
+ if (own.length)
262
+ parts.push(`own(${own.reduce((s, l) => s + l.handles.length, 0)})`);
263
+ for (const p of packs)
264
+ parts.push(`pack "${p.name}" (${p.handles.length}, ${p.signed?.verified ? 'signed' : 'unsigned'}${p.enabled ? '' : ', off'})`);
265
+ if (this.mutedSet.size)
266
+ parts.push(`muted ${this.mutedSet.size}`);
267
+ return parts.length ? parts.join(' · ') : 'no KOL lists';
268
+ }
269
+ }
@@ -0,0 +1,11 @@
1
+ // @dexterm/data — social / news / trending model (task-1330, lane I-03; INTEGRATIONS-DESIGN §5.1).
2
+ //
3
+ // Conventions follow ../model.ts: epoch-ms timestamps, `null` = "source did not say", plain interfaces
4
+ // only. Every post's `text` has ALREADY been through the ingest firewall (./firewall.ts) when it is a
5
+ // SocialPost; the pre-firewall shape is `RawPost`. Nothing here is ever posted anywhere, and nothing is
6
+ // fetched from X: posts arrive from keyless sources (news RSS, on-chain rules) today and from the user's
7
+ // own X login (lane I-02) later — the model is the same.
8
+ /** Display truncation (design §5.6): 280 shown, 512 stored, 160 for bios. */
9
+ export const POST_DISPLAY_MAX = 280;
10
+ export const POST_STORED_MAX = 512;
11
+ export const BIO_MAX = 160;
@@ -0,0 +1,184 @@
1
+ // @dexterm/data — credibility, velocity, trend rows and narratives (task-1330; design §5.3 / §5.4).
2
+ //
3
+ // Rank = velocity × credibility, computed locally and deterministically:
4
+ // velocity = spam-excluded listed posts in the last hour ÷ the mean of the previous 23 hourly
5
+ // buckets (≥ 2 non-empty buckets required, else the venue's own volume velocity, else "new")
6
+ // credibility = mean author credibility over the window + KOL count + on-chain confirmation
7
+ // − boost penalty − spam share; 0..100
8
+ // score = velocity × credibility/100 + 0.1 per independent venue source that lists the mint
9
+ // Author credibility comes only from the user's own observations (early entry, confirm rate, follower
10
+ // quality when known) on top of a tier baseline; a KOL list grants membership, never credibility.
11
+ import { countsForVelocity } from "./dedupe.js";
12
+ export const HOUR_MS = 3_600_000;
13
+ export const WINDOW_HOURS = 24;
14
+ export const MIN_VELOCITY_SAMPLES = 2;
15
+ export const BOOST_PENALTY = 20;
16
+ export const CHAIN_CONFIRM_BONUS = 15;
17
+ export const KOL_BONUS_PER = 2;
18
+ export const KOL_BONUS_CAP = 10;
19
+ export const SPAM_SHARE_PENALTY = 20;
20
+ export const VENUE_CONFIRM_BONUS = 0.1;
21
+ /** Baselines by tier (design §5.5 rule 6 + §5.4): news domains are official sources; spam is spam. */
22
+ export const TIER_BASE = { src: 90, 'kol-a': 60, 'kol-b': 50, own: 55, none: 30, spam: 4 };
23
+ const clamp01 = (n) => (typeof n === 'number' && Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : null);
24
+ export function credibilityOf(author, obs, now) {
25
+ const earlyEntry = clamp01(obs?.earlyEntry);
26
+ const followerQuality = clamp01(obs?.followerQuality);
27
+ const onChainConfirmRate = clamp01(obs?.onChainConfirmRate);
28
+ let score = TIER_BASE[author.tier];
29
+ if (earlyEntry !== null)
30
+ score += 25 * earlyEntry;
31
+ if (onChainConfirmRate !== null)
32
+ score += 15 * onChainConfirmRate;
33
+ if (followerQuality !== null)
34
+ score += 10 * followerQuality;
35
+ const spamPenalty = author.tier === 'spam' ? Math.max(0, score - TIER_BASE.spam) : 0;
36
+ score -= spamPenalty;
37
+ score = Math.max(0, Math.min(100, Math.round(score)));
38
+ return { handle: author.handle, score, inputs: { earlyEntry, followerQuality, onChainConfirmRate, spamPenalty, boostPenalty: 0 }, computedAt: now };
39
+ }
40
+ /** Posts per hourly bucket, index 0 = the current hour, 1 = the previous, … up to `hours`. */
41
+ export function hourlyBuckets(posts, now, hours = WINDOW_HOURS) {
42
+ const out = new Array(hours).fill(0);
43
+ for (const p of posts) {
44
+ if (!countsForVelocity(p))
45
+ continue;
46
+ const age = now - p.t;
47
+ if (age < 0)
48
+ continue;
49
+ const i = Math.floor(age / HOUR_MS);
50
+ if (i < hours)
51
+ out[i] += 1;
52
+ }
53
+ return out;
54
+ }
55
+ export function socialVelocity(buckets) {
56
+ const postsPerHour = buckets[0] ?? 0;
57
+ const prior = buckets.slice(1);
58
+ const samples = prior.filter((n) => n > 0).length;
59
+ if (samples < MIN_VELOCITY_SAMPLES)
60
+ return { postsPerHour, velocity: null, samples };
61
+ const mean = prior.reduce((s, n) => s + n, 0) / prior.length;
62
+ return { postsPerHour, velocity: mean > 0 ? postsPerHour / mean : null, samples };
63
+ }
64
+ export function buildTrendRow(input) {
65
+ const { posts, venue, now } = input;
66
+ const listed = posts.filter((p) => !p.hidden);
67
+ const counted = posts.filter(countsForVelocity);
68
+ const lastHour = counted.filter((p) => now - p.t < HOUR_MS);
69
+ const buckets = hourlyBuckets(posts, now);
70
+ const sv = socialVelocity(buckets);
71
+ let velocity = sv.velocity;
72
+ let basis = 'social';
73
+ if (velocity === null) {
74
+ if (venue && venue.volumeVelocity !== null) {
75
+ velocity = venue.volumeVelocity;
76
+ basis = 'venue';
77
+ }
78
+ else
79
+ basis = 'new';
80
+ }
81
+ const windowPosts = lastHour.length ? lastHour : counted;
82
+ const kols = new Set();
83
+ let credSum = 0;
84
+ for (const p of windowPosts) {
85
+ credSum += input.credibility(p.author);
86
+ if (input.kolHandles.has(p.author.handle.replace(/^@/, '').toLowerCase()))
87
+ kols.add(p.author.handle.toLowerCase());
88
+ }
89
+ const spamCount = listed.filter((p) => p.flags.includes('spam')).reduce((s, p) => s + (p.collapsed?.posts ?? p.dupCount), 0);
90
+ const squatCount = listed.filter((p) => p.flags.includes('squat') || p.flags.includes('unverified-cashtag')).length;
91
+ const total = counted.length + spamCount;
92
+ const spamShare = total > 0 ? spamCount / total : 0;
93
+ let cred = windowPosts.length ? credSum / windowPosts.length : venue ? 20 + (venue.organicScore ?? 0) * 0.2 : 0;
94
+ cred += Math.min(kols.size, KOL_BONUS_CAP) * KOL_BONUS_PER;
95
+ if (input.chainConfirmed)
96
+ cred += CHAIN_CONFIRM_BONUS;
97
+ if (venue?.boosted)
98
+ cred -= BOOST_PENALTY;
99
+ cred -= spamShare * SPAM_SHARE_PENALTY;
100
+ cred = Math.max(0, Math.min(100, Math.round(cred)));
101
+ const venueSources = venue ? venue.sources.filter((s) => s !== 'boost').length : 0;
102
+ const vel = velocity ?? (sv.postsPerHour > 0 ? 1 : 0.5);
103
+ const score = vel * (cred / 100) + venueSources * VENUE_CONFIRM_BONUS;
104
+ const latest = [...counted].sort((a, b) => b.t - a.t).find((p) => input.credibility(p.author) >= 40) ?? null;
105
+ const sources = new Set();
106
+ for (const p of listed)
107
+ sources.add(p.platform === 'x' ? 'X' : p.platform === 'telegram' ? 'TG' : p.platform === 'discord' ? 'DC' : p.platform === 'news' ? 'news' : 'chain');
108
+ for (const s of venue?.sources ?? [])
109
+ sources.add(s);
110
+ const updatedAt = Math.max(venue?.fetchedAt ?? 0, ...listed.map((p) => p.provenance.fetchedAt));
111
+ return {
112
+ mint: input.mint, symbol: input.symbol, narrative: input.narrative ?? null,
113
+ velocity, velocityBasis: basis, postsPerHour: sv.postsPerHour, kolCount: kols.size, credibility: cred, score: Math.round(score * 1000) / 1000,
114
+ sources: [...sources], priceChange1h: venue?.priceChange1h ?? null, liq: venue?.liquidityUsd ?? null,
115
+ latestCrediblePostId: latest?.id ?? null, latestCrediblePost: latest,
116
+ hiddenSpam: spamCount, hiddenSquats: squatCount, updatedAt: updatedAt || now, venue,
117
+ };
118
+ }
119
+ /** Highest score first; ties by posts/h then symbol so the order is total. */
120
+ export function rankRows(rows) {
121
+ return [...rows].sort((a, b) => b.score - a.score || b.postsPerHour - a.postsPerHour || a.symbol.localeCompare(b.symbol));
122
+ }
123
+ /**
124
+ * Narratives = clusters of tokens linked by shared KOL threads (one KOL handle posting about both inside
125
+ * the window) or co-mention (one post naming both mints). Union-find over the rows' mints. Labels come
126
+ * from `labels` (the analyst summary or the user), never from a single post; unlabelled clusters keep
127
+ * `label: null` and the UI lists the symbols.
128
+ */
129
+ export function buildNarratives(rows, posts, kolHandles, labels = new Map(), newsCountFor = () => 0) {
130
+ const mints = rows.map((r) => r.mint);
131
+ const idx = new Map(mints.map((m, i) => [m, i]));
132
+ const parent = mints.map((_, i) => i);
133
+ const find = (i) => (parent[i] === i ? i : (parent[i] = find(parent[i])));
134
+ const union = (a, b) => { const ra = find(a), rb = find(b); if (ra !== rb)
135
+ parent[rb] = ra; };
136
+ const byKol = new Map();
137
+ const threads = new Map();
138
+ for (const p of posts) {
139
+ if (!countsForVelocity(p))
140
+ continue;
141
+ const ids = p.mints.map((m) => idx.get(m)).filter((i) => i !== undefined);
142
+ for (let i = 1; i < ids.length; i++)
143
+ union(ids[0], ids[i]); // co-mention
144
+ const h = p.author.handle.replace(/^@/, '').toLowerCase();
145
+ if (kolHandles.has(h)) {
146
+ const s = byKol.get(h) ?? new Set();
147
+ for (const i of ids)
148
+ s.add(i);
149
+ byKol.set(h, s);
150
+ }
151
+ }
152
+ for (const [h, set] of byKol) {
153
+ const arr = [...set];
154
+ for (let i = 1; i < arr.length; i++)
155
+ union(arr[0], arr[i]);
156
+ for (const i of arr) {
157
+ const t = threads.get(i) ?? new Set();
158
+ t.add(h);
159
+ threads.set(i, t);
160
+ }
161
+ }
162
+ const groups = new Map();
163
+ mints.forEach((_, i) => { const r = find(i); const g = groups.get(r) ?? []; g.push(i); groups.set(r, g); });
164
+ const out = [];
165
+ for (const members of groups.values()) {
166
+ if (members.length < 2)
167
+ continue; // a narrative needs at least two tokens
168
+ const rs = members.map((i) => rows[i]).sort((a, b) => b.score - a.score);
169
+ const id = rs.map((r) => r.mint.slice(0, 6)).sort().join('+');
170
+ const kolSet = new Set();
171
+ for (const i of members)
172
+ for (const h of threads.get(i) ?? [])
173
+ kolSet.add(h);
174
+ const vels = rs.map((r) => r.velocity).filter((v) => v !== null);
175
+ const d24 = rs.map((r) => r.venue?.priceChange1h).filter((v) => typeof v === 'number');
176
+ out.push({
177
+ id, label: labels.get(id) ?? null, tokens: rs.map((r) => r.mint), symbols: rs.map((r) => r.symbol),
178
+ velocity: vels.length ? vels.reduce((s, v) => s + v, 0) / vels.length : null,
179
+ delta24h: d24.length ? d24.reduce((s, v) => s + v, 0) / d24.length : null,
180
+ kolThreads: kolSet.size, newsCount: members.reduce((s, i) => s + newsCountFor(rows[i].mint), 0),
181
+ });
182
+ }
183
+ return out.sort((a, b) => (b.velocity ?? 0) - (a.velocity ?? 0) || b.tokens.length - a.tokens.length);
184
+ }
@@ -0,0 +1,113 @@
1
+ // @dexterm/data — publishers' RSS as the NEWS column (task-1330; design §4 "context, not signal").
2
+ // Four official feeds, polled every 10 minutes through the shared HttpClient (`news-rss` source,
3
+ // polite bucket), parsed with a small dependency-free RSS 2.0 / Atom reader. Headlines are untrusted
4
+ // text and go through the ingest firewall like any post; links are kept as `url` (opened by the OS
5
+ // browser on ⏎, never fetched by dexterm).
6
+ export const RSS_FEEDS = [
7
+ { id: 'coindesk', url: 'https://www.coindesk.com/arc/outboundfeeds/rss/', domain: 'coindesk.com', label: 'coindesk' },
8
+ { id: 'cointelegraph', url: 'https://cointelegraph.com/rss', domain: 'cointelegraph.com', label: 'cointelegr' },
9
+ { id: 'theblock', url: 'https://www.theblock.co/rss.xml', domain: 'theblock.co', label: 'theblock' },
10
+ { id: 'decrypt', url: 'https://decrypt.co/feed', domain: 'decrypt.co', label: 'decrypt' },
11
+ ];
12
+ export const NEWS_REFRESH_MS = 10 * 60_000;
13
+ const META = { source: 'news-rss', bucket: 'news-rss' };
14
+ const ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' };
15
+ export function decodeEntities(s) {
16
+ return s.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (m, e) => {
17
+ if (e[0] === '#') {
18
+ const cp = e[1] === 'x' || e[1] === 'X' ? parseInt(e.slice(2), 16) : parseInt(e.slice(1), 10);
19
+ return Number.isFinite(cp) && cp > 0 && cp <= 0x10ffff ? String.fromCodePoint(cp) : m;
20
+ }
21
+ return ENTITIES[e.toLowerCase()] ?? m;
22
+ });
23
+ }
24
+ function tag(block, name) {
25
+ const m = new RegExp(`<${name}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${name}>`, 'i').exec(block);
26
+ if (!m)
27
+ return null;
28
+ let v = m[1].trim();
29
+ const cd = /^<!\[CDATA\[([\s\S]*?)\]\]>$/.exec(v);
30
+ if (cd)
31
+ v = cd[1];
32
+ return decodeEntities(v.replace(/<[^>]+>/g, '')).trim();
33
+ }
34
+ /** RSS 2.0 `<item>` and Atom `<entry>` → RssItem[], newest first; items without a title are dropped. */
35
+ export function parseRss(xml) {
36
+ const out = [];
37
+ const blocks = [...xml.matchAll(/<(item|entry)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/gi)].map((m) => m[2]);
38
+ for (const b of blocks) {
39
+ const title = tag(b, 'title');
40
+ if (!title)
41
+ continue;
42
+ let link = tag(b, 'link');
43
+ if (!link) {
44
+ const atom = /<link[^>]*href="([^"]+)"/i.exec(b);
45
+ link = atom ? decodeEntities(atom[1]) : null;
46
+ }
47
+ if (link && !/^https?:\/\//i.test(link))
48
+ link = null;
49
+ const dateText = tag(b, 'pubDate') ?? tag(b, 'dc:date') ?? tag(b, 'updated') ?? tag(b, 'published');
50
+ const t = dateText ? Date.parse(dateText) : NaN;
51
+ const guid = tag(b, 'guid') ?? tag(b, 'id') ?? link ?? title;
52
+ out.push({ title, link, publishedAt: Number.isFinite(t) ? t : null, guid });
53
+ }
54
+ return out.sort((a, b) => (b.publishedAt ?? 0) - (a.publishedAt ?? 0));
55
+ }
56
+ const TAG_RULES = [
57
+ ['security', /\b(hack|hacked|exploit|drain|drains|drained|breach|phishing|stolen|theft|scam|rug|malware|vulnerab)/i],
58
+ ['regulation', /\b(sec\b|cftc|regulat|lawsuit|court|judge|congress|senate|bill\b|ban\b|bans\b|barred|fine\b|fines\b|seize|settlement|attorney general|treasury)/i],
59
+ ['solana', /\b(solana|sol\b|pump\.?fun|jupiter|raydium|meteora|memecoin|meme coin|phantom)/i],
60
+ ['macro', /\b(fed\b|rates?\b|inflation|cpi|etf|bitcoin|btc|ethereum|eth\b|market|stocks?|treasur|dollar|tariff)/i],
61
+ ];
62
+ /** Keyword bracket for the NEWS panel (`[macro]`, `[security]`, …): rules, not a judgement. */
63
+ export function newsTag(title) {
64
+ for (const [t, re] of TAG_RULES)
65
+ if (re.test(title))
66
+ return t;
67
+ return 'other';
68
+ }
69
+ export function rssItemToRaw(item, feed, fetchedAt) {
70
+ return {
71
+ id: `news:${feed.id}:${item.guid}`.slice(0, 200),
72
+ t: item.publishedAt ?? fetchedAt,
73
+ platform: 'news',
74
+ author: { id: feed.domain, handle: feed.domain, tier: 'src', createdAt: null },
75
+ text: item.title,
76
+ url: item.link,
77
+ lang: null,
78
+ metrics: {},
79
+ mints: [],
80
+ source: 'news-rss',
81
+ fetchedAt,
82
+ };
83
+ }
84
+ export class NewsRss {
85
+ http;
86
+ feeds;
87
+ constructor(http, feeds = RSS_FEEDS) {
88
+ this.http = http;
89
+ this.feeds = feeds;
90
+ }
91
+ async fetch(feed) {
92
+ const r = await this.http.getText(feed.url, META);
93
+ return { items: parseRss(r.data), fetchedAt: r.fetchedAt };
94
+ }
95
+ /** All feeds, sequential (the bucket paces); each failure is a caption, never a throw. */
96
+ async fetchAll() {
97
+ const posts = [];
98
+ const errors = {};
99
+ let fetchedAt = null;
100
+ for (const f of this.feeds) {
101
+ try {
102
+ const r = await this.fetch(f);
103
+ fetchedAt = Math.max(fetchedAt ?? 0, r.fetchedAt);
104
+ for (const it of r.items)
105
+ posts.push(rssItemToRaw(it, f, r.fetchedAt));
106
+ }
107
+ catch (e) {
108
+ errors[f.id] = (e instanceof Error ? e.message : String(e)).slice(0, 80);
109
+ }
110
+ }
111
+ return { posts, errors, fetchedAt };
112
+ }
113
+ }