dexterm-cli 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/LICENSE +661 -0
  2. package/dist/packages/agents/src/audit.js +122 -0
  3. package/dist/packages/agents/src/bus.js +379 -0
  4. package/dist/packages/agents/src/executor.js +177 -0
  5. package/dist/packages/agents/src/gate.js +176 -0
  6. package/dist/packages/agents/src/identity.js +179 -0
  7. package/dist/packages/agents/src/index.js +15 -0
  8. package/dist/packages/agents/src/proposals.js +153 -0
  9. package/dist/packages/agents/src/schema.js +87 -0
  10. package/dist/packages/agents/src/sdk.js +140 -0
  11. package/dist/packages/agents/src/tiers.js +114 -0
  12. package/dist/packages/agents/src/tools.js +69 -0
  13. package/dist/packages/agents/src/ws.js +158 -0
  14. package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
  15. package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
  17. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +67 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -0
  19. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
  20. package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
  21. package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
  22. package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
  23. package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
  24. package/dist/packages/cli/bin/dexterm.mjs +11 -0
  25. package/dist/packages/cli/src/agent-cmd.js +303 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/analyst/backend.js +526 -0
  28. package/dist/packages/cli/src/analyst/results.js +38 -0
  29. package/dist/packages/cli/src/analyst/session.js +319 -0
  30. package/dist/packages/cli/src/audit.js +51 -0
  31. package/dist/packages/cli/src/backtest-cmd.js +149 -0
  32. package/dist/packages/cli/src/boot-state.js +62 -0
  33. package/dist/packages/cli/src/custom.js +261 -0
  34. package/dist/packages/cli/src/display.js +191 -0
  35. package/dist/packages/cli/src/doctor.js +361 -0
  36. package/dist/packages/cli/src/feed.js +634 -0
  37. package/dist/packages/cli/src/fixtures.js +275 -0
  38. package/dist/packages/cli/src/follow-cmd.js +221 -0
  39. package/dist/packages/cli/src/images-config.js +59 -0
  40. package/dist/packages/cli/src/index.js +31 -0
  41. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  42. package/dist/packages/cli/src/larp-record.js +21 -0
  43. package/dist/packages/cli/src/live/copy.js +80 -0
  44. package/dist/packages/cli/src/live/flow.js +233 -0
  45. package/dist/packages/cli/src/live/index.js +509 -0
  46. package/dist/packages/cli/src/live/intel.js +227 -0
  47. package/dist/packages/cli/src/live/newpairs.js +334 -0
  48. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  49. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  50. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  51. package/dist/packages/cli/src/live/token.js +585 -0
  52. package/dist/packages/cli/src/live/trade.js +929 -0
  53. package/dist/packages/cli/src/live/watchlist.js +178 -0
  54. package/dist/packages/cli/src/main.js +5583 -0
  55. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  56. package/dist/packages/cli/src/pack-cmd.js +323 -0
  57. package/dist/packages/cli/src/plan-cmd.js +278 -0
  58. package/dist/packages/cli/src/popup.js +810 -0
  59. package/dist/packages/cli/src/rules-cmd.js +131 -0
  60. package/dist/packages/cli/src/rules.js +339 -0
  61. package/dist/packages/cli/src/scan/command.js +57 -0
  62. package/dist/packages/cli/src/scan/engine.js +419 -0
  63. package/dist/packages/cli/src/scan/observe.js +64 -0
  64. package/dist/packages/cli/src/scan/ui.js +98 -0
  65. package/dist/packages/cli/src/screener/filter.js +241 -0
  66. package/dist/packages/cli/src/screener/presets.js +80 -0
  67. package/dist/packages/cli/src/screener/rows.js +146 -0
  68. package/dist/packages/cli/src/screener/rug.js +50 -0
  69. package/dist/packages/cli/src/screener/sort.js +52 -0
  70. package/dist/packages/cli/src/screener/view.js +183 -0
  71. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  72. package/dist/packages/cli/src/screens/analyst.js +326 -0
  73. package/dist/packages/cli/src/screens/boot.js +74 -0
  74. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  75. package/dist/packages/cli/src/screens/coin.js +33 -0
  76. package/dist/packages/cli/src/screens/follows.js +201 -0
  77. package/dist/packages/cli/src/screens/kol.js +90 -0
  78. package/dist/packages/cli/src/screens/narrative.js +90 -0
  79. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  80. package/dist/packages/cli/src/screens/overlay.js +257 -0
  81. package/dist/packages/cli/src/screens/plans.js +126 -0
  82. package/dist/packages/cli/src/screens/positions.js +176 -0
  83. package/dist/packages/cli/src/screens/rules.js +170 -0
  84. package/dist/packages/cli/src/screens/screener.js +459 -0
  85. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  86. package/dist/packages/cli/src/screens/ticket.js +342 -0
  87. package/dist/packages/cli/src/screens/token.js +312 -0
  88. package/dist/packages/cli/src/screens/trending.js +181 -0
  89. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  90. package/dist/packages/cli/src/social/demo.js +96 -0
  91. package/dist/packages/cli/src/social/view.js +250 -0
  92. package/dist/packages/cli/src/state.js +463 -0
  93. package/dist/packages/cli/src/theme-cmd.js +126 -0
  94. package/dist/packages/cli/src/trade.js +319 -0
  95. package/dist/packages/cli/src/voice-hud.js +42 -0
  96. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  97. package/dist/packages/cli/src/wallet-command.js +31 -0
  98. package/dist/packages/cli/src/widgets.js +208 -0
  99. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  100. package/dist/packages/cli/src/x-cmd.js +154 -0
  101. package/dist/packages/custom/src/config.js +213 -0
  102. package/dist/packages/custom/src/index.js +10 -0
  103. package/dist/packages/custom/src/keymap.js +107 -0
  104. package/dist/packages/custom/src/layout.js +235 -0
  105. package/dist/packages/custom/src/packs.js +633 -0
  106. package/dist/packages/custom/src/rules.js +704 -0
  107. package/dist/packages/custom/src/schema.js +515 -0
  108. package/dist/packages/custom/src/themes.js +176 -0
  109. package/dist/packages/custom/src/widget-html.js +222 -0
  110. package/dist/packages/custom/src/workspace.js +141 -0
  111. package/dist/packages/data/src/analyst-actions.js +133 -0
  112. package/dist/packages/data/src/budget.js +88 -0
  113. package/dist/packages/data/src/cache.js +142 -0
  114. package/dist/packages/data/src/candles.js +168 -0
  115. package/dist/packages/data/src/clock.js +64 -0
  116. package/dist/packages/data/src/credits.js +137 -0
  117. package/dist/packages/data/src/detail.js +97 -0
  118. package/dist/packages/data/src/fast-lane.js +321 -0
  119. package/dist/packages/data/src/fixtures.js +135 -0
  120. package/dist/packages/data/src/health.js +245 -0
  121. package/dist/packages/data/src/http.js +277 -0
  122. package/dist/packages/data/src/images/cache.js +141 -0
  123. package/dist/packages/data/src/images/decode-worker.js +28 -0
  124. package/dist/packages/data/src/images/decode.js +81 -0
  125. package/dist/packages/data/src/images/fetch.js +162 -0
  126. package/dist/packages/data/src/images/gif.js +214 -0
  127. package/dist/packages/data/src/images/index.js +10 -0
  128. package/dist/packages/data/src/images/jpeg.js +544 -0
  129. package/dist/packages/data/src/images/png.js +310 -0
  130. package/dist/packages/data/src/images/service.js +334 -0
  131. package/dist/packages/data/src/images/types.js +27 -0
  132. package/dist/packages/data/src/index.js +208 -0
  133. package/dist/packages/data/src/intel/helius-history.js +59 -0
  134. package/dist/packages/data/src/intel/holder-class.js +125 -0
  135. package/dist/packages/data/src/intel/rules.js +421 -0
  136. package/dist/packages/data/src/intel/scan.js +170 -0
  137. package/dist/packages/data/src/larp/evidence.js +288 -0
  138. package/dist/packages/data/src/larp/links.js +137 -0
  139. package/dist/packages/data/src/larp/robots.js +90 -0
  140. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  141. package/dist/packages/data/src/limiter.js +341 -0
  142. package/dist/packages/data/src/model.js +12 -0
  143. package/dist/packages/data/src/paper.js +331 -0
  144. package/dist/packages/data/src/rpc-config.js +126 -0
  145. package/dist/packages/data/src/rpc-pool.js +166 -0
  146. package/dist/packages/data/src/social/chain.js +83 -0
  147. package/dist/packages/data/src/social/dedupe.js +205 -0
  148. package/dist/packages/data/src/social/feed.js +462 -0
  149. package/dist/packages/data/src/social/firewall.js +110 -0
  150. package/dist/packages/data/src/social/index.js +11 -0
  151. package/dist/packages/data/src/social/kol.js +269 -0
  152. package/dist/packages/data/src/social/model.js +11 -0
  153. package/dist/packages/data/src/social/rank.js +184 -0
  154. package/dist/packages/data/src/social/rss.js +113 -0
  155. package/dist/packages/data/src/social/summary.js +104 -0
  156. package/dist/packages/data/src/social/venues.js +186 -0
  157. package/dist/packages/data/src/sources/dexscreener.js +170 -0
  158. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  159. package/dist/packages/data/src/sources/goplus.js +161 -0
  160. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  161. package/dist/packages/data/src/sources/jupiter.js +198 -0
  162. package/dist/packages/data/src/sources/pda.js +113 -0
  163. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  164. package/dist/packages/data/src/sources/pumpportal.js +247 -0
  165. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  166. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  167. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  168. package/dist/packages/data/src/store.js +289 -0
  169. package/dist/packages/data/src/trade/bridge.js +464 -0
  170. package/dist/packages/data/src/trade/caps.js +323 -0
  171. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  172. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  173. package/dist/packages/data/src/trade/copy/index.js +9 -0
  174. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  175. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  176. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  177. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  178. package/dist/packages/data/src/trade/copy/types.js +6 -0
  179. package/dist/packages/data/src/trade/executor.js +933 -0
  180. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  181. package/dist/packages/data/src/trade/link-store.js +52 -0
  182. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  183. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  184. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  185. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  186. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  187. package/dist/packages/data/src/trade/plans/index.js +12 -0
  188. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  189. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  190. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  191. package/dist/packages/data/src/trade/plans/service.js +168 -0
  192. package/dist/packages/data/src/trade/plans/types.js +6 -0
  193. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  194. package/dist/packages/data/src/trade/signer/client.js +197 -0
  195. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  196. package/dist/packages/data/src/trade/signer/host.js +97 -0
  197. package/dist/packages/data/src/trade/signer/main.js +121 -0
  198. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  199. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  200. package/dist/packages/data/src/trade/signer/service.js +400 -0
  201. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  202. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  203. package/dist/packages/data/src/trade/swap-build.js +108 -0
  204. package/dist/packages/data/src/trade/token-delta.js +201 -0
  205. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  206. package/dist/packages/data/src/util.js +24 -0
  207. package/dist/packages/data/src/watchlist.js +176 -0
  208. package/dist/packages/data/src/x/api.js +256 -0
  209. package/dist/packages/data/src/x/budget.js +66 -0
  210. package/dist/packages/data/src/x/config.js +49 -0
  211. package/dist/packages/data/src/x/index.js +10 -0
  212. package/dist/packages/data/src/x/mock-server.js +217 -0
  213. package/dist/packages/data/src/x/oauth.js +242 -0
  214. package/dist/packages/data/src/x/pkce.js +21 -0
  215. package/dist/packages/data/src/x/source.js +285 -0
  216. package/dist/packages/data/src/x/token-store.js +134 -0
  217. package/dist/packages/data/src/x/transport.js +71 -0
  218. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  219. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  220. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  221. package/dist/packages/mcp/assets/cases.json +1194 -0
  222. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  223. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  224. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  225. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  226. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  227. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  228. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  229. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  230. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  231. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  232. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  233. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  234. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  235. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  236. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  237. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  238. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  239. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  240. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  241. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  242. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  243. package/dist/packages/mcp/src/dossier.js +78 -0
  244. package/dist/packages/mcp/src/firewall.js +21 -0
  245. package/dist/packages/mcp/src/index.js +22 -0
  246. package/dist/packages/mcp/src/server.js +849 -0
  247. package/dist/packages/mcp/src/user-content.js +434 -0
  248. package/dist/packages/packs/src/backtest.js +183 -0
  249. package/dist/packages/packs/src/digest.js +95 -0
  250. package/dist/packages/packs/src/firewall.js +57 -0
  251. package/dist/packages/packs/src/index.js +19 -0
  252. package/dist/packages/packs/src/larp-eval.js +100 -0
  253. package/dist/packages/packs/src/narrative.js +309 -0
  254. package/dist/packages/packs/src/reason.js +135 -0
  255. package/dist/packages/packs/src/rug-model.js +137 -0
  256. package/dist/packages/packs/src/schema.js +54 -0
  257. package/dist/packages/packs/src/token-dossier.js +174 -0
  258. package/dist/packages/packs/src/verdicts.js +17 -0
  259. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  260. package/dist/packages/popup/src/config.js +71 -0
  261. package/dist/packages/popup/src/dock.js +271 -0
  262. package/dist/packages/popup/src/host.js +210 -0
  263. package/dist/packages/popup/src/index.js +12 -0
  264. package/dist/packages/popup/src/loopback.js +53 -0
  265. package/dist/packages/popup/src/open.js +183 -0
  266. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  267. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  268. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  269. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  270. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  271. package/dist/packages/popup/src/pages/chart.css +50 -0
  272. package/dist/packages/popup/src/pages/chart.html +56 -0
  273. package/dist/packages/popup/src/pages/chart.js +554 -0
  274. package/dist/packages/popup/src/pages/common.css +34 -0
  275. package/dist/packages/popup/src/pages/common.js +187 -0
  276. package/dist/packages/popup/src/pages/feeds.html +20 -0
  277. package/dist/packages/popup/src/pages/feeds.js +77 -0
  278. package/dist/packages/popup/src/pages/narrative.html +20 -0
  279. package/dist/packages/popup/src/pages/narrative.js +61 -0
  280. package/dist/packages/popup/src/pages/plans.css +30 -0
  281. package/dist/packages/popup/src/pages/plans.html +26 -0
  282. package/dist/packages/popup/src/pages/plans.js +82 -0
  283. package/dist/packages/popup/src/pages/roster.css +41 -0
  284. package/dist/packages/popup/src/pages/roster.html +25 -0
  285. package/dist/packages/popup/src/pages/roster.js +75 -0
  286. package/dist/packages/popup/src/pages/token.html +20 -0
  287. package/dist/packages/popup/src/pages/token.js +112 -0
  288. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  289. package/dist/packages/popup/src/pages/widget.css +5 -0
  290. package/dist/packages/popup/src/pages/widget.html +21 -0
  291. package/dist/packages/popup/src/pages/widget.js +85 -0
  292. package/dist/packages/popup/src/protocol.js +31 -0
  293. package/dist/packages/popup/src/selftest.js +156 -0
  294. package/dist/packages/popup/src/server.js +582 -0
  295. package/dist/packages/popup/src/sinkproxy.js +167 -0
  296. package/dist/packages/popup/src/widget-html.js +231 -0
  297. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  298. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  299. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  300. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  301. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  302. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  303. package/dist/packages/recorder/src/cli.js +113 -0
  304. package/dist/packages/recorder/src/export.js +71 -0
  305. package/dist/packages/recorder/src/index.js +20 -0
  306. package/dist/packages/recorder/src/labels.js +151 -0
  307. package/dist/packages/recorder/src/open.js +29 -0
  308. package/dist/packages/recorder/src/recorder.js +553 -0
  309. package/dist/packages/recorder/src/schedule.js +43 -0
  310. package/dist/packages/recorder/src/schema.js +135 -0
  311. package/dist/packages/recorder/src/snapshot.js +191 -0
  312. package/dist/packages/recorder/src/sweep.js +52 -0
  313. package/dist/packages/recorder/src/verdicts.js +52 -0
  314. package/dist/packages/tui/src/bubbles.js +415 -0
  315. package/dist/packages/tui/src/canvas.js +370 -0
  316. package/dist/packages/tui/src/chrome.js +85 -0
  317. package/dist/packages/tui/src/color.js +133 -0
  318. package/dist/packages/tui/src/flush.js +62 -0
  319. package/dist/packages/tui/src/fmt.js +188 -0
  320. package/dist/packages/tui/src/fx-trade.js +278 -0
  321. package/dist/packages/tui/src/fx.js +527 -0
  322. package/dist/packages/tui/src/images/blocks.js +141 -0
  323. package/dist/packages/tui/src/images/coin.js +22 -0
  324. package/dist/packages/tui/src/images/detect.js +140 -0
  325. package/dist/packages/tui/src/images/index.js +7 -0
  326. package/dist/packages/tui/src/images/layer.js +146 -0
  327. package/dist/packages/tui/src/images/pixels.js +157 -0
  328. package/dist/packages/tui/src/images/protocols.js +87 -0
  329. package/dist/packages/tui/src/index.js +15 -0
  330. package/dist/packages/tui/src/keymap.js +355 -0
  331. package/dist/packages/tui/src/layout.js +11 -0
  332. package/dist/packages/tui/src/sound.js +177 -0
  333. package/dist/packages/tui/src/theme.js +182 -0
  334. package/dist/packages/tui/src/widgets.js +598 -0
  335. package/dist/packages/voice/src/controller.js +395 -0
  336. package/dist/packages/voice/src/doctor.js +63 -0
  337. package/dist/packages/voice/src/engines.js +44 -0
  338. package/dist/packages/voice/src/index.js +16 -0
  339. package/dist/packages/voice/src/router.js +502 -0
  340. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  341. package/dist/packages/voice/src/stt/apple.js +179 -0
  342. package/dist/packages/voice/src/stt/fake.js +34 -0
  343. package/dist/packages/voice/src/stt/whisper.js +102 -0
  344. package/dist/packages/voice/src/summary.js +44 -0
  345. package/dist/packages/voice/src/tts/fake.js +30 -0
  346. package/dist/packages/voice/src/tts/say.js +96 -0
  347. package/dist/packages/voice/src/types.js +3 -0
  348. package/package.json +42 -4
  349. package/README.md +0 -3
@@ -0,0 +1,288 @@
1
+ // LARP detector — mechanical evidence signals (task-1315). Pure + deterministic
2
+ // given fetched bytes; no network except through an injected fetcher. These are
3
+ // SIGNALS, never verdicts: the analyst weighs them. HTML is parsed with regex
4
+ // only (zero-dep, and we never execute it) because the page is untrusted data.
5
+ import { classifyLink, parseHttpUrl } from "./links.js";
6
+ const DRAINER_SIGNATURES = [
7
+ [/setApprovalForAll/i, 'setApprovalForAll (ERC-721/1155 approval drainer)'],
8
+ [/permit2|permit\(/i, 'permit/permit2 signature grab'],
9
+ [/eth_sendTransaction|window\.ethereum/i, 'EVM wallet calls on a Solana token (chain mismatch)'],
10
+ [/eval\s*\(\s*atob\s*\(/i, 'eval(atob( obfuscated script'],
11
+ [/Function\s*\(\s*atob/i, 'Function(atob( obfuscated script'],
12
+ [/drainer|seaport\.js|inferno|monkey-drainer|pink-drainer/i, 'known drainer-kit string'],
13
+ [/signAllTransactions[\s\S]{0,400}(SystemProgram\.transfer|createTransferInstruction)/i, 'sign-all then transfer-all pattern'],
14
+ ];
15
+ const BUILDER_SIGNATURES = [
16
+ [/\.carrd\.co|data-carrd|cardScript/i, 'carrd'],
17
+ [/static\.wixstatic\.com|X-Wix-|wix\.com/i, 'wix'],
18
+ [/squarespace\.com|static1\.squarespace/i, 'squarespace'],
19
+ [/framerusercontent\.com|framer\.com|data-framer/i, 'framer'],
20
+ [/assets\.website-files\.com|webflow\.(io|com)|data-wf-/i, 'webflow'],
21
+ [/cdn\.durable\.co|durable\.co/i, 'durable'],
22
+ [/\.github\.io|github-pages/i, 'github-pages'],
23
+ [/vercel\.app|_next\/static/i, 'vercel/next'],
24
+ [/netlify\.app|netlify/i, 'netlify'],
25
+ [/dorik|data-dorik/i, 'dorik'],
26
+ ];
27
+ const DEFAULT_PAGE_SIGNATURES = [
28
+ [/there isn.?t a github pages site here|404\.html/i, 'github-pages 404'],
29
+ [/the deployment could not be found|deployment_not_found|welcome to vercel/i, 'vercel default/missing'],
30
+ [/this (domain|site) is (for sale|parked)|buy this domain|parkingcrew|sedoparking|godaddy\.com\/domainfind/i, 'parked domain'],
31
+ [/future home of something|default web page|it works!|apache2 (ubuntu|debian) default/i, 'web-server default page'],
32
+ [/site not found|no such app|there's nothing here/i, 'host placeholder (missing app)'],
33
+ [/this site can.?t be reached|dns_probe_finished/i, 'unreachable'],
34
+ [/^\s*(coming soon|under construction)\s*$/i, 'coming-soon stub'],
35
+ ];
36
+ function firstMatch(html, table) {
37
+ for (const [re, name] of table)
38
+ if (re.test(html))
39
+ return name;
40
+ return null;
41
+ }
42
+ function allMatches(html, table) {
43
+ const out = [];
44
+ for (const [re, name] of table)
45
+ if (re.test(html))
46
+ out.push(name);
47
+ return out;
48
+ }
49
+ function tag(html, re) {
50
+ const m = re.exec(html);
51
+ return m ? m[1].replace(/\s+/g, ' ').trim() : null;
52
+ }
53
+ /** Strip tags/scripts/styles to a rough visible-text approximation. */
54
+ function visibleText(html) {
55
+ return html
56
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
57
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
58
+ .replace(/<!--[\s\S]*?-->/g, ' ')
59
+ .replace(/<[^>]+>/g, ' ')
60
+ .replace(/&[a-z#0-9]+;/gi, ' ')
61
+ .replace(/\s+/g, ' ')
62
+ .trim();
63
+ }
64
+ /** Every absolute http(s) href/src on the page. */
65
+ export function extractUrls(html, baseUrl) {
66
+ const out = new Set();
67
+ const re = /(?:href|src)\s*=\s*["']([^"']+)["']/gi;
68
+ let m;
69
+ while ((m = re.exec(html)) !== null) {
70
+ const raw = m[1];
71
+ let abs;
72
+ try {
73
+ abs = new URL(raw, baseUrl ?? undefined).toString();
74
+ }
75
+ catch {
76
+ abs = null;
77
+ }
78
+ const p = abs ? parseHttpUrl(abs) : null;
79
+ if (p)
80
+ out.add(p.toString());
81
+ }
82
+ return [...out];
83
+ }
84
+ function hostOf(url) {
85
+ const u = parseHttpUrl(url);
86
+ return u ? u.hostname.toLowerCase().replace(/^www\./, '') : null;
87
+ }
88
+ /**
89
+ * Compute mechanical signals from a fetched page. `symbol`/`name` enable the "does the site even
90
+ * mention this token" check. Returns a `fetched:false` bundle (with the block reason) when the
91
+ * fetch failed, so the analyst can report it as a missing-data fact rather than guess.
92
+ */
93
+ export function pageSignals(res, opts = {}) {
94
+ const base = {
95
+ fetched: res.ok, status: res.status, finalUrl: res.ok ? res.finalUrl : null, blocked: res.blocked,
96
+ title: null, description: null, generator: null, siteBuilder: null, defaultPage: null,
97
+ textLength: 0, linkCount: 0, externalHosts: [], hasApp: false, hasDocs: false, hasWhitepaper: false,
98
+ hasGithub: false, walletConnect: false, drainerFlags: [], scriptHosts: [], mentionsSymbol: null, mentionsName: null, excerpt: '',
99
+ links: [], teamClaim: false, stockTextSignals: [],
100
+ };
101
+ if (!res.ok || !res.body)
102
+ return base;
103
+ const html = res.body;
104
+ const finalHost = hostOf(res.finalUrl);
105
+ const title = tag(html, /<title[^>]*>([\s\S]*?)<\/title>/i);
106
+ const description = tag(html, /<meta[^>]+name=["']description["'][^>]*content=["']([^"']*)["']/i)
107
+ ?? tag(html, /<meta[^>]+property=["']og:description["'][^>]*content=["']([^"']*)["']/i);
108
+ const generator = tag(html, /<meta[^>]+name=["']generator["'][^>]*content=["']([^"']*)["']/i);
109
+ const text = visibleText(html);
110
+ const urls = extractUrls(html, res.finalUrl);
111
+ const externalHosts = [...new Set(urls.map(hostOf).filter((h) => !!h && h !== finalHost))];
112
+ const scriptRe = /<script[^>]+src\s*=\s*["']([^"']+)["']/gi;
113
+ const scriptHosts = new Set();
114
+ let sm;
115
+ while ((sm = scriptRe.exec(html)) !== null) {
116
+ const h = hostOf(sm[1].startsWith('http') ? sm[1] : new URL(sm[1], res.finalUrl ?? 'https://x/').toString());
117
+ if (h && h !== finalHost)
118
+ scriptHosts.add(h);
119
+ }
120
+ const kinds = new Set(urls.map((u) => { const p = parseHttpUrl(u); return p ? classifyLink(p) : 'other'; }));
121
+ const lowerHtml = html.toLowerCase();
122
+ const hasApp = /\b(launch app|open app|dapp|\/app\b|app\.|demo|try (it|now)|get started)\b/i.test(text) || /href=["'][^"']*\/(app|dapp|demo)\b/i.test(html);
123
+ const sym = (opts.symbol ?? '').replace(/^\$/, '').toLowerCase();
124
+ const name = (opts.name ?? '').toLowerCase();
125
+ const lowerText = text.toLowerCase();
126
+ return {
127
+ ...base,
128
+ title: title ? title.slice(0, 160) : null,
129
+ description: description ? description.slice(0, 300) : null,
130
+ generator: generator ? generator.slice(0, 80) : null,
131
+ siteBuilder: firstMatch(html, BUILDER_SIGNATURES) ?? (generator ? generator.split(/[\s(]/)[0].toLowerCase() : null),
132
+ defaultPage: firstMatch(text, DEFAULT_PAGE_SIGNATURES) ?? (text.length < 40 ? 'near-empty page' : null),
133
+ textLength: text.length,
134
+ linkCount: urls.length,
135
+ externalHosts: externalHosts.slice(0, 30),
136
+ hasApp,
137
+ hasDocs: kinds.has('docs') || /\bdocs?\b|documentation|whitepaper|litepaper/i.test(text),
138
+ hasWhitepaper: /whitepaper|litepaper/i.test(lowerHtml),
139
+ hasGithub: kinds.has('github'),
140
+ walletConnect: /connect\s*wallet|walletconnect|@solana\/wallet-adapter|window\.solana|phantom|solflare/i.test(html),
141
+ drainerFlags: allMatches(html, DRAINER_SIGNATURES),
142
+ scriptHosts: [...scriptHosts].slice(0, 20),
143
+ mentionsSymbol: sym ? lowerText.includes(sym) : null,
144
+ mentionsName: name ? lowerText.includes(name) : null,
145
+ excerpt: text.slice(0, 1200),
146
+ links: urls.slice(0, 24).map((url) => ({ url, kind: classifyLink(new URL(url)) })),
147
+ teamClaim: /\b(our team|founder|co-founder|meet the team)\b/i.test(text),
148
+ stockTextSignals: allMatches(text, [[/lorem ipsum/i, 'lorem ipsum placeholder'], [/your (company|brand|business) (name|here)/i, 'template placeholder'], [/insert (text|copy|title) here/i, 'unfilled template']]),
149
+ };
150
+ }
151
+ /** Exact advertised domain, stripping only www. Without a PSL, never attribute
152
+ * a public suffix's registration (co.uk) to a project (docs.coin.co.uk).
153
+ * RDAP may refuse a subdomain; that is missing data, not an invented age. */
154
+ export function registrableDomain(url) {
155
+ const u = parseHttpUrl(url);
156
+ if (!u)
157
+ return null;
158
+ const host = u.hostname.toLowerCase().replace(/^www\./, '');
159
+ if (/^\d/.test(host) || host.includes(':'))
160
+ return null; // IP literal
161
+ const parts = host.split('.');
162
+ if (parts.length < 2)
163
+ return null;
164
+ return host;
165
+ }
166
+ /** Domain registration age + registrar via RDAP (rdap.org bootstrap). Keyless. */
167
+ export async function domainAgeViaRdap(url, fetchText, now = () => Date.now()) {
168
+ const domain = registrableDomain(url);
169
+ const base = { domain: domain ?? '', registeredAt: null, ageDays: null, registrar: null, privacy: null, source: 'rdap', error: null };
170
+ if (!domain)
171
+ return { ...base, error: 'no registrable domain' };
172
+ let r;
173
+ try {
174
+ r = await fetchText(`https://rdap.org/domain/${encodeURIComponent(domain)}`);
175
+ }
176
+ catch (e) {
177
+ return { ...base, error: `rdap fetch failed: ${e.message}` };
178
+ }
179
+ if (!r.ok)
180
+ return { ...base, error: `rdap status ${r.status}` };
181
+ let data;
182
+ try {
183
+ data = JSON.parse(r.body);
184
+ }
185
+ catch {
186
+ return { ...base, error: 'rdap: invalid json' };
187
+ }
188
+ const events = data.events ?? [];
189
+ const reg = events.find((e) => e.eventAction === 'registration')?.eventDate ?? null;
190
+ const entities = data.entities ?? [];
191
+ const registrar = entities.find((e) => e.roles?.includes('registrar'))?.vcardArray ? vcardName(entities.find((e) => e.roles?.includes('registrar')).vcardArray) : null;
192
+ const registrant = entities.find((e) => e.roles?.includes('registrant'));
193
+ const privacy = registrant ? /redact|privacy|protect|withheld|gdpr/i.test(JSON.stringify(registrant)) : null;
194
+ const ageDays = reg ? Math.max(0, Math.round((now() - Date.parse(reg)) / 86_400_000)) : null;
195
+ return { domain, registeredAt: reg, ageDays: Number.isFinite(ageDays) ? ageDays : null, registrar, privacy, source: 'rdap', error: null };
196
+ }
197
+ function vcardName(vcardArray) {
198
+ // vCard jCard: ["vcard", [ ["fn", {}, "text", "Name"], ... ]]
199
+ try {
200
+ const entries = vcardArray[1];
201
+ const fn = entries.find((e) => e[0] === 'fn');
202
+ return fn ? String(fn[3]).slice(0, 80) : null;
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ /** Parse github.com/<owner>/<repo> from a URL. */
209
+ export function parseGithubRepo(url) {
210
+ const u = parseHttpUrl(url);
211
+ if (!u || u.hostname.toLowerCase().replace(/^www\./, '') !== 'github.com')
212
+ return null;
213
+ const parts = u.pathname.split('/').filter(Boolean);
214
+ if (parts.length < 2)
215
+ return null;
216
+ const owner = parts[0];
217
+ const repo = parts[1].replace(/\.git$/, '');
218
+ if (/^(orgs|sponsors|features|about|topics|collections|marketplace)$/i.test(owner))
219
+ return null;
220
+ return { owner, repo };
221
+ }
222
+ /** Repo creation/last-push/stars via the keyless GitHub REST API (60 req/hr unauth). */
223
+ export async function githubRepoActivity(url, fetchText, now = () => Date.now()) {
224
+ const r = parseGithubRepo(url);
225
+ if (!r)
226
+ return null;
227
+ const base = { owner: r.owner, repo: r.repo, createdAt: null, pushedAt: null, daysSincePush: null, stars: null, openIssues: null, archived: null, fork: null, description: null, error: null };
228
+ let res;
229
+ try {
230
+ res = await fetchText(`https://api.github.com/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}`);
231
+ }
232
+ catch (e) {
233
+ return { ...base, error: `github fetch failed: ${e.message}` };
234
+ }
235
+ if (!res.ok)
236
+ return { ...base, error: res.status === 404 ? 'repo not found' : `github status ${res.status}` };
237
+ let d;
238
+ try {
239
+ d = JSON.parse(res.body);
240
+ }
241
+ catch {
242
+ return { ...base, error: 'github: invalid json' };
243
+ }
244
+ const pushedAt = typeof d.pushed_at === 'string' ? d.pushed_at : null;
245
+ const daysSincePush = pushedAt ? Math.max(0, Math.round((now() - Date.parse(pushedAt)) / 86_400_000)) : null;
246
+ const commits = [];
247
+ let historyError = null;
248
+ try {
249
+ const history = await fetchText(`https://api.github.com/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}/commits?per_page=5`);
250
+ const parsed = history.ok ? JSON.parse(history.body) : null;
251
+ if (!Array.isArray(parsed))
252
+ historyError = `commit history unavailable (${history.status})`;
253
+ else
254
+ for (const item of parsed.slice(0, 5)) {
255
+ if (typeof item?.sha === 'string' && /^[a-f0-9]{40}$/.test(item.sha))
256
+ commits.push({ sha: item.sha, date: typeof item.commit?.committer?.date === 'string' ? item.commit.committer.date : null });
257
+ }
258
+ }
259
+ catch {
260
+ historyError = 'commit history unavailable';
261
+ }
262
+ return {
263
+ owner: r.owner, repo: r.repo,
264
+ createdAt: typeof d.created_at === 'string' ? d.created_at : null,
265
+ pushedAt, daysSincePush,
266
+ stars: typeof d.stargazers_count === 'number' ? d.stargazers_count : null,
267
+ openIssues: typeof d.open_issues_count === 'number' ? d.open_issues_count : null,
268
+ archived: typeof d.archived === 'boolean' ? d.archived : null,
269
+ fork: typeof d.fork === 'boolean' ? d.fork : null,
270
+ description: typeof d.description === 'string' ? d.description.slice(0, 200) : null,
271
+ commits, historyError,
272
+ error: null,
273
+ };
274
+ }
275
+ /** Reachability of a small set of advertised links (broken-link signal). Caller caps the list. */
276
+ export async function probeLinks(urls, fetchProbe, max = 8) {
277
+ const out = [];
278
+ for (const url of urls.slice(0, max)) {
279
+ try {
280
+ const r = await fetchProbe(url);
281
+ out.push({ url, ok: r.ok, status: r.status, blocked: r.blocked });
282
+ }
283
+ catch (e) {
284
+ out.push({ url, ok: false, status: 0, blocked: e.message });
285
+ }
286
+ }
287
+ return out;
288
+ }
@@ -0,0 +1,137 @@
1
+ // LARP detector — link extraction (task-1315). Pure, offline, deterministic.
2
+ // Collects every website/social link a coin advertises from the three keyless
3
+ // sources we already read — DexScreener token `info` (websites[] + socials[]),
4
+ // Jupiter token metadata (website/twitter/telegram) and the pump.fun metadata
5
+ // URI JSON — then classifies and de-duplicates them. No network here: callers
6
+ // pass already-fetched raw shapes. Only http/https links survive; mailto:,
7
+ // javascript:, data: and relative links are dropped.
8
+ const MAX_LINKS = 24;
9
+ /** Parse to a URL only if it is a syntactically valid absolute http(s) link. */
10
+ export function parseHttpUrl(raw) {
11
+ if (typeof raw !== 'string')
12
+ return null;
13
+ const s = raw.trim();
14
+ if (!s || s.length > 2048)
15
+ return null;
16
+ let u;
17
+ try {
18
+ u = new URL(s);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ if (u.protocol !== 'http:' && u.protocol !== 'https:')
24
+ return null;
25
+ if (u.username || u.password)
26
+ return null;
27
+ if (!u.hostname)
28
+ return null;
29
+ return u;
30
+ }
31
+ /** Lower-cased host with any leading `www.` removed. */
32
+ function bareHost(u) {
33
+ return u.hostname.toLowerCase().replace(/^www\./, '');
34
+ }
35
+ /** Classify a link by host (and path for docs). Host is the source of truth, not any label. */
36
+ export function classifyLink(u) {
37
+ const host = bareHost(u);
38
+ const path = u.pathname.toLowerCase();
39
+ if (host === 'twitter.com' || host === 'x.com' || host === 'fxtwitter.com' || host === 'nitter.net' || host === 't.co')
40
+ return 'x';
41
+ if (host === 't.me' || host === 'telegram.me' || host === 'telegram.dog')
42
+ return 'telegram';
43
+ if (host === 'discord.gg' || host === 'discord.com' || host === 'discordapp.com')
44
+ return 'discord';
45
+ if (host === 'github.com' || host === 'raw.githubusercontent.com')
46
+ return 'github';
47
+ if (host === 'medium.com' || host.endsWith('.medium.com'))
48
+ return 'medium';
49
+ if (host === 'youtube.com' || host === 'youtu.be' || host.endsWith('.youtube.com'))
50
+ return 'youtube';
51
+ if (host === 'reddit.com' || host.endsWith('.reddit.com'))
52
+ return 'reddit';
53
+ if (host === 'instagram.com' || host.endsWith('.instagram.com'))
54
+ return 'instagram';
55
+ if (host === 'tiktok.com' || host.endsWith('.tiktok.com'))
56
+ return 'tiktok';
57
+ if (host === 'facebook.com' || host === 'fb.com' || host.endsWith('.facebook.com'))
58
+ return 'facebook';
59
+ if (host === 'linktr.ee' || host === 'linktree.com')
60
+ return 'linktree';
61
+ if (host.endsWith('.gitbook.io') || host === 'gitbook.io' || host.startsWith('docs.') || path.startsWith('/docs') || path.startsWith('/whitepaper'))
62
+ return 'docs';
63
+ return 'website';
64
+ }
65
+ /** A short, injection-inert label for the link list (host; DexScreener website labels when sane). */
66
+ function labelFor(u, rawLabel, kind) {
67
+ const host = bareHost(u);
68
+ if (kind === 'website' && rawLabel) {
69
+ const clean = rawLabel.replace(/[\u0000-\u001F\u007F]/g, '').replace(/\s+/g, ' ').trim().slice(0, 24);
70
+ if (clean)
71
+ return clean;
72
+ }
73
+ return host;
74
+ }
75
+ /** Map a DexScreener social `type` to our kind when the host is ambiguous (host still wins). */
76
+ function socialTypeKind(type) {
77
+ switch ((type ?? '').toLowerCase()) {
78
+ case 'twitter':
79
+ case 'x': return 'x';
80
+ case 'telegram': return 'telegram';
81
+ case 'discord': return 'discord';
82
+ case 'github': return 'github';
83
+ case 'medium': return 'medium';
84
+ case 'youtube': return 'youtube';
85
+ case 'reddit': return 'reddit';
86
+ case 'instagram': return 'instagram';
87
+ case 'tiktok': return 'tiktok';
88
+ case 'facebook': return 'facebook';
89
+ default: return null;
90
+ }
91
+ }
92
+ /** De-dup key: scheme-insensitive host + path (ignores trailing slash and query). */
93
+ function dedupKey(u) {
94
+ return `${bareHost(u)}${u.pathname.replace(/\/+$/, '')}`.toLowerCase();
95
+ }
96
+ /**
97
+ * Extract and classify every advertised link for a token. Source order sets priority for the
98
+ * primary website/x/telegram picks and for de-duplication (the first occurrence wins its label).
99
+ */
100
+ export function extractLinks(src) {
101
+ const out = [];
102
+ const seen = new Set();
103
+ const add = (raw, source, typeHint, rawLabel) => {
104
+ if (out.length >= MAX_LINKS)
105
+ return;
106
+ const u = parseHttpUrl(raw);
107
+ if (!u)
108
+ return;
109
+ const key = dedupKey(u);
110
+ if (seen.has(key))
111
+ return;
112
+ seen.add(key);
113
+ // Host classification wins; the DexScreener `type` only disambiguates a generic host.
114
+ let kind = classifyLink(u);
115
+ if (kind === 'website') {
116
+ const hinted = socialTypeKind(typeHint);
117
+ if (hinted)
118
+ kind = hinted;
119
+ }
120
+ out.push({ kind, url: u.toString(), label: labelFor(u, rawLabel, kind), source });
121
+ };
122
+ // 1) DexScreener info: explicit websites[] then socials[].
123
+ for (const w of src.dexscreener?.info?.websites ?? [])
124
+ add(w?.url, 'dexscreener', undefined, w?.label);
125
+ for (const s of src.dexscreener?.info?.socials ?? [])
126
+ add(s?.url, 'dexscreener', s?.type, s?.label);
127
+ // 2) Jupiter flat fields.
128
+ add(src.jupiter?.website, 'jupiter');
129
+ add(src.jupiter?.twitter, 'jupiter', 'twitter');
130
+ add(src.jupiter?.telegram, 'jupiter', 'telegram');
131
+ // 3) pump.fun metadata URI JSON.
132
+ add(src.pumpfun?.website, 'pumpfun');
133
+ add(src.pumpfun?.twitter ?? src.pumpfun?.x, 'pumpfun', 'twitter');
134
+ add(src.pumpfun?.telegram, 'pumpfun', 'telegram');
135
+ const first = (k) => out.find((l) => l.kind === k)?.url ?? null;
136
+ return { website: first('website'), x: first('x'), telegram: first('telegram'), all: out };
137
+ }
@@ -0,0 +1,90 @@
1
+ // Conservative robots policy for analyst page reads, never a bypass for blocked pages.
2
+ import { safeFetch, SsrfError } from "./safe-fetch.js";
3
+ // Iterative wildcard matching avoids regex backtracking on attacker-owned robots.
4
+ function pathMatches(pattern, target) {
5
+ const unreserved = (s) => s.replace(/%([a-f0-9]{2})/gi, (m, hex) => {
6
+ const ch = String.fromCharCode(parseInt(hex, 16));
7
+ return /[a-z0-9._~-]/i.test(ch) ? ch : m.toUpperCase();
8
+ });
9
+ pattern = unreserved(pattern);
10
+ target = unreserved(target);
11
+ const exact = pattern.endsWith('$');
12
+ const p = (exact ? pattern.slice(0, -1) : pattern + '*');
13
+ let i = 0, j = 0, star = -1, retry = 0;
14
+ while (j < target.length) {
15
+ if (p[i] === '*') {
16
+ star = i++;
17
+ retry = j;
18
+ }
19
+ else if (p[i] === target[j]) {
20
+ i++;
21
+ j++;
22
+ }
23
+ else if (star >= 0) {
24
+ i = star + 1;
25
+ j = ++retry;
26
+ }
27
+ else
28
+ return false;
29
+ }
30
+ while (p[i] === '*')
31
+ i++;
32
+ return i === p.length;
33
+ }
34
+ /** RFC-style product groups and longest-path allow/disallow matching. */
35
+ export function robotsAllows(text, url) {
36
+ const groups = [];
37
+ let group = { agents: [], rules: [] };
38
+ for (const line of text.split(/\r?\n/)) {
39
+ const m = /^\s*([\w-]+)\s*:\s*(.*?)\s*$/.exec(line.split('#')[0]);
40
+ if (!m)
41
+ continue;
42
+ const key = m[1].toLowerCase(), value = m[2];
43
+ if (key === 'user-agent') {
44
+ if (group.rules.length) {
45
+ groups.push(group);
46
+ group = { agents: [], rules: [] };
47
+ }
48
+ group.agents.push(value.toLowerCase());
49
+ }
50
+ else if (group.agents.length && (key === 'allow' || key === 'disallow') && value) {
51
+ group.rules.push({ allow: key === 'allow', path: value });
52
+ }
53
+ }
54
+ groups.push(group);
55
+ const specific = groups.filter((g) => g.agents.includes('dexterm'));
56
+ const selected = specific.length ? specific : groups.filter((g) => g.agents.includes('*'));
57
+ let best = -1, allowed = true;
58
+ for (const rule of selected.flatMap((g) => g.rules)) {
59
+ if (pathMatches(rule.path, url.pathname + url.search)) {
60
+ const length = rule.path.replaceAll('*', '').length;
61
+ if (length > best || (length === best && rule.allow)) {
62
+ best = length;
63
+ allowed = rule.allow;
64
+ }
65
+ }
66
+ }
67
+ return allowed;
68
+ }
69
+ export async function fetchPage(url, options = {}) {
70
+ // Per-operation cache only: no persistent untrusted state, and every origin
71
+ // reached by a redirect gets its own robots check through the SSRF transport.
72
+ const robots = new Map();
73
+ return safeFetch(url, { ...options, beforeRequest: async (target) => {
74
+ let result = robots.get(target.origin);
75
+ if (!result) {
76
+ result = await safeFetch(new URL('/robots.txt', target).href, { ...options, beforeRequest: undefined, maxBytes: 64 * 1024, timeoutMs: 3000 });
77
+ robots.set(target.origin, result);
78
+ }
79
+ if ([404, 410].includes(result.status) && !result.blocked)
80
+ return;
81
+ if (!result.ok || result.blocked || result.truncated)
82
+ throw new SsrfError('robots unavailable; page not fetched');
83
+ // A long requested crawl delay cannot fit this interactive tool's budget.
84
+ // Decline the page rather than quietly violate it (even for another group).
85
+ if ([...result.body.matchAll(/^\s*crawl-delay\s*:\s*([\d.]+)/gim)].some((m) => Number(m[1]) > 1))
86
+ throw new SsrfError('robots crawl-delay exceeds interactive budget; page not fetched');
87
+ if (!robotsAllows(result.body, target))
88
+ throw new SsrfError('robots disallow; page not fetched');
89
+ } });
90
+ }