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,177 @@
1
+ // @dexterm/agents — the tool executor and the live feed (task-1345, tier 2).
2
+ //
3
+ // The bus gates every call (identity, tier, closed schema, rate/quota) and ONLY THEN hands a read or
4
+ // navigation call to an AgentExecutor. Keeping execution behind an interface means the running TUI can
5
+ // back it with the real @dexterm/data layer (via the MCP tools) while tests and the fixture demo back it
6
+ // with deterministic data and never touch the network. The executor never sees a token, never signs, and
7
+ // cannot widen a capability — it only runs the name the gate already approved.
8
+ import { firewallText } from '../../data/src/index.js';
9
+ /** Topics an agent may subscribe to. Each pushed event is server-authored and firewalled. */
10
+ export const TOPICS = ['new_pairs', 'trades', 'alerts', 'screen'];
11
+ export function isTopic(x) { return typeof x === 'string' && TOPICS.includes(x); }
12
+ // ---------------------------------------------------------------------------------------------------
13
+ // Outbound scrub (task-1345, tier 3): a belt-and-braces net over EVERY string an agent receives. The
14
+ // executor/source already firewalls untrusted token text (symbols, names, risk strings) into <untrusted>
15
+ // blocks, but the bus strips terminal-escape / C0-C1 / bidi / zero-width characters from every string
16
+ // leaf of a tool result and a pushed event too, so a buggy or compromised source can never carry an ANSI
17
+ // clear-screen, a right-to-left override, or a zero-width-join obfuscation into the agent (or, if the
18
+ // agent echoes it, into a terminal). Strip-only: it never truncates or re-wraps legitimate data.
19
+ // First remove whole ANSI escape sequences (CSI `ESC [ … letter` and OSC `ESC ] … BEL/ST`), so no printable
20
+ // payload like `[2J` is left behind, then strip any remaining C0/C1 control, bidi-override and zero-width.
21
+ const ANSI_SEQ = /\u001b\[[0-?]*[ -/]*[@-~]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b[@-Z\\-_]/g;
22
+ const UNSAFE_CHARS = /[\u0000-\u001F\u007F-\u009F​-‏‪-‮⁦-⁩]/g;
23
+ export function stripControl(s) { return s.replace(ANSI_SEQ, '').replace(UNSAFE_CHARS, ''); }
24
+ export function scrubStrings(v, depth = 0) {
25
+ if (depth > 8)
26
+ return v;
27
+ if (typeof v === 'string')
28
+ return stripControl(v);
29
+ if (Array.isArray(v))
30
+ return v.map((x) => scrubStrings(x, depth + 1));
31
+ if (v && typeof v === 'object') {
32
+ const out = {};
33
+ for (const [k, val] of Object.entries(v))
34
+ out[stripControl(k).slice(0, 128)] = scrubStrings(val, depth + 1);
35
+ return out;
36
+ }
37
+ return v;
38
+ }
39
+ // ---------------------------------------------------------------------------------------------------
40
+ // Fixtures: deterministic, offline, used by the bus tests, `dexterm agent serve --fixtures` and the
41
+ // worked demo. Numbers are seeded and stable; symbols/names go through the firewall like live data.
42
+ const FIXTURE_ROWS = [
43
+ { mint: 'FixtMintAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', symbol: 'TROLL', name: 'Troll Cat', launchpad: 'pump.fun', ageSec: 240, priceUsd: 0.00042, mcap: 420_000, liquidityUsd: 38_000, volH1: 120_000, holderCount: 410, organicScore: 62, freeze: false, mintAuth: false, lpLockedPct: 100, top10Pct: 18, rugScore: 12 },
44
+ { mint: 'FixtMintBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', symbol: 'MOONER', name: 'Mooner', launchpad: 'raydium', ageSec: 5400, priceUsd: 0.0131, mcap: 2_100_000, liquidityUsd: 190_000, volH1: 540_000, holderCount: 1900, organicScore: 71, freeze: false, mintAuth: false, lpLockedPct: 100, top10Pct: 22, rugScore: 8 },
45
+ { mint: 'FixtMintCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', symbol: 'RUGME', name: 'Totally Safe Inu 🚀', launchpad: 'pump.fun', ageSec: 90, priceUsd: 0.0000008, mcap: 15_000, liquidityUsd: 3200, volH1: 60_000, holderCount: 42, organicScore: 9, freeze: true, mintAuth: true, lpLockedPct: 0, top10Pct: 71, rugScore: 86 },
46
+ { mint: 'FixtMintDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD', symbol: 'GIGA', name: 'Gigabrain', launchpad: 'meteora', ageSec: 12_600, priceUsd: 0.21, mcap: 8_800_000, liquidityUsd: 760_000, volH1: 1_200_000, holderCount: 6100, organicScore: 80, freeze: false, mintAuth: false, lpLockedPct: 100, top10Pct: 14, rugScore: 5 },
47
+ ];
48
+ function fw(s) { return firewallText(s, { max: 48 }).text; }
49
+ const now = () => Date.now();
50
+ export class FixtureExecutor {
51
+ /** agent_note writes land here so the fixture demo / tests can read them back. */
52
+ notes = [];
53
+ /** navigation requests land here (the "app" would apply them). */
54
+ navigation = [];
55
+ async exec(session, tool, args) {
56
+ const byMint = (m) => FIXTURE_ROWS.find((r) => r.mint === m);
57
+ switch (tool) {
58
+ case 'screen_state':
59
+ return { screen: 'screener', filter: '', sort: 'vol24:desc', selected: { mint: FIXTURE_ROWS[0].mint, symbol: FIXTURE_ROWS[0].symbol }, visibleMints: FIXTURE_ROWS.map((r) => r.mint), watchlist: [], actions: { enabled: true, mints: FIXTURE_ROWS.map((r) => r.mint), feeds: ['new', 'trending', 'all'], sortKeys: ['vol24', 'mcap', 'age', 'liq'] }, now: now() };
60
+ case 'screener_query': {
61
+ const preset = args.preset ?? 'trending';
62
+ const rows = preset === 'new' ? [...FIXTURE_ROWS].sort((a, b) => a.ageSec - b.ageSec) : [...FIXTURE_ROWS].sort((a, b) => b.volH1 - a.volH1);
63
+ const limit = Math.min(Number(args.limit ?? 20), 50);
64
+ return { preset, now: now(), rows: rows.slice(0, limit).map((r) => ({ mint: r.mint, symbol: fw(r.symbol), launchpad: r.launchpad, tokenAgeSec: r.ageSec, priceUsd: r.priceUsd, mcap: r.mcap, liquidityUsd: r.liquidityUsd, volH1: r.volH1, holderCount: r.holderCount, organicScore: r.organicScore, audit: { mintAuthority: r.mintAuth, freezeAuthority: r.freeze, top10Pct: r.top10Pct, lpLockedPct: r.lpLockedPct, rugScore: r.rugScore }, source: 'fixtures', ageSec: 3 })) };
65
+ }
66
+ case 'get_token': {
67
+ const r = byMint(args.mint) ?? FIXTURE_ROWS.find((x) => x.symbol.toUpperCase() === String(args.symbol ?? '').replace(/^\$/, '').toUpperCase());
68
+ if (!r)
69
+ return { error: 'unknown token (fixtures)' };
70
+ return { token: { mint: r.mint, symbol: fw(r.symbol), name: fw(r.name), launchpad: r.launchpad }, pair: { priceUsd: r.priceUsd, mcap: r.mcap, liquidityUsd: r.liquidityUsd, source: 'fixtures', ageSec: 3 }, stats: { holderCount: r.holderCount, organicScore: r.organicScore }, audit: { mintAuthority: r.mintAuth, freezeAuthority: r.freeze, top10Pct: r.top10Pct, lpLockedPct: r.lpLockedPct, rugScore: r.rugScore }, now: now() };
71
+ }
72
+ case 'rugcheck': {
73
+ const r = byMint(args.mint);
74
+ if (!r)
75
+ return { error: 'unknown token (fixtures)' };
76
+ const risks = [];
77
+ if (r.freeze)
78
+ risks.push({ name: 'freeze authority enabled', level: 'danger' });
79
+ if (r.mintAuth)
80
+ risks.push({ name: 'mint authority enabled', level: 'danger' });
81
+ if (r.lpLockedPct < 100)
82
+ risks.push({ name: 'LP not fully locked', level: 'warn' });
83
+ if (r.top10Pct > 40)
84
+ risks.push({ name: 'top-10 holders concentrated', level: 'warn' });
85
+ return { mint: r.mint, scoreNormalised: r.rugScore, lpLockedPct: r.lpLockedPct, tokenProgram: 'Tokenkeg', risks, source: 'fixtures', ageSec: 3 };
86
+ }
87
+ case 'get_dev_history': {
88
+ const r = byMint(args.mint);
89
+ if (!r)
90
+ return { error: 'unknown token (fixtures)' };
91
+ return { mint: r.mint, creator: 'FixtDev1111111111111111111111111111111111111', creators: ['FixtDev1111111111111111111111111111111111111'], devHistory: null, snipers: null, bundledPct: null, tier: 'needs-key', note: 'prior-launch forensics need a key; keyless tier (fixtures)' };
92
+ }
93
+ case 'get_holders': {
94
+ const r = byMint(args.mint);
95
+ if (!r)
96
+ return { error: 'unknown token (fixtures)' };
97
+ return { mint: r.mint, top10PctExLp: r.top10Pct, holderCount: r.holderCount, holders: [], supply: 1e9, decimals: 6, source: 'fixtures', ageSec: 3 };
98
+ }
99
+ case 'get_trades': {
100
+ const r = byMint(args.mint);
101
+ if (!r)
102
+ return { error: 'unknown token (fixtures)' };
103
+ const washy = r.rugScore > 50;
104
+ return { mint: r.mint, window: args.window ?? 'h1', count: 180, buys: 100, sells: 80, buyUsd: Math.round(r.volH1 * 0.55), sellUsd: Math.round(r.volH1 * 0.45), distinctWallets: washy ? 6 : 140, top2WalletVolumeSharePct: washy ? 68 : 9, source: 'fixtures', ageSec: 3 };
105
+ }
106
+ case 'get_candles': {
107
+ const r = byMint(args.mint);
108
+ if (!r)
109
+ return { error: 'unknown token (fixtures)' };
110
+ return { mint: r.mint, interval: args.interval ?? '1h', bars: 24, summary: { first: r.priceUsd * 0.8, last: r.priceUsd, high: r.priceUsd * 1.2, low: r.priceUsd * 0.7, changePct: 25, drawdownFromHighPct: -16.7, vwap: r.priceUsd * 0.95 }, source: 'fixtures', ageSec: 3 };
111
+ }
112
+ case 'get_dossier': {
113
+ const r = byMint(args.mint) ?? FIXTURE_ROWS.find((x) => x.symbol.toUpperCase() === String(args.symbol ?? '').replace(/^\$/, '').toUpperCase());
114
+ if (!r)
115
+ return { error: 'unknown token (fixtures)' };
116
+ const [token, rug, trades] = await Promise.all([this.exec(session, 'get_token', { mint: r.mint }), this.exec(session, 'rugcheck', { mint: r.mint }), this.exec(session, 'get_trades', { mint: r.mint, window: 'h1' })]);
117
+ return { mint: r.mint, interval: args.interval ?? '1h', token, rugcheck: rug, trades, availability: { market: true, security: true, tape: true, canVerdict: true } };
118
+ }
119
+ case 'open_token':
120
+ case 'show_in_screener':
121
+ case 'switch_feed':
122
+ this.navigation.push({ agent: session.agentName, tool, args });
123
+ return { ok: true, note: 'The app applies this and shows "<agent> did X". You cannot confirm, size, or send a trade.' };
124
+ case 'agent_note': {
125
+ const text = fw(String(args.text ?? ''));
126
+ const tag = args.tag ? fw(String(args.tag)) : null;
127
+ this.notes.push({ agent: session.agentName, mint: String(args.mint), text, tag, t: now() });
128
+ return { ok: true, note: 'note attached and labelled with your agent name', text, tag };
129
+ }
130
+ default:
131
+ return { error: `fixture executor has no handler for ${tool}` };
132
+ }
133
+ }
134
+ }
135
+ /** A feed that tests/the demo drive by calling emit(); an optional timer makes it tick on its own. */
136
+ export class FixtureFeed {
137
+ listeners = new Set();
138
+ seq = 0;
139
+ timer = null;
140
+ on(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
141
+ snapshot(topic) {
142
+ if (topic === 'screen')
143
+ return { screen: 'screener', filter: '', selected: FIXTURE_ROWS[0].mint };
144
+ return null;
145
+ }
146
+ emit(topic, data) {
147
+ const ev = { topic, seq: ++this.seq, t: now(), data };
148
+ for (const l of this.listeners) {
149
+ try {
150
+ l(ev);
151
+ }
152
+ catch { /* one bad listener never stops the fan-out */ }
153
+ }
154
+ return ev;
155
+ }
156
+ /** Start a deterministic cycle of events (for the demo). Unref'd so it never keeps the process alive. */
157
+ start(intervalMs = 1500) {
158
+ if (this.timer)
159
+ return;
160
+ let i = 0;
161
+ this.timer = setInterval(() => {
162
+ const r = FIXTURE_ROWS[i % FIXTURE_ROWS.length];
163
+ i++;
164
+ if (i % 3 === 0)
165
+ this.emit('new_pairs', { mint: r.mint, symbol: fw(r.symbol), launchpad: r.launchpad, ageSec: 5 });
166
+ else if (i % 3 === 1)
167
+ this.emit('trades', { mint: r.mint, side: i % 2 ? 'buy' : 'sell', usd: 1200 + i * 10, priceUsd: r.priceUsd });
168
+ else
169
+ this.emit('alerts', { mint: r.mint, kind: 'volume_spike', note: fw(`${r.symbol} volume spiked`) });
170
+ }, intervalMs);
171
+ this.timer.unref?.();
172
+ }
173
+ stop() { if (this.timer) {
174
+ clearInterval(this.timer);
175
+ this.timer = null;
176
+ } }
177
+ }
@@ -0,0 +1,176 @@
1
+ // @dexterm/agents — the gate (task-1345). ONE function every agent call passes, on every transport
2
+ // (bus method or bridged MCP tool call), before anything is looked up or executed:
3
+ //
4
+ // 1. the session must be open (not kicked) and its agent not paused;
5
+ // 2. the tool name must not be on the never-list (arm / sign / caps / gate / wallet / mandate / keys …)
6
+ // and must exist in the tier table — anything else is `unknown-tool`;
7
+ // 3. the agent's GRANTED tier must reach the tool's tier (the tier comes from the registry at hello,
8
+ // never from the request; a request field named `tier`, `agent` or `session` is refused as spoofing);
9
+ // 4. the arguments must validate against the tool's closed schema and fit the size cap;
10
+ // 5. the per-agent rate limits and quotas must have room (reads/min, navigation per 10 s, proposals/h);
11
+ // 6. a proposal must carry an idempotency key.
12
+ //
13
+ // Every refusal is written to the audit log with a short reason. Sustained refusals trip a per-agent
14
+ // breaker (`flood`): the agent is auto-paused for FLOOD_PAUSE_MS and the roster shows why.
15
+ import { ACTION_RATE } from '../../data/src/index.js';
16
+ import { tierAtLeast, tierFor, neverAllowed, PROPOSAL_TOOLS, NAVIGATION_TOOLS } from "./tiers.js";
17
+ import { validateArgs } from "./schema.js";
18
+ export const DEFAULT_LIMITS = Object.freeze({
19
+ readsPerMin: 120,
20
+ proposalsPerHour: 20,
21
+ navPer10s: ACTION_RATE.max,
22
+ maxArgsBytes: 16 * 1024,
23
+ maxTopics: 8,
24
+ connections: 2,
25
+ });
26
+ /** Refusals in FLOOD_WINDOW_MS that trip the breaker, and how long the auto-pause lasts. */
27
+ export const FLOOD_REFUSALS = 40;
28
+ export const FLOOD_WINDOW_MS = 60_000;
29
+ export const FLOOD_PAUSE_MS = 5 * 60_000;
30
+ /** Fields an agent must never be able to set on a request: identity and capability come from the server. */
31
+ const SPOOF_FIELDS = ['agent', 'agent_id', 'agentId', 'agent_name', 'agentName', 'tier', 'session', 'session_id', 'sessionId', 'token', 'as', 'on_behalf_of', 'impersonate'];
32
+ export class AgentGate {
33
+ audit;
34
+ now;
35
+ base;
36
+ onFlood;
37
+ isPausedExt;
38
+ reads = new Map();
39
+ nav = new Map();
40
+ proposals = new Map();
41
+ refusals = new Map();
42
+ floodUntil = new Map();
43
+ perAgent = new Map();
44
+ constructor(opts) {
45
+ this.audit = opts.audit;
46
+ this.now = opts.now ?? (() => Date.now());
47
+ this.base = { ...DEFAULT_LIMITS, ...(opts.limits ?? {}) };
48
+ this.onFlood = opts.onFlood;
49
+ this.isPausedExt = opts.isPaused ?? (() => false);
50
+ }
51
+ /** Per-agent overrides from the registry (`limits` in agents.json). */
52
+ setAgentLimits(agentId, l) {
53
+ if (!l) {
54
+ this.perAgent.delete(agentId);
55
+ return;
56
+ }
57
+ const o = {};
58
+ if (l.readsPerMin !== undefined)
59
+ o.readsPerMin = l.readsPerMin;
60
+ if (l.proposalsPerHour !== undefined)
61
+ o.proposalsPerHour = l.proposalsPerHour;
62
+ if (l.connections !== undefined)
63
+ o.connections = l.connections;
64
+ this.perAgent.set(agentId, o);
65
+ }
66
+ limitsFor(agentId) { return { ...this.base, ...(this.perAgent.get(agentId) ?? {}) }; }
67
+ /** Current usage for the roster ("12/min", "3/20 proposals this hour"). */
68
+ usage(agentId) {
69
+ const t = this.now();
70
+ const count = (m, win) => { const w = m.get(agentId); if (!w)
71
+ return 0; return w.times.filter((x) => t - x < win).length; };
72
+ const until = this.floodUntil.get(agentId) ?? null;
73
+ return { readsPerMin: count(this.reads, 60_000), proposalsPerHour: count(this.proposals, 3_600_000), navPer10s: count(this.nav, ACTION_RATE.windowMs), floodPausedUntil: until !== null && until > t ? until : null, limits: this.limitsFor(agentId) };
74
+ }
75
+ /** The gate. Pure apart from the windows and the audit row it writes. */
76
+ check(session, req) {
77
+ const t = this.now();
78
+ const refuse = (code, error) => {
79
+ this.audit.append({ agentId: session.agentId, agentName: session.agentName, session: session.id, kind: 'refused', tool: String(req.tool).slice(0, 64), detail: `${code}: ${error}`, ok: false });
80
+ this.noteRefusal(session, t);
81
+ return { ok: false, code, error };
82
+ };
83
+ if (session.closed)
84
+ return refuse('session-closed', `session closed${session.closedReason ? ` (${session.closedReason})` : ''}`);
85
+ const fu = this.floodUntil.get(session.agentId);
86
+ if (fu !== undefined && fu > t)
87
+ return refuse('flood-paused', `auto-paused after repeated refusals; resumes in ${Math.ceil((fu - t) / 1000)} s or when the user resumes it`);
88
+ if (this.isPausedExt(session.agentId))
89
+ return refuse('agent-paused', 'the user paused this agent');
90
+ const name = req.tool;
91
+ if (typeof name !== 'string' || neverAllowed(name))
92
+ return refuse('never', 'no agent may do that at any tier');
93
+ const need = tierFor(name);
94
+ if (need === null)
95
+ return refuse('unknown-tool', `unknown tool ${JSON.stringify(String(name).slice(0, 40))}`);
96
+ if (!tierAtLeast(session.tier, need))
97
+ return refuse('tier', `${name} needs tier ${need}; this agent is ${session.tier}`);
98
+ // spoofing: identity / capability fields in the arguments are never honoured
99
+ const args = req.args === undefined ? {} : req.args;
100
+ if (typeof args === 'object' && args !== null && !Array.isArray(args)) {
101
+ for (const f of SPOOF_FIELDS)
102
+ if (Object.hasOwn(args, f))
103
+ return refuse('spoof', `argument "${f}" is not yours to set: identity and tier come from the server`);
104
+ }
105
+ let bytes = 0;
106
+ try {
107
+ bytes = Buffer.byteLength(JSON.stringify(args));
108
+ }
109
+ catch {
110
+ return refuse('bad-args', 'arguments are not serialisable');
111
+ }
112
+ if (bytes > this.limitsFor(session.agentId).maxArgsBytes)
113
+ return refuse('too-large', `arguments exceed ${this.limitsFor(session.agentId).maxArgsBytes} bytes`);
114
+ const schemaErr = validateArgs(req.schema, args);
115
+ if (schemaErr)
116
+ return refuse('bad-args', schemaErr);
117
+ // quotas
118
+ const limits = this.limitsFor(session.agentId);
119
+ if (PROPOSAL_TOOLS.has(name)) {
120
+ const key = args.idempotency_key;
121
+ if (typeof key !== 'string' || key.length < 8 || key.length > 64 || !/^[A-Za-z0-9_.:-]+$/.test(key))
122
+ return refuse('idempotency-key', 'proposals need idempotency_key (8–64 chars: letters, digits, _ . : -)');
123
+ if (!this.take(this.proposals, session.agentId, 3_600_000, limits.proposalsPerHour, t))
124
+ return refuse('quota', `proposal quota reached (${limits.proposalsPerHour}/hour)`);
125
+ }
126
+ else if (NAVIGATION_TOOLS.has(name)) {
127
+ if (!this.take(this.nav, session.agentId, ACTION_RATE.windowMs, limits.navPer10s, t))
128
+ return refuse('rate-limited', `navigation is limited to ${limits.navPer10s} per ${ACTION_RATE.windowMs / 1000} s`);
129
+ }
130
+ else if (name !== 'subscribe' && name !== 'unsubscribe') {
131
+ if (!this.take(this.reads, session.agentId, 60_000, limits.readsPerMin, t))
132
+ return refuse('rate-limited', `read rate limit reached (${limits.readsPerMin}/min)`);
133
+ }
134
+ session.lastActivity = t;
135
+ return { ok: true, tool: name, tier: need };
136
+ }
137
+ /** Record a successful call (the owner calls this after executing so the detail can name the target). */
138
+ record(session, tool, detail, kind = 'call') {
139
+ this.audit.append({ agentId: session.agentId, agentName: session.agentName, session: session.id, kind, tool, detail, ok: true });
140
+ }
141
+ /** A proposal's quota slot is returned when the proposal turned out to be an idempotent repeat. */
142
+ refundProposal(agentId) { const w = this.proposals.get(agentId); if (w)
143
+ w.times.pop(); }
144
+ /** Clear the breaker (user resume). */
145
+ clearFlood(agentId) { this.floodUntil.delete(agentId); this.refusals.delete(agentId); }
146
+ take(m, agentId, windowMs, max, t) {
147
+ let w = m.get(agentId);
148
+ if (!w) {
149
+ w = { times: [] };
150
+ m.set(agentId, w);
151
+ }
152
+ while (w.times.length && t - w.times[0] >= windowMs)
153
+ w.times.shift();
154
+ if (w.times.length >= max)
155
+ return false;
156
+ w.times.push(t);
157
+ return true;
158
+ }
159
+ noteRefusal(session, t) {
160
+ let w = this.refusals.get(session.agentId);
161
+ if (!w) {
162
+ w = { times: [] };
163
+ this.refusals.set(session.agentId, w);
164
+ }
165
+ while (w.times.length && t - w.times[0] >= FLOOD_WINDOW_MS)
166
+ w.times.shift();
167
+ w.times.push(t);
168
+ if (w.times.length >= FLOOD_REFUSALS && !(this.floodUntil.get(session.agentId) > t)) {
169
+ const until = t + FLOOD_PAUSE_MS;
170
+ this.floodUntil.set(session.agentId, until);
171
+ w.times.length = 0;
172
+ this.audit.append({ agentId: session.agentId, agentName: session.agentName, session: session.id, kind: 'flood', tool: '-', detail: `${FLOOD_REFUSALS} refusals in ${FLOOD_WINDOW_MS / 1000} s: auto-paused ${FLOOD_PAUSE_MS / 60_000} min`, ok: false });
173
+ this.onFlood?.(session.agentId, session.agentName, until);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,179 @@
1
+ // @dexterm/agents — per-agent identity (task-1345).
2
+ //
3
+ // The user registers each agent once (`dexterm agent add NAME --tier T`); the agent gets a bearer token
4
+ // that is shown exactly once and stored only as a salted SHA-256 hash in `<dexterm home>/agents.json`
5
+ // (mode 0600). Every connection (bus `hello`, or a bridged `dexterm mcp` child) presents the name and the
6
+ // token; the registry verifies the hash with a constant-time compare and hands back the GRANTED tier.
7
+ // The agent never states its own tier. Tokens are rotated with `dexterm agent rotate NAME`; a rotated or
8
+ // removed token fails verification at once, so a kicked agent cannot reconnect with the old secret.
9
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
10
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, chmodSync } from 'node:fs';
11
+ import { dirname, join } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { isTier } from "./tiers.js";
14
+ export const AGENT_NAME_RE = /^[a-z][a-z0-9-]{0,23}$/;
15
+ export const TOKEN_PREFIX = 'dxa_';
16
+ /** Hard cap on registered agents (a flood of `agent add` is still bounded). */
17
+ export const MAX_AGENTS = 32;
18
+ export function dextermHome(env = process.env) {
19
+ return env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(homedir(), '.dexterm');
20
+ }
21
+ export function registryPath(env = process.env) {
22
+ return join(dextermHome(env), 'agents.json');
23
+ }
24
+ export function hashToken(salt, token) {
25
+ return createHash('sha256').update(salt + ':' + token, 'utf8').digest('hex');
26
+ }
27
+ function newToken() { return TOKEN_PREFIX + randomBytes(32).toString('base64url'); }
28
+ function newId() { return randomBytes(6).toString('hex'); }
29
+ /** Fail-closed token shape check before any hashing (also what the bus/MCP use to reject junk early). */
30
+ export function looksLikeToken(t) {
31
+ return typeof t === 'string' && t.startsWith(TOKEN_PREFIX) && t.length >= 20 && t.length <= 128 && /^[A-Za-z0-9_-]+$/.test(t.slice(TOKEN_PREFIX.length));
32
+ }
33
+ export class AgentRegistry {
34
+ path;
35
+ now;
36
+ agents = [];
37
+ constructor(opts = {}) {
38
+ this.path = opts.path ?? registryPath(opts.env);
39
+ this.now = opts.now ?? (() => Date.now());
40
+ this.load();
41
+ }
42
+ get file() { return this.path; }
43
+ load() {
44
+ // An in-memory registry keeps whatever add()/rotate() built: re-reading would wipe it (there is no file).
45
+ if (this.path === ':memory:')
46
+ return;
47
+ if (!existsSync(this.path)) {
48
+ this.agents = [];
49
+ return;
50
+ }
51
+ let parsed;
52
+ try {
53
+ parsed = JSON.parse(readFileSync(this.path, 'utf8'));
54
+ }
55
+ catch {
56
+ this.agents = [];
57
+ return;
58
+ }
59
+ const f = parsed;
60
+ const list = Array.isArray(f?.agents) ? f.agents : [];
61
+ // Defensive re-validation: a hand-edited file cannot grant a tier outside the three or a bad name.
62
+ this.agents = list.filter((a) => !!a && typeof a === 'object' && typeof a.id === 'string' && typeof a.name === 'string' && AGENT_NAME_RE.test(a.name)
63
+ && isTier(a.tier) && typeof a.tokenHash === 'string' && typeof a.salt === 'string').map((a) => ({ ...a, paused: a.paused === true, rotatedAt: typeof a.rotatedAt === 'number' ? a.rotatedAt : null }));
64
+ }
65
+ save() {
66
+ if (this.path === ':memory:')
67
+ return;
68
+ mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
69
+ const tmp = `${this.path}.${process.pid}.tmp`;
70
+ const body = { version: 1, agents: this.agents };
71
+ writeFileSync(tmp, JSON.stringify(body, null, 2) + '\n', { mode: 0o600 });
72
+ try {
73
+ chmodSync(tmp, 0o600);
74
+ }
75
+ catch { /* best effort on odd filesystems */ }
76
+ renameSync(tmp, this.path);
77
+ }
78
+ list() { return this.agents.map((a) => ({ ...a })); }
79
+ get(name) { return this.agents.find((a) => a.name === name); }
80
+ byId(id) { return this.agents.find((a) => a.id === id); }
81
+ /** Register an agent. Returns the record and the ONE-TIME plaintext token (never stored). */
82
+ add(name, tier, opts = {}) {
83
+ if (!AGENT_NAME_RE.test(name))
84
+ throw new Error(`agent name must match ${AGENT_NAME_RE} (lowercase letters, digits, dashes; ≤ 24 chars)`);
85
+ if (!isTier(tier))
86
+ throw new Error('tier must be observe, annotate or propose');
87
+ if (this.get(name))
88
+ throw new Error(`agent "${name}" already exists (rotate its token with: dexterm agent rotate ${name})`);
89
+ if (this.agents.length >= MAX_AGENTS)
90
+ throw new Error(`at most ${MAX_AGENTS} agents can be registered`);
91
+ const token = newToken();
92
+ const salt = randomBytes(12).toString('hex');
93
+ const agent = { id: newId(), name, tier, tokenHash: hashToken(salt, token), salt, createdAt: this.now(), paused: false, rotatedAt: null };
94
+ if (opts.note)
95
+ agent.note = String(opts.note).slice(0, 80);
96
+ if (opts.limits)
97
+ agent.limits = sanitizeLimits(opts.limits);
98
+ this.agents.push(agent);
99
+ this.save();
100
+ return { agent: { ...agent }, token };
101
+ }
102
+ remove(name) {
103
+ const n = this.agents.length;
104
+ this.agents = this.agents.filter((a) => a.name !== name);
105
+ if (this.agents.length === n)
106
+ return false;
107
+ this.save();
108
+ return true;
109
+ }
110
+ /** Replace the token; the old one fails from this moment. Returns the new one-time token. */
111
+ rotate(name) {
112
+ const a = this.get(name);
113
+ if (!a)
114
+ throw new Error(`no agent "${name}"`);
115
+ const token = newToken();
116
+ a.salt = randomBytes(12).toString('hex');
117
+ a.tokenHash = hashToken(a.salt, token);
118
+ a.rotatedAt = this.now();
119
+ this.save();
120
+ return token;
121
+ }
122
+ setTier(name, tier) {
123
+ const a = this.get(name);
124
+ if (!a)
125
+ throw new Error(`no agent "${name}"`);
126
+ if (!isTier(tier))
127
+ throw new Error('tier must be observe, annotate or propose');
128
+ a.tier = tier;
129
+ this.save();
130
+ }
131
+ setPaused(name, paused) {
132
+ const a = this.get(name);
133
+ if (!a)
134
+ throw new Error(`no agent "${name}"`);
135
+ a.paused = paused;
136
+ this.save();
137
+ }
138
+ setLimits(name, limits) {
139
+ const a = this.get(name);
140
+ if (!a)
141
+ throw new Error(`no agent "${name}"`);
142
+ a.limits = sanitizeLimits(limits);
143
+ this.save();
144
+ }
145
+ /**
146
+ * Verify a presented name + token. Constant-time on the hash; the same `null` for an unknown name,
147
+ * a bad token shape and a wrong token (no oracle). Re-reads the file first so a rotate / remove from
148
+ * another process (the CLI) takes effect on the next connection.
149
+ */
150
+ verify(name, token) {
151
+ this.load();
152
+ if (typeof name !== 'string' || !AGENT_NAME_RE.test(name) || !looksLikeToken(token))
153
+ return null;
154
+ const a = this.get(name);
155
+ if (!a)
156
+ return null;
157
+ const got = Buffer.from(hashToken(a.salt, token), 'hex');
158
+ const want = Buffer.from(a.tokenHash, 'hex');
159
+ if (got.length !== want.length || !timingSafeEqual(got, want))
160
+ return null;
161
+ return { ...a };
162
+ }
163
+ /** Re-read from disk (the TUI calls this before rendering the roster so CLI edits show up). */
164
+ reload() { this.load(); }
165
+ }
166
+ function sanitizeLimits(l) {
167
+ const out = {};
168
+ const num = (v, lo, hi) => (typeof v === 'number' && Number.isFinite(v) && v >= lo && v <= hi ? Math.floor(v) : undefined);
169
+ const r = num(l.readsPerMin, 1, 600);
170
+ if (r !== undefined)
171
+ out.readsPerMin = r;
172
+ const p = num(l.proposalsPerHour, 0, 200);
173
+ if (p !== undefined)
174
+ out.proposalsPerHour = p;
175
+ const c = num(l.connections, 1, 8);
176
+ if (c !== undefined)
177
+ out.connections = c;
178
+ return out;
179
+ }
@@ -0,0 +1,15 @@
1
+ // @dexterm/agents — public surface (task-1345). Nothing in this package can arm, sign, confirm, send,
2
+ // raise a cap, change the confirmation gate, or touch a wallet, a mandate or the signer: the tier table
3
+ // has no such tool and the gate refuses the shape of such a request before any lookup.
4
+ export * from "./tiers.js";
5
+ export * from "./identity.js";
6
+ export * from "./audit.js";
7
+ export * from "./schema.js";
8
+ export * from "./gate.js";
9
+ // tier 2 (task-1345): transports — the tool table, the loopback WebSocket, the bus, the executor/feed,
10
+ // the proposal store. The SDK is a separate entry point (./sdk) so an external agent imports only it.
11
+ export * from "./tools.js";
12
+ export * from "./ws.js";
13
+ export * from "./executor.js";
14
+ export * from "./proposals.js";
15
+ export * from "./bus.js";