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,141 @@
1
+ // @dexterm/data — on-disk coin-image cache (task-1326). Lives under the app data dir (next to dexterm.db):
2
+ // `<dir>/images/<sha256(url)[0..31]>.png` holds OUR re-encoded thumbnail (never the bytes the host sent),
3
+ // so nothing downstream — the popup page, a future export — ever parses third-party image data. A
4
+ // `.fail` marker remembers a refused/undecodable URL for a while so a dead IPFS link is not retried on
5
+ // every launch. LRU by file mtime: a hit touches the file; when the directory grows past `maxBytes` the
6
+ // oldest files go. Every filesystem failure is swallowed (a cache must never break the app).
7
+ import { createHash } from 'node:crypto';
8
+ import { mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync, utimesSync, renameSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ export function imageCacheKey(url) {
11
+ return createHash('sha256').update(url).digest('hex').slice(0, 32);
12
+ }
13
+ export class ImageDiskCache {
14
+ dir;
15
+ maxBytes;
16
+ failTtlMs;
17
+ now;
18
+ ready = false;
19
+ lastTouch = new Map();
20
+ stats = { files: 0, bytes: 0, hits: 0, misses: 0, writes: 0, evictions: 0 };
21
+ constructor(opts) {
22
+ this.dir = opts.dir;
23
+ this.maxBytes = opts.maxBytes ?? 32 * 1024 * 1024;
24
+ this.failTtlMs = opts.failTtlMs ?? 6 * 3_600_000;
25
+ this.now = opts.now ?? (() => Date.now());
26
+ }
27
+ ensure() {
28
+ if (this.ready)
29
+ return true;
30
+ try {
31
+ mkdirSync(this.dir, { recursive: true, mode: 0o700 });
32
+ this.ready = true;
33
+ }
34
+ catch { /* unwritable: cache off */ }
35
+ return this.ready;
36
+ }
37
+ pathFor(key, ext) { return join(this.dir, `${key}.${ext}`); }
38
+ /** Our stored thumbnail PNG for a URL key, or null. Touches the file (LRU). */
39
+ get(key) {
40
+ if (!this.ensure())
41
+ return null;
42
+ try {
43
+ const p = this.pathFor(key, 'png');
44
+ const buf = readFileSync(p);
45
+ this.stats.hits += 1;
46
+ const t = this.now();
47
+ if ((this.lastTouch.get(key) ?? 0) + 600_000 < t) {
48
+ this.lastTouch.set(key, t);
49
+ try {
50
+ utimesSync(p, new Date(t), new Date(t));
51
+ }
52
+ catch { /* read-only fs: LRU just degrades */ }
53
+ }
54
+ return buf;
55
+ }
56
+ catch {
57
+ this.stats.misses += 1;
58
+ return null;
59
+ }
60
+ }
61
+ /** True when a failure marker younger than failTtl exists for this key (the marker carries its own timestamp). */
62
+ failed(key) {
63
+ if (!this.ensure())
64
+ return false;
65
+ try {
66
+ const text = readFileSync(this.pathFor(key, 'fail'), 'utf8');
67
+ const at = Number(text.split('\n')[0]);
68
+ return Number.isFinite(at) && this.now() - at < this.failTtlMs;
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
74
+ markFailed(key, reason) {
75
+ if (!this.ensure())
76
+ return;
77
+ try {
78
+ writeFileSync(this.pathFor(key, 'fail'), `${this.now()}\n${reason.slice(0, 200)}`, { mode: 0o600 });
79
+ }
80
+ catch { /* ignore */ }
81
+ }
82
+ /** Store our PNG thumbnail (atomic rename), then evict the oldest files past the byte cap. */
83
+ put(key, png) {
84
+ if (!this.ensure())
85
+ return;
86
+ try {
87
+ const p = this.pathFor(key, 'png');
88
+ const tmp = `${p}.${process.pid}.tmp`;
89
+ writeFileSync(tmp, png, { mode: 0o600 });
90
+ renameSync(tmp, p);
91
+ try {
92
+ const t = new Date(this.now());
93
+ utimesSync(p, t, t);
94
+ }
95
+ catch { /* LRU just degrades */ }
96
+ try {
97
+ unlinkSync(this.pathFor(key, 'fail'));
98
+ }
99
+ catch { /* no marker */ }
100
+ this.stats.writes += 1;
101
+ this.lastTouch.set(key, this.now());
102
+ this.evict();
103
+ }
104
+ catch { /* ignore */ }
105
+ }
106
+ /** Drop oldest files until the directory is under the cap. Also measures the directory. */
107
+ evict() {
108
+ let entries = [];
109
+ try {
110
+ for (const name of readdirSync(this.dir)) {
111
+ if (!name.endsWith('.png') && !name.endsWith('.fail'))
112
+ continue;
113
+ try {
114
+ const st = statSync(join(this.dir, name));
115
+ entries.push({ name, size: st.size, mtime: st.mtimeMs });
116
+ }
117
+ catch { /* vanished */ }
118
+ }
119
+ }
120
+ catch {
121
+ return;
122
+ }
123
+ let total = entries.reduce((s, e) => s + e.size, 0);
124
+ this.stats.files = entries.length;
125
+ if (total > this.maxBytes) {
126
+ entries = entries.sort((a, b) => a.mtime - b.mtime);
127
+ for (const e of entries) {
128
+ if (total <= this.maxBytes * 0.9)
129
+ break;
130
+ try {
131
+ unlinkSync(join(this.dir, e.name));
132
+ total -= e.size;
133
+ this.stats.evictions += 1;
134
+ this.stats.files -= 1;
135
+ }
136
+ catch { /* ignore */ }
137
+ }
138
+ }
139
+ this.stats.bytes = total;
140
+ }
141
+ }
@@ -0,0 +1,28 @@
1
+ // @dexterm/data — worker-thread entry for coin-image decoding (task-1326). Decoding a 800×800 PNG or a
2
+ // baseline JPEG takes 5–60 ms of pure JS; on the render thread that is one to two dropped frames per
3
+ // image at 30 fps, so the ImageService runs the decode + thumbnail + PNG re-encode here and only ever
4
+ // receives small transferable buffers back. Anything thrown (hostile bytes, caps) comes back as a message.
5
+ import { parentPort } from 'node:worker_threads';
6
+ import { decodeThumbnail } from "./decode.js";
7
+ import { encodePng } from "./png.js";
8
+ /** Pure job runner (also used by the main-thread fallback). */
9
+ export function runDecodeJob(job) {
10
+ try {
11
+ const d = decodeThumbnail(job.bytes, job.thumbMax);
12
+ const png = encodePng({ w: d.w, h: d.h, rgba: d.rgba });
13
+ // own copies: a pooled Buffer slab and a subarray view are not transferable across the worker boundary
14
+ return { id: job.id, ok: true, w: d.w, h: d.h, rgba: new Uint8Array(d.rgba), png: new Uint8Array(png), format: d.format, srcW: d.srcW, srcH: d.srcH };
15
+ }
16
+ catch (e) {
17
+ return { id: job.id, ok: false, error: e.message ?? String(e) };
18
+ }
19
+ }
20
+ if (parentPort) {
21
+ parentPort.on('message', (job) => {
22
+ const r = runDecodeJob(job);
23
+ if (r.ok)
24
+ parentPort.postMessage(r, [r.rgba.buffer, r.png.buffer]);
25
+ else
26
+ parentPort.postMessage(r);
27
+ });
28
+ }
@@ -0,0 +1,81 @@
1
+ import { DEFAULT_LIMITS, ImageDecodeError, sniffFormat } from "./types.js";
2
+ import { decodePng } from "./png.js";
3
+ import { decodeJpeg } from "./jpeg.js";
4
+ import { decodeGif } from "./gif.js";
5
+ /** Decode any supported container. Throws ImageDecodeError for junk, unsupported formats or capped inputs. */
6
+ export function decodeImage(bytes, limits = DEFAULT_LIMITS) {
7
+ if (bytes.length > limits.maxBytes)
8
+ throw new ImageDecodeError(`input is ${bytes.length} bytes, over the ${limits.maxBytes} byte cap`);
9
+ const format = sniffFormat(bytes);
10
+ if (!format)
11
+ throw new ImageDecodeError('not a png/jpeg/gif/webp (magic bytes)');
12
+ if (format === 'webp')
13
+ throw new ImageDecodeError('unsupported: webp (no decoder in the zero-dependency tree)');
14
+ const img = format === 'png' ? decodePng(bytes, limits) : format === 'jpeg' ? decodeJpeg(bytes, limits) : decodeGif(bytes, limits);
15
+ return { img, format };
16
+ }
17
+ /**
18
+ * Area-average resample to exactly `dw × dh` (box filter over the source rectangle each destination pixel
19
+ * covers). Alpha is averaged premultiplied so transparent edge pixels do not bleed their (usually black)
20
+ * colour into the thumbnail. Works for down- and up-scaling.
21
+ */
22
+ export function resample(src, dw, dh) {
23
+ dw = Math.max(1, Math.floor(dw));
24
+ dh = Math.max(1, Math.floor(dh));
25
+ const { w: sw, h: sh, rgba } = src;
26
+ const out = new Uint8Array(dw * dh * 4);
27
+ const xs = sw / dw, ys = sh / dh;
28
+ for (let y = 0; y < dh; y++) {
29
+ const y0 = y * ys, y1 = (y + 1) * ys;
30
+ const iy0 = Math.floor(y0), iy1 = Math.min(sh, Math.ceil(y1));
31
+ for (let x = 0; x < dw; x++) {
32
+ const x0 = x * xs, x1 = (x + 1) * xs;
33
+ const ix0 = Math.floor(x0), ix1 = Math.min(sw, Math.ceil(x1));
34
+ let r = 0, g = 0, b = 0, a = 0, wsum = 0;
35
+ for (let sy = iy0; sy < iy1; sy++) {
36
+ const wy = Math.min(y1, sy + 1) - Math.max(y0, sy);
37
+ if (wy <= 0)
38
+ continue;
39
+ for (let sx = ix0; sx < ix1; sx++) {
40
+ const wx = Math.min(x1, sx + 1) - Math.max(x0, sx);
41
+ if (wx <= 0)
42
+ continue;
43
+ const wgt = wx * wy;
44
+ const o = (sy * sw + sx) * 4;
45
+ const al = rgba[o + 3] / 255;
46
+ r += rgba[o] * al * wgt;
47
+ g += rgba[o + 1] * al * wgt;
48
+ b += rgba[o + 2] * al * wgt;
49
+ a += al * wgt;
50
+ wsum += wgt;
51
+ }
52
+ }
53
+ const oo = (y * dw + x) * 4;
54
+ if (wsum <= 0 || a <= 0) {
55
+ out[oo] = 0;
56
+ out[oo + 1] = 0;
57
+ out[oo + 2] = 0;
58
+ out[oo + 3] = 0;
59
+ continue;
60
+ }
61
+ out[oo] = Math.round(r / a);
62
+ out[oo + 1] = Math.round(g / a);
63
+ out[oo + 2] = Math.round(b / a);
64
+ out[oo + 3] = Math.round((a / wsum) * 255);
65
+ }
66
+ }
67
+ return { w: dw, h: dh, rgba: out };
68
+ }
69
+ /** Shrink to fit inside `max × max` keeping the aspect ratio; never upscales. */
70
+ export function fitWithin(src, max) {
71
+ if (src.w <= max && src.h <= max)
72
+ return src;
73
+ const s = max / Math.max(src.w, src.h);
74
+ return resample(src, Math.max(1, Math.round(src.w * s)), Math.max(1, Math.round(src.h * s)));
75
+ }
76
+ /** Decode + shrink to a thumbnail no larger than `thumbMax` on its longer side. */
77
+ export function decodeThumbnail(bytes, thumbMax = 96, limits = DEFAULT_LIMITS) {
78
+ const { img, format } = decodeImage(bytes, limits);
79
+ const small = fitWithin(img, thumbMax);
80
+ return { ...small, format, srcW: img.w, srcH: img.h };
81
+ }
@@ -0,0 +1,162 @@
1
+ // @dexterm/data — SSRF-safe binary GET for coin images (task-1326). Same policy as the LARP detector's
2
+ // safeFetch (larp/safe-fetch.ts): http/https only, no credentials, every hostname resolved and refused
3
+ // when ANY address is private/loopback/link-local/CGNAT/multicast/reserved, the socket pinned to the
4
+ // validated address, redirects followed manually (each hop re-validated, max 3), hard byte and time caps.
5
+ // Differences for images: the body is BINARY and capped by Content-Length up front, the Accept header asks
6
+ // for the formats we can decode (no webp), and there is no per-origin pacing (a CDN serving 40 icons is
7
+ // not a target site; the ImageService bounds concurrency instead). Never throws for a bad image/host:
8
+ // returns { ok:false, reason } so the UI can draw the placeholder.
9
+ import { request as httpRequest } from 'node:http';
10
+ import { request as httpsRequest } from 'node:https';
11
+ import { isIP } from 'node:net';
12
+ import { assertPublicUrl, publicAddresses, SsrfError } from "../larp/safe-fetch.js";
13
+ import { lookup as dnsLookup } from 'node:dns';
14
+ import { promisify } from 'node:util';
15
+ const lookupAll = promisify(dnsLookup);
16
+ async function defaultResolve(host) {
17
+ const res = await lookupAll(host, { all: true });
18
+ return res.map((r) => r.address);
19
+ }
20
+ const DEFAULTS = { maxBytes: 2 * 1024 * 1024, timeoutMs: 8000, maxRedirects: 3 };
21
+ export const IMAGE_ACCEPT = 'image/png,image/jpeg,image/gif;q=0.9,*/*;q=0.1';
22
+ export const IMAGE_USER_AGENT = 'dexterm/0.0.1 (+local terminal screener; coin image thumbnail, read-only)';
23
+ /** Node http/https transport with the DNS answer pinned to the validated public address. */
24
+ export function pinnedBytesTransport(resolve) {
25
+ return (url, { headers, maxBytes, signal }) => new Promise((yes, no) => {
26
+ const req = (url.protocol === 'https:' ? httpsRequest : httpRequest)(url, {
27
+ method: 'GET', agent: false, signal, headers,
28
+ lookup(host, options, cb) {
29
+ void publicAddresses(host, resolve).then((ips) => {
30
+ if (options.all)
31
+ cb(null, ips.map((address) => ({ address, family: isIP(address) })));
32
+ else
33
+ cb(null, ips[0], isIP(ips[0]));
34
+ }, (err) => cb(err, '', 4));
35
+ },
36
+ }, (res) => {
37
+ const headers = {};
38
+ for (const [k, v] of Object.entries(res.headers))
39
+ headers[k] = Array.isArray(v) ? v.join(', ') : v;
40
+ const status = res.statusCode ?? 502;
41
+ const cl = Number(headers['content-length']);
42
+ if (Number.isFinite(cl) && cl > maxBytes) {
43
+ res.destroy();
44
+ yes({ status, headers, body: new Uint8Array(0) });
45
+ return;
46
+ }
47
+ const chunks = [];
48
+ let total = 0;
49
+ res.on('data', (d) => {
50
+ if (total > maxBytes)
51
+ return;
52
+ chunks.push(d);
53
+ total += d.length;
54
+ if (total > maxBytes)
55
+ res.destroy(); // over the cap: stop reading; the caller sees length > maxBytes
56
+ });
57
+ res.on('end', () => yes({ status, headers, body: Buffer.concat(chunks, total) }));
58
+ res.on('close', () => yes({ status, headers, body: Buffer.concat(chunks, total) }));
59
+ res.on('error', (e) => { if (total > maxBytes)
60
+ yes({ status, headers, body: Buffer.concat(chunks, total) });
61
+ else
62
+ no(e); });
63
+ });
64
+ req.on('error', no);
65
+ req.end();
66
+ });
67
+ }
68
+ /**
69
+ * Rewrite known CDN URLs to ask for a small rendition: DexScreener's image CDN takes width/height query
70
+ * params (the screener's `info.imageUrl` asks for 800×800 PNGs, ~125 KB each; 128×128 is ~10 KB). Any other
71
+ * host is left untouched. Pure.
72
+ */
73
+ /** DexScreener's image CDN (the host most coin pictures come from); it honours width/height query params. */
74
+ export const DEXSCREENER_IMAGE_CDN = 'https://cdn.dexscreener.com';
75
+ export function thumbnailUrl(raw, px = 128) {
76
+ try {
77
+ const u = new URL(raw);
78
+ if (u.protocol !== 'https:' && u.protocol !== 'http:')
79
+ return raw;
80
+ if (u.origin === DEXSCREENER_IMAGE_CDN && u.pathname.startsWith('/cms/images/')) {
81
+ u.searchParams.set('width', String(px));
82
+ u.searchParams.set('height', String(px));
83
+ if (!u.searchParams.has('quality'))
84
+ u.searchParams.set('quality', '90');
85
+ return u.toString();
86
+ }
87
+ return raw;
88
+ }
89
+ catch {
90
+ return raw;
91
+ }
92
+ }
93
+ /** Binary SSRF-safe GET with manual, re-validated redirects. Never throws for policy/network failures. */
94
+ export async function fetchBytes(rawUrl, opts = {}) {
95
+ const maxBytes = opts.maxBytes ?? DEFAULTS.maxBytes;
96
+ const timeoutMs = opts.timeoutMs ?? DEFAULTS.timeoutMs;
97
+ const maxRedirects = opts.maxRedirects ?? DEFAULTS.maxRedirects;
98
+ const resolve = opts.resolve ?? defaultResolve;
99
+ const transport = opts.requestImpl ?? pinnedBytesTransport(resolve);
100
+ const started = Date.now();
101
+ const ctl = new AbortController();
102
+ const timer = setTimeout(() => ctl.abort(new Error('timed out')), timeoutMs);
103
+ timer.unref?.();
104
+ const onOuter = () => ctl.abort(opts.signal?.reason ?? new Error('aborted'));
105
+ if (opts.signal) {
106
+ if (opts.signal.aborted)
107
+ onOuter();
108
+ else
109
+ opts.signal.addEventListener('abort', onOuter, { once: true });
110
+ }
111
+ const redirects = [];
112
+ const fail = (reason, status = 0, finalUrl = rawUrl) => ({ ok: false, status, finalUrl, contentType: null, bytes: new Uint8Array(0), redirects, reason, elapsedMs: Date.now() - started });
113
+ try {
114
+ let current = rawUrl;
115
+ for (let hop = 0; hop <= maxRedirects; hop++) {
116
+ let u;
117
+ try {
118
+ u = await assertPublicUrl(current, resolve);
119
+ }
120
+ catch (e) {
121
+ return fail(e instanceof SsrfError ? e.reason : String(e));
122
+ }
123
+ if (ctl.signal.aborted)
124
+ return fail('timed out', 0, current);
125
+ let res;
126
+ try {
127
+ res = await transport(u, { headers: { 'user-agent': opts.userAgent ?? IMAGE_USER_AGENT, accept: opts.accept ?? IMAGE_ACCEPT, 'accept-encoding': 'identity' }, maxBytes, signal: ctl.signal });
128
+ }
129
+ catch (e) {
130
+ return fail(ctl.signal.aborted ? 'timed out' : `fetch failed: ${e.message}`, 0, current);
131
+ }
132
+ if (res.status >= 300 && res.status < 400) {
133
+ const loc = res.headers.location;
134
+ if (!loc)
135
+ return fail(`${res.status} redirect without location`, res.status, current);
136
+ if (hop >= maxRedirects)
137
+ return fail(`too many redirects (> ${maxRedirects})`, res.status, current);
138
+ let next;
139
+ try {
140
+ next = new URL(loc, u).toString();
141
+ }
142
+ catch {
143
+ return fail('bad redirect location', res.status, current);
144
+ }
145
+ redirects.push(u.toString());
146
+ current = next;
147
+ continue;
148
+ }
149
+ const ct = res.headers['content-type'] ?? null;
150
+ if (res.status < 200 || res.status >= 300)
151
+ return fail(`HTTP ${res.status}`, res.status, u.toString());
152
+ if (res.body.length > maxBytes)
153
+ return fail(`body over the ${maxBytes} byte cap`, res.status, u.toString());
154
+ return { ok: true, status: res.status, finalUrl: u.toString(), contentType: ct, bytes: res.body, redirects, reason: null, elapsedMs: Date.now() - started };
155
+ }
156
+ return fail(`too many redirects (> ${maxRedirects})`);
157
+ }
158
+ finally {
159
+ clearTimeout(timer);
160
+ opts.signal?.removeEventListener('abort', onOuter);
161
+ }
162
+ }
@@ -0,0 +1,214 @@
1
+ import { DEFAULT_LIMITS, ImageDecodeError, checkDims } from "./types.js";
2
+ function readSubBlocks(b, pos, cap) {
3
+ const parts = [];
4
+ let total = 0;
5
+ for (;;) {
6
+ if (pos >= b.length)
7
+ throw new ImageDecodeError('gif: truncated sub-blocks');
8
+ const n = b[pos];
9
+ pos += 1;
10
+ if (n === 0)
11
+ break;
12
+ if (pos + n > b.length)
13
+ throw new ImageDecodeError('gif: truncated sub-block');
14
+ parts.push(b.subarray(pos, pos + n));
15
+ total += n;
16
+ if (total > cap)
17
+ throw new ImageDecodeError('gif: data over the byte cap');
18
+ pos += n;
19
+ }
20
+ const data = new Uint8Array(total);
21
+ let o = 0;
22
+ for (const p of parts) {
23
+ data.set(p, o);
24
+ o += p.length;
25
+ }
26
+ return { data, next: pos };
27
+ }
28
+ /** LZW decode `data` into `npix` palette indices. */
29
+ function lzw(data, minCodeSize, npix) {
30
+ if (minCodeSize < 2 || minCodeSize > 11)
31
+ throw new ImageDecodeError('gif: bad LZW code size');
32
+ const out = new Uint8Array(npix);
33
+ const clear = 1 << minCodeSize;
34
+ const eoi = clear + 1;
35
+ const MAX = 4096;
36
+ const prefix = new Int32Array(MAX);
37
+ const suffix = new Uint8Array(MAX);
38
+ const lengths = new Uint16Array(MAX);
39
+ for (let i = 0; i < clear; i++) {
40
+ prefix[i] = -1;
41
+ suffix[i] = i;
42
+ lengths[i] = 1;
43
+ }
44
+ let codeSize = minCodeSize + 1;
45
+ let next = eoi + 1;
46
+ let prev = -1;
47
+ let bitBuf = 0, bitCnt = 0, pos = 0, op = 0;
48
+ const stack = new Uint8Array(MAX + 1);
49
+ while (op < npix) {
50
+ while (bitCnt < codeSize) {
51
+ if (pos >= data.length)
52
+ return out; // stream ended early: keep what we have
53
+ bitBuf |= data[pos++] << bitCnt;
54
+ bitCnt += 8;
55
+ }
56
+ const code = bitBuf & ((1 << codeSize) - 1);
57
+ bitBuf >>>= codeSize;
58
+ bitCnt -= codeSize;
59
+ if (code === clear) {
60
+ codeSize = minCodeSize + 1;
61
+ next = eoi + 1;
62
+ prev = -1;
63
+ continue;
64
+ }
65
+ if (code === eoi)
66
+ break;
67
+ let cur = code;
68
+ let first;
69
+ if (code < next) {
70
+ // known code: emit its string
71
+ let n = lengths[code];
72
+ let c = code;
73
+ for (let i = n - 1; i >= 0; i--) {
74
+ stack[i] = suffix[c];
75
+ c = prefix[c];
76
+ }
77
+ first = stack[0];
78
+ for (let i = 0; i < n && op < npix; i++)
79
+ out[op++] = stack[i];
80
+ }
81
+ else if (code === next && prev >= 0) {
82
+ // KwKwK case: prev string + its first char
83
+ const n = lengths[prev];
84
+ let c = prev;
85
+ for (let i = n - 1; i >= 0; i--) {
86
+ stack[i] = suffix[c];
87
+ c = prefix[c];
88
+ }
89
+ stack[n] = stack[0];
90
+ first = stack[0];
91
+ for (let i = 0; i <= n && op < npix; i++)
92
+ out[op++] = stack[i];
93
+ }
94
+ else {
95
+ throw new ImageDecodeError('gif: corrupt LZW stream');
96
+ }
97
+ if (prev >= 0 && next < MAX) {
98
+ prefix[next] = prev;
99
+ suffix[next] = first;
100
+ lengths[next] = lengths[prev] + 1;
101
+ next += 1;
102
+ if (next === (1 << codeSize) && codeSize < 12)
103
+ codeSize += 1;
104
+ }
105
+ prev = cur;
106
+ }
107
+ return out;
108
+ }
109
+ export function decodeGif(b, limits = DEFAULT_LIMITS) {
110
+ if (b.length < 13 || String.fromCharCode(b[0], b[1], b[2]) !== 'GIF')
111
+ throw new ImageDecodeError('gif: bad signature');
112
+ const w = b[6] | (b[7] << 8), h = b[8] | (b[9] << 8);
113
+ checkDims(w, h, limits, 'gif');
114
+ const flags = b[10];
115
+ const bgIndex = b[11];
116
+ let pos = 13;
117
+ let gct = null;
118
+ if (flags & 0x80) {
119
+ const n = 3 * (1 << ((flags & 7) + 1));
120
+ if (pos + n > b.length)
121
+ throw new ImageDecodeError('gif: truncated palette');
122
+ gct = b.subarray(pos, pos + n);
123
+ pos += n;
124
+ }
125
+ let transparent = -1;
126
+ const out = new Uint8Array(w * h * 4); // transparent canvas
127
+ void bgIndex;
128
+ while (pos < b.length) {
129
+ const tag = b[pos];
130
+ if (tag === 0x3b)
131
+ break; // trailer
132
+ if (tag === 0x21) { // extension
133
+ const label = b[pos + 1];
134
+ pos += 2;
135
+ if (label === 0xf9) { // GCE: size, flags, delay(2), transparent index, 0
136
+ const size = b[pos];
137
+ if (size >= 4 && pos + 5 < b.length) {
138
+ if (b[pos + 1] & 1)
139
+ transparent = b[pos + 4];
140
+ }
141
+ pos += 1 + size;
142
+ if (b[pos] !== 0) {
143
+ pos = readSubBlocks(b, pos, limits.maxBytes).next;
144
+ }
145
+ else
146
+ pos += 1;
147
+ }
148
+ else {
149
+ pos = readSubBlocks(b, pos, limits.maxBytes).next;
150
+ }
151
+ continue;
152
+ }
153
+ if (tag === 0x2c) { // image descriptor
154
+ if (pos + 10 > b.length)
155
+ throw new ImageDecodeError('gif: truncated image descriptor');
156
+ const left = b[pos + 1] | (b[pos + 2] << 8), top = b[pos + 3] | (b[pos + 4] << 8);
157
+ const iw = b[pos + 5] | (b[pos + 6] << 8), ih = b[pos + 7] | (b[pos + 8] << 8);
158
+ const iflags = b[pos + 9];
159
+ pos += 10;
160
+ checkDims(iw, ih, limits, 'gif frame');
161
+ let pal = gct;
162
+ if (iflags & 0x80) {
163
+ const n = 3 * (1 << ((iflags & 7) + 1));
164
+ if (pos + n > b.length)
165
+ throw new ImageDecodeError('gif: truncated local palette');
166
+ pal = b.subarray(pos, pos + n);
167
+ pos += n;
168
+ }
169
+ if (!pal)
170
+ throw new ImageDecodeError('gif: no palette');
171
+ const minCode = b[pos];
172
+ pos += 1;
173
+ const { data, next } = readSubBlocks(b, pos, limits.maxBytes);
174
+ pos = next;
175
+ const idx = lzw(data, minCode, iw * ih);
176
+ const interlaced = (iflags & 0x40) !== 0;
177
+ const rowOrder = [];
178
+ if (interlaced) {
179
+ for (let y = 0; y < ih; y += 8)
180
+ rowOrder.push(y);
181
+ for (let y = 4; y < ih; y += 8)
182
+ rowOrder.push(y);
183
+ for (let y = 2; y < ih; y += 4)
184
+ rowOrder.push(y);
185
+ for (let y = 1; y < ih; y += 2)
186
+ rowOrder.push(y);
187
+ }
188
+ for (let r = 0; r < ih; r++) {
189
+ const y = interlaced ? rowOrder[r] : r;
190
+ const dy = top + y;
191
+ if (dy < 0 || dy >= h)
192
+ continue;
193
+ for (let x = 0; x < iw; x++) {
194
+ const dx = left + x;
195
+ if (dx < 0 || dx >= w)
196
+ continue;
197
+ const ci = idx[r * iw + x];
198
+ if (ci === transparent)
199
+ continue;
200
+ if (ci * 3 + 2 >= pal.length)
201
+ continue; // out-of-palette index: leave transparent
202
+ const o = (dy * w + dx) * 4;
203
+ out[o] = pal[ci * 3];
204
+ out[o + 1] = pal[ci * 3 + 1];
205
+ out[o + 2] = pal[ci * 3 + 2];
206
+ out[o + 3] = 255;
207
+ }
208
+ }
209
+ return { w, h, rgba: out }; // first frame only
210
+ }
211
+ throw new ImageDecodeError(`gif: unknown block 0x${tag.toString(16)}`);
212
+ }
213
+ throw new ImageDecodeError('gif: no image data');
214
+ }
@@ -0,0 +1,10 @@
1
+ // @dexterm/data — coin images (task-1326): pure-TS decoders, SSRF-safe binary fetch, disk LRU, service.
2
+ export * from "./types.js";
3
+ export { decodePng, encodePng, isPng } from "./png.js";
4
+ export { decodeJpeg } from "./jpeg.js";
5
+ export { decodeGif } from "./gif.js";
6
+ export { decodeImage, decodeThumbnail, resample, fitWithin } from "./decode.js";
7
+ export { fetchBytes, thumbnailUrl, pinnedBytesTransport, DEXSCREENER_IMAGE_CDN, IMAGE_ACCEPT, IMAGE_USER_AGENT } from "./fetch.js";
8
+ export { ImageDiskCache, imageCacheKey } from "./cache.js";
9
+ export { ImageService } from "./service.js";
10
+ export { runDecodeJob } from "./decode-worker.js";