@tangle-network/browser-agent-driver 0.33.3 → 0.34.0

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 (485) hide show
  1. package/README.md +42 -1
  2. package/discovery/llms.txt +27 -0
  3. package/discovery/tangle-agent.json +36 -0
  4. package/dist/brain/action-parse.d.ts +16 -0
  5. package/dist/brain/action-parse.d.ts.map +1 -0
  6. package/dist/brain/action-parse.js +191 -0
  7. package/dist/brain/action-parse.js.map +1 -0
  8. package/dist/brain/decide.d.ts +58 -0
  9. package/dist/brain/decide.d.ts.map +1 -0
  10. package/dist/brain/decide.js +418 -0
  11. package/dist/brain/decide.js.map +1 -0
  12. package/dist/brain/history-compact.d.ts +19 -0
  13. package/dist/brain/history-compact.d.ts.map +1 -0
  14. package/dist/brain/history-compact.js +153 -0
  15. package/dist/brain/history-compact.js.map +1 -0
  16. package/dist/brain/index.d.ts +78 -152
  17. package/dist/brain/index.d.ts.map +1 -1
  18. package/dist/brain/index.js +62 -2268
  19. package/dist/brain/index.js.map +1 -1
  20. package/dist/brain/model-client.d.ts +101 -0
  21. package/dist/brain/model-client.d.ts.map +1 -0
  22. package/dist/brain/model-client.js +391 -0
  23. package/dist/brain/model-client.js.map +1 -0
  24. package/dist/brain/plan.d.ts +57 -0
  25. package/dist/brain/plan.d.ts.map +1 -0
  26. package/dist/brain/plan.js +194 -0
  27. package/dist/brain/plan.js.map +1 -0
  28. package/dist/brain/prompts.d.ts +37 -0
  29. package/dist/brain/prompts.d.ts.map +1 -0
  30. package/dist/brain/prompts.js +455 -0
  31. package/dist/brain/prompts.js.map +1 -0
  32. package/dist/brain/provider-fetch.d.ts +26 -0
  33. package/dist/brain/provider-fetch.d.ts.map +1 -0
  34. package/dist/brain/provider-fetch.js +45 -0
  35. package/dist/brain/provider-fetch.js.map +1 -0
  36. package/dist/brain/snapshot-budget.d.ts +20 -0
  37. package/dist/brain/snapshot-budget.d.ts.map +1 -0
  38. package/dist/brain/snapshot-budget.js +202 -0
  39. package/dist/brain/snapshot-budget.js.map +1 -0
  40. package/dist/brain/system-prompt.d.ts +67 -0
  41. package/dist/brain/system-prompt.d.ts.map +1 -0
  42. package/dist/brain/system-prompt.js +128 -0
  43. package/dist/brain/system-prompt.js.map +1 -0
  44. package/dist/brain/tasks/design-audit.d.ts +33 -0
  45. package/dist/brain/tasks/design-audit.d.ts.map +1 -0
  46. package/dist/brain/tasks/design-audit.js +100 -0
  47. package/dist/brain/tasks/design-audit.js.map +1 -0
  48. package/dist/brain/tasks/evaluate.d.ts +25 -0
  49. package/dist/brain/tasks/evaluate.d.ts.map +1 -0
  50. package/dist/brain/tasks/evaluate.js +55 -0
  51. package/dist/brain/tasks/evaluate.js.map +1 -0
  52. package/dist/brain/tasks/goal-verification.d.ts +32 -0
  53. package/dist/brain/tasks/goal-verification.d.ts.map +1 -0
  54. package/dist/brain/tasks/goal-verification.js +68 -0
  55. package/dist/brain/tasks/goal-verification.js.map +1 -0
  56. package/dist/brain/tasks/knowledge.d.ts +26 -0
  57. package/dist/brain/tasks/knowledge.d.ts.map +1 -0
  58. package/dist/brain/tasks/knowledge.js +56 -0
  59. package/dist/brain/tasks/knowledge.js.map +1 -0
  60. package/dist/brain/tasks/link-scout.d.ts +37 -0
  61. package/dist/brain/tasks/link-scout.d.ts.map +1 -0
  62. package/dist/brain/tasks/link-scout.js +69 -0
  63. package/dist/brain/tasks/link-scout.js.map +1 -0
  64. package/dist/brain/types.d.ts +55 -0
  65. package/dist/brain/types.d.ts.map +1 -0
  66. package/dist/brain/types.js +7 -0
  67. package/dist/brain/types.js.map +1 -0
  68. package/dist/browser/stealth-init-script.d.ts +9 -0
  69. package/dist/browser/stealth-init-script.d.ts.map +1 -0
  70. package/dist/browser/stealth-init-script.js +128 -0
  71. package/dist/browser/stealth-init-script.js.map +1 -0
  72. package/dist/browser/storage-state.d.ts +29 -0
  73. package/dist/browser/storage-state.d.ts.map +1 -0
  74. package/dist/browser/storage-state.js +47 -0
  75. package/dist/browser/storage-state.js.map +1 -0
  76. package/dist/cli/args.d.ts +141 -0
  77. package/dist/cli/args.d.ts.map +1 -0
  78. package/dist/cli/args.js +167 -0
  79. package/dist/cli/args.js.map +1 -0
  80. package/dist/cli/benchmark-sync.d.ts +9 -0
  81. package/dist/cli/benchmark-sync.d.ts.map +1 -0
  82. package/dist/cli/benchmark-sync.js +43 -0
  83. package/dist/cli/benchmark-sync.js.map +1 -0
  84. package/dist/cli/build-config.d.ts +11 -0
  85. package/dist/cli/build-config.d.ts.map +1 -0
  86. package/dist/cli/build-config.js +355 -0
  87. package/dist/cli/build-config.js.map +1 -0
  88. package/dist/cli/commands/auth.d.ts +3 -0
  89. package/dist/cli/commands/auth.d.ts.map +1 -0
  90. package/dist/cli/commands/auth.js +36 -0
  91. package/dist/cli/commands/auth.js.map +1 -0
  92. package/dist/cli/commands/chrome-debug.d.ts +7 -0
  93. package/dist/cli/commands/chrome-debug.d.ts.map +1 -0
  94. package/dist/cli/commands/chrome-debug.js +11 -0
  95. package/dist/cli/commands/chrome-debug.js.map +1 -0
  96. package/dist/cli/commands/design-audit.d.ts +3 -0
  97. package/dist/cli/commands/design-audit.d.ts.map +1 -0
  98. package/dist/cli/commands/design-audit.js +68 -0
  99. package/dist/cli/commands/design-audit.js.map +1 -0
  100. package/dist/cli/commands/preview.d.ts +14 -0
  101. package/dist/cli/commands/preview.d.ts.map +1 -0
  102. package/dist/cli/commands/preview.js +31 -0
  103. package/dist/cli/commands/preview.js.map +1 -0
  104. package/dist/cli/commands/run.d.ts +3 -0
  105. package/dist/cli/commands/run.d.ts.map +1 -0
  106. package/dist/cli/commands/run.js +728 -0
  107. package/dist/cli/commands/run.js.map +1 -0
  108. package/dist/cli/commands/runs.d.ts +8 -0
  109. package/dist/cli/commands/runs.d.ts.map +1 -0
  110. package/dist/cli/commands/runs.js +35 -0
  111. package/dist/cli/commands/runs.js.map +1 -0
  112. package/dist/cli/commands/share.d.ts +10 -0
  113. package/dist/cli/commands/share.d.ts.map +1 -0
  114. package/dist/cli/commands/share.js +32 -0
  115. package/dist/cli/commands/share.js.map +1 -0
  116. package/dist/cli/commands/showcase.d.ts +17 -0
  117. package/dist/cli/commands/showcase.d.ts.map +1 -0
  118. package/dist/cli/commands/showcase.js +20 -0
  119. package/dist/cli/commands/showcase.js.map +1 -0
  120. package/dist/cli/commands/snapshot.d.ts +12 -0
  121. package/dist/cli/commands/snapshot.d.ts.map +1 -0
  122. package/dist/cli/commands/snapshot.js +24 -0
  123. package/dist/cli/commands/snapshot.js.map +1 -0
  124. package/dist/cli/commands/view.d.ts +7 -0
  125. package/dist/cli/commands/view.d.ts.map +1 -0
  126. package/dist/cli/commands/view.js +23 -0
  127. package/dist/cli/commands/view.js.map +1 -0
  128. package/dist/cli/constants.d.ts +6 -0
  129. package/dist/cli/constants.d.ts.map +1 -0
  130. package/dist/cli/constants.js +3 -0
  131. package/dist/cli/constants.js.map +1 -0
  132. package/dist/cli/version.d.ts +7 -0
  133. package/dist/cli/version.d.ts.map +1 -0
  134. package/dist/cli/version.js +16 -0
  135. package/dist/cli/version.js.map +1 -0
  136. package/dist/cli-design-audit.d.ts +44 -36
  137. package/dist/cli-design-audit.d.ts.map +1 -1
  138. package/dist/cli-design-audit.js +137 -1646
  139. package/dist/cli-design-audit.js.map +1 -1
  140. package/dist/cli-jobs.js +2 -1
  141. package/dist/cli-jobs.js.map +1 -1
  142. package/dist/cli.js +66 -1697
  143. package/dist/cli.js.map +1 -1
  144. package/dist/config.d.ts +5 -0
  145. package/dist/config.d.ts.map +1 -1
  146. package/dist/config.js +8 -0
  147. package/dist/config.js.map +1 -1
  148. package/dist/design/audit/evolve/agent.d.ts +23 -0
  149. package/dist/design/audit/evolve/agent.d.ts.map +1 -0
  150. package/dist/design/audit/evolve/agent.js +272 -0
  151. package/dist/design/audit/evolve/agent.js.map +1 -0
  152. package/dist/design/audit/evolve/css.d.ts +10 -0
  153. package/dist/design/audit/evolve/css.d.ts.map +1 -0
  154. package/dist/design/audit/evolve/css.js +206 -0
  155. package/dist/design/audit/evolve/css.js.map +1 -0
  156. package/dist/design/audit/evolve/index.d.ts +13 -0
  157. package/dist/design/audit/evolve/index.d.ts.map +1 -0
  158. package/dist/design/audit/evolve/index.js +12 -0
  159. package/dist/design/audit/evolve/index.js.map +1 -0
  160. package/dist/design/audit/evolve/report.d.ts +3 -0
  161. package/dist/design/audit/evolve/report.d.ts.map +1 -0
  162. package/dist/design/audit/evolve/report.js +45 -0
  163. package/dist/design/audit/evolve/report.js.map +1 -0
  164. package/dist/design/audit/evolve/types.d.ts +14 -0
  165. package/dist/design/audit/evolve/types.d.ts.map +1 -0
  166. package/dist/design/audit/evolve/types.js +2 -0
  167. package/dist/design/audit/evolve/types.js.map +1 -0
  168. package/dist/design/audit/pipeline.d.ts +12 -0
  169. package/dist/design/audit/pipeline.d.ts.map +1 -1
  170. package/dist/design/audit/pipeline.js +43 -27
  171. package/dist/design/audit/pipeline.js.map +1 -1
  172. package/dist/design/audit/reference/artifact/build.d.ts +33 -0
  173. package/dist/design/audit/reference/artifact/build.d.ts.map +1 -0
  174. package/dist/design/audit/reference/artifact/build.js +64 -0
  175. package/dist/design/audit/reference/artifact/build.js.map +1 -0
  176. package/dist/design/audit/reference/artifact/render.d.ts +39 -0
  177. package/dist/design/audit/reference/artifact/render.d.ts.map +1 -0
  178. package/dist/design/audit/reference/artifact/render.js +213 -0
  179. package/dist/design/audit/reference/artifact/render.js.map +1 -0
  180. package/dist/design/audit/reference/artifact/text.d.ts +11 -0
  181. package/dist/design/audit/reference/artifact/text.d.ts.map +1 -0
  182. package/dist/design/audit/reference/artifact/text.js +20 -0
  183. package/dist/design/audit/reference/artifact/text.js.map +1 -0
  184. package/dist/design/audit/reference/artifact/to-findings.d.ts +57 -0
  185. package/dist/design/audit/reference/artifact/to-findings.d.ts.map +1 -0
  186. package/dist/design/audit/reference/artifact/to-findings.js +314 -0
  187. package/dist/design/audit/reference/artifact/to-findings.js.map +1 -0
  188. package/dist/design/audit/reference/config.d.ts +50 -0
  189. package/dist/design/audit/reference/config.d.ts.map +1 -0
  190. package/dist/design/audit/reference/config.js +108 -0
  191. package/dist/design/audit/reference/config.js.map +1 -0
  192. package/dist/design/audit/reference/contracts.d.ts +1030 -0
  193. package/dist/design/audit/reference/contracts.d.ts.map +1 -0
  194. package/dist/design/audit/reference/contracts.js +28 -0
  195. package/dist/design/audit/reference/contracts.js.map +1 -0
  196. package/dist/design/audit/reference/corpus/build.d.ts +115 -0
  197. package/dist/design/audit/reference/corpus/build.d.ts.map +1 -0
  198. package/dist/design/audit/reference/corpus/build.js +152 -0
  199. package/dist/design/audit/reference/corpus/build.js.map +1 -0
  200. package/dist/design/audit/reference/corpus/schema.d.ts +33 -0
  201. package/dist/design/audit/reference/corpus/schema.d.ts.map +1 -0
  202. package/dist/design/audit/reference/corpus/schema.js +249 -0
  203. package/dist/design/audit/reference/corpus/schema.js.map +1 -0
  204. package/dist/design/audit/reference/corpus/store.d.ts +26 -0
  205. package/dist/design/audit/reference/corpus/store.d.ts.map +1 -0
  206. package/dist/design/audit/reference/corpus/store.js +104 -0
  207. package/dist/design/audit/reference/corpus/store.js.map +1 -0
  208. package/dist/design/audit/reference/dna/delta.d.ts +21 -0
  209. package/dist/design/audit/reference/dna/delta.d.ts.map +1 -0
  210. package/dist/design/audit/reference/dna/delta.js +126 -0
  211. package/dist/design/audit/reference/dna/delta.js.map +1 -0
  212. package/dist/design/audit/reference/dna/derive.d.ts +31 -0
  213. package/dist/design/audit/reference/dna/derive.d.ts.map +1 -0
  214. package/dist/design/audit/reference/dna/derive.js +381 -0
  215. package/dist/design/audit/reference/dna/derive.js.map +1 -0
  216. package/dist/design/audit/reference/dna/descriptor.d.ts +36 -0
  217. package/dist/design/audit/reference/dna/descriptor.d.ts.map +1 -0
  218. package/dist/design/audit/reference/dna/descriptor.js +209 -0
  219. package/dist/design/audit/reference/dna/descriptor.js.map +1 -0
  220. package/dist/design/audit/reference/dna/page-adapter.d.ts +17 -0
  221. package/dist/design/audit/reference/dna/page-adapter.d.ts.map +1 -0
  222. package/dist/design/audit/reference/dna/page-adapter.js +35 -0
  223. package/dist/design/audit/reference/dna/page-adapter.js.map +1 -0
  224. package/dist/design/audit/reference/dna/scroll-capture.d.ts +93 -0
  225. package/dist/design/audit/reference/dna/scroll-capture.d.ts.map +1 -0
  226. package/dist/design/audit/reference/dna/scroll-capture.js +374 -0
  227. package/dist/design/audit/reference/dna/scroll-capture.js.map +1 -0
  228. package/dist/design/audit/reference/engine/budget.d.ts +63 -0
  229. package/dist/design/audit/reference/engine/budget.d.ts.map +1 -0
  230. package/dist/design/audit/reference/engine/budget.js +110 -0
  231. package/dist/design/audit/reference/engine/budget.js.map +1 -0
  232. package/dist/design/audit/reference/engine/core.d.ts +32 -0
  233. package/dist/design/audit/reference/engine/core.d.ts.map +1 -0
  234. package/dist/design/audit/reference/engine/core.js +261 -0
  235. package/dist/design/audit/reference/engine/core.js.map +1 -0
  236. package/dist/design/audit/reference/engine/guard.d.ts +43 -0
  237. package/dist/design/audit/reference/engine/guard.d.ts.map +1 -0
  238. package/dist/design/audit/reference/engine/guard.js +43 -0
  239. package/dist/design/audit/reference/engine/guard.js.map +1 -0
  240. package/dist/design/audit/reference/engine/score-core.d.ts +48 -0
  241. package/dist/design/audit/reference/engine/score-core.d.ts.map +1 -0
  242. package/dist/design/audit/reference/engine/score-core.js +134 -0
  243. package/dist/design/audit/reference/engine/score-core.js.map +1 -0
  244. package/dist/design/audit/reference/engine/wiring.d.ts +35 -0
  245. package/dist/design/audit/reference/engine/wiring.d.ts.map +1 -0
  246. package/dist/design/audit/reference/engine/wiring.js +80 -0
  247. package/dist/design/audit/reference/engine/wiring.js.map +1 -0
  248. package/dist/design/audit/reference/eval/taste-core.d.ts +77 -0
  249. package/dist/design/audit/reference/eval/taste-core.d.ts.map +1 -0
  250. package/dist/design/audit/reference/eval/taste-core.js +95 -0
  251. package/dist/design/audit/reference/eval/taste-core.js.map +1 -0
  252. package/dist/design/audit/reference/generate/generator.d.ts +49 -0
  253. package/dist/design/audit/reference/generate/generator.d.ts.map +1 -0
  254. package/dist/design/audit/reference/generate/generator.js +94 -0
  255. package/dist/design/audit/reference/generate/generator.js.map +1 -0
  256. package/dist/design/audit/reference/generate/parse.d.ts +28 -0
  257. package/dist/design/audit/reference/generate/parse.d.ts.map +1 -0
  258. package/dist/design/audit/reference/generate/parse.js +180 -0
  259. package/dist/design/audit/reference/generate/parse.js.map +1 -0
  260. package/dist/design/audit/reference/generate/prompt.d.ts +36 -0
  261. package/dist/design/audit/reference/generate/prompt.d.ts.map +1 -0
  262. package/dist/design/audit/reference/generate/prompt.js +124 -0
  263. package/dist/design/audit/reference/generate/prompt.js.map +1 -0
  264. package/dist/design/audit/reference/index.d.ts +26 -0
  265. package/dist/design/audit/reference/index.d.ts.map +1 -0
  266. package/dist/design/audit/reference/index.js +31 -0
  267. package/dist/design/audit/reference/index.js.map +1 -0
  268. package/dist/design/audit/reference/judge/image-clamp.d.ts +34 -0
  269. package/dist/design/audit/reference/judge/image-clamp.d.ts.map +1 -0
  270. package/dist/design/audit/reference/judge/image-clamp.js +88 -0
  271. package/dist/design/audit/reference/judge/image-clamp.js.map +1 -0
  272. package/dist/design/audit/reference/judge/pairwise.d.ts +34 -0
  273. package/dist/design/audit/reference/judge/pairwise.d.ts.map +1 -0
  274. package/dist/design/audit/reference/judge/pairwise.js +105 -0
  275. package/dist/design/audit/reference/judge/pairwise.js.map +1 -0
  276. package/dist/design/audit/reference/judge/parse.d.ts +31 -0
  277. package/dist/design/audit/reference/judge/parse.d.ts.map +1 -0
  278. package/dist/design/audit/reference/judge/parse.js +113 -0
  279. package/dist/design/audit/reference/judge/parse.js.map +1 -0
  280. package/dist/design/audit/reference/judge/prompt.d.ts +38 -0
  281. package/dist/design/audit/reference/judge/prompt.d.ts.map +1 -0
  282. package/dist/design/audit/reference/judge/prompt.js +111 -0
  283. package/dist/design/audit/reference/judge/prompt.js.map +1 -0
  284. package/dist/design/audit/reference/judge/quality.d.ts +28 -0
  285. package/dist/design/audit/reference/judge/quality.d.ts.map +1 -0
  286. package/dist/design/audit/reference/judge/quality.js +62 -0
  287. package/dist/design/audit/reference/judge/quality.js.map +1 -0
  288. package/dist/design/audit/reference/judge/rank.d.ts +41 -0
  289. package/dist/design/audit/reference/judge/rank.d.ts.map +1 -0
  290. package/dist/design/audit/reference/judge/rank.js +196 -0
  291. package/dist/design/audit/reference/judge/rank.js.map +1 -0
  292. package/dist/design/audit/reference/judge/text-judge.d.ts +31 -0
  293. package/dist/design/audit/reference/judge/text-judge.d.ts.map +1 -0
  294. package/dist/design/audit/reference/judge/text-judge.js +43 -0
  295. package/dist/design/audit/reference/judge/text-judge.js.map +1 -0
  296. package/dist/design/audit/reference/judge/vision-judge.d.ts +59 -0
  297. package/dist/design/audit/reference/judge/vision-judge.d.ts.map +1 -0
  298. package/dist/design/audit/reference/judge/vision-judge.js +150 -0
  299. package/dist/design/audit/reference/judge/vision-judge.js.map +1 -0
  300. package/dist/design/audit/reference/judge/vision-model.d.ts +65 -0
  301. package/dist/design/audit/reference/judge/vision-model.d.ts.map +1 -0
  302. package/dist/design/audit/reference/judge/vision-model.js +110 -0
  303. package/dist/design/audit/reference/judge/vision-model.js.map +1 -0
  304. package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts +68 -0
  305. package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts.map +1 -0
  306. package/dist/design/audit/reference/pipeline/evaluate-reference.js +52 -0
  307. package/dist/design/audit/reference/pipeline/evaluate-reference.js.map +1 -0
  308. package/dist/design/audit/reference/reference-context.d.ts +31 -0
  309. package/dist/design/audit/reference/reference-context.d.ts.map +1 -0
  310. package/dist/design/audit/reference/reference-context.js +73 -0
  311. package/dist/design/audit/reference/reference-context.js.map +1 -0
  312. package/dist/design/audit/reference/retrieval/embedding-hash.d.ts +30 -0
  313. package/dist/design/audit/reference/retrieval/embedding-hash.d.ts.map +1 -0
  314. package/dist/design/audit/reference/retrieval/embedding-hash.js +87 -0
  315. package/dist/design/audit/reference/retrieval/embedding-hash.js.map +1 -0
  316. package/dist/design/audit/reference/retrieval/embedding-openai.d.ts +38 -0
  317. package/dist/design/audit/reference/retrieval/embedding-openai.d.ts.map +1 -0
  318. package/dist/design/audit/reference/retrieval/embedding-openai.js +73 -0
  319. package/dist/design/audit/reference/retrieval/embedding-openai.js.map +1 -0
  320. package/dist/design/audit/reference/retrieval/matcher.d.ts +34 -0
  321. package/dist/design/audit/reference/retrieval/matcher.d.ts.map +1 -0
  322. package/dist/design/audit/reference/retrieval/matcher.js +107 -0
  323. package/dist/design/audit/reference/retrieval/matcher.js.map +1 -0
  324. package/dist/design/audit/reference/run.d.ts +59 -0
  325. package/dist/design/audit/reference/run.d.ts.map +1 -0
  326. package/dist/design/audit/reference/run.js +99 -0
  327. package/dist/design/audit/reference/run.js.map +1 -0
  328. package/dist/design/audit/report.d.ts +35 -0
  329. package/dist/design/audit/report.d.ts.map +1 -0
  330. package/dist/design/audit/report.js +195 -0
  331. package/dist/design/audit/report.js.map +1 -0
  332. package/dist/design/audit/tokens/extract.d.ts +71 -0
  333. package/dist/design/audit/tokens/extract.d.ts.map +1 -0
  334. package/dist/design/audit/tokens/extract.js +1034 -0
  335. package/dist/design/audit/tokens/extract.js.map +1 -0
  336. package/dist/design/audit/types.d.ts +8 -0
  337. package/dist/design/audit/types.d.ts.map +1 -1
  338. package/dist/design/compare.d.ts.map +1 -1
  339. package/dist/design/compare.js +2 -6
  340. package/dist/design/compare.js.map +1 -1
  341. package/dist/design/cookie-consent.d.ts +3 -0
  342. package/dist/design/cookie-consent.d.ts.map +1 -0
  343. package/dist/design/cookie-consent.js +26 -0
  344. package/dist/design/cookie-consent.js.map +1 -0
  345. package/dist/design/viewports.d.ts +18 -0
  346. package/dist/design/viewports.d.ts.map +1 -0
  347. package/dist/design/viewports.js +10 -0
  348. package/dist/design/viewports.js.map +1 -0
  349. package/dist/index.d.ts +2 -2
  350. package/dist/index.d.ts.map +1 -1
  351. package/dist/index.js +1 -1
  352. package/dist/index.js.map +1 -1
  353. package/dist/memory/store.d.ts +19 -1
  354. package/dist/memory/store.d.ts.map +1 -1
  355. package/dist/memory/store.js +47 -0
  356. package/dist/memory/store.js.map +1 -1
  357. package/dist/provider-defaults.d.ts +18 -0
  358. package/dist/provider-defaults.d.ts.map +1 -1
  359. package/dist/provider-defaults.js +33 -0
  360. package/dist/provider-defaults.js.map +1 -1
  361. package/dist/runner/allowed-domains.d.ts +11 -0
  362. package/dist/runner/allowed-domains.d.ts.map +1 -0
  363. package/dist/runner/allowed-domains.js +14 -0
  364. package/dist/runner/allowed-domains.js.map +1 -0
  365. package/dist/runner/batch-fill.d.ts +28 -0
  366. package/dist/runner/batch-fill.d.ts.map +1 -0
  367. package/dist/runner/batch-fill.js +80 -0
  368. package/dist/runner/batch-fill.js.map +1 -0
  369. package/dist/runner/completion-policy.d.ts +26 -0
  370. package/dist/runner/completion-policy.d.ts.map +1 -0
  371. package/dist/runner/completion-policy.js +76 -0
  372. package/dist/runner/completion-policy.js.map +1 -0
  373. package/dist/runner/constants.d.ts +14 -0
  374. package/dist/runner/constants.d.ts.map +1 -0
  375. package/dist/runner/constants.js +23 -0
  376. package/dist/runner/constants.js.map +1 -0
  377. package/dist/runner/decision-screenshot.d.ts +24 -0
  378. package/dist/runner/decision-screenshot.d.ts.map +1 -0
  379. package/dist/runner/decision-screenshot.js +23 -0
  380. package/dist/runner/decision-screenshot.js.map +1 -0
  381. package/dist/runner/domain-boundary.d.ts +36 -0
  382. package/dist/runner/domain-boundary.d.ts.map +1 -0
  383. package/dist/runner/domain-boundary.js +81 -0
  384. package/dist/runner/domain-boundary.js.map +1 -0
  385. package/dist/runner/effect-verify.d.ts +28 -0
  386. package/dist/runner/effect-verify.d.ts.map +1 -0
  387. package/dist/runner/effect-verify.js +42 -0
  388. package/dist/runner/effect-verify.js.map +1 -0
  389. package/dist/runner/execute-plan.d.ts +58 -0
  390. package/dist/runner/execute-plan.d.ts.map +1 -0
  391. package/dist/runner/execute-plan.js +426 -0
  392. package/dist/runner/execute-plan.js.map +1 -0
  393. package/dist/runner/max-turns-extension.d.ts +19 -0
  394. package/dist/runner/max-turns-extension.d.ts.map +1 -0
  395. package/dist/runner/max-turns-extension.js +25 -0
  396. package/dist/runner/max-turns-extension.js.map +1 -0
  397. package/dist/runner/micro-plan.d.ts +11 -0
  398. package/dist/runner/micro-plan.d.ts.map +1 -0
  399. package/dist/runner/micro-plan.js +29 -0
  400. package/dist/runner/micro-plan.js.map +1 -0
  401. package/dist/runner/prompt-snippets.d.ts +5 -0
  402. package/dist/runner/prompt-snippets.d.ts.map +1 -0
  403. package/dist/runner/prompt-snippets.js +13 -0
  404. package/dist/runner/prompt-snippets.js.map +1 -0
  405. package/dist/runner/replay/contracts.d.ts +300 -0
  406. package/dist/runner/replay/contracts.d.ts.map +1 -0
  407. package/dist/runner/replay/contracts.js +42 -0
  408. package/dist/runner/replay/contracts.js.map +1 -0
  409. package/dist/runner/replay/controller.d.ts +27 -0
  410. package/dist/runner/replay/controller.d.ts.map +1 -0
  411. package/dist/runner/replay/controller.js +197 -0
  412. package/dist/runner/replay/controller.js.map +1 -0
  413. package/dist/runner/replay/guard.d.ts +28 -0
  414. package/dist/runner/replay/guard.d.ts.map +1 -0
  415. package/dist/runner/replay/guard.js +100 -0
  416. package/dist/runner/replay/guard.js.map +1 -0
  417. package/dist/runner/runner.d.ts +44 -51
  418. package/dist/runner/runner.d.ts.map +1 -1
  419. package/dist/runner/runner.js +155 -851
  420. package/dist/runner/runner.js.map +1 -1
  421. package/dist/runner/scout-feedback.d.ts +51 -0
  422. package/dist/runner/scout-feedback.d.ts.map +1 -0
  423. package/dist/runner/scout-feedback.js +149 -0
  424. package/dist/runner/scout-feedback.js.map +1 -0
  425. package/dist/skills/macro-loader.d.ts +1 -1
  426. package/dist/test-runner.d.ts.map +1 -1
  427. package/dist/test-runner.js +25 -0
  428. package/dist/test-runner.js.map +1 -1
  429. package/dist/types/actions.d.ts +241 -0
  430. package/dist/types/actions.d.ts.map +1 -0
  431. package/dist/types/actions.js +5 -0
  432. package/dist/types/actions.js.map +1 -0
  433. package/dist/types/config.d.ts +211 -0
  434. package/dist/types/config.d.ts.map +1 -0
  435. package/dist/types/config.js +2 -0
  436. package/dist/types/config.js.map +1 -0
  437. package/dist/types/design-audit.d.ts +92 -0
  438. package/dist/types/design-audit.d.ts.map +1 -0
  439. package/dist/types/design-audit.js +5 -0
  440. package/dist/types/design-audit.js.map +1 -0
  441. package/dist/types/design-tokens.d.ts +138 -0
  442. package/dist/types/design-tokens.d.ts.map +1 -0
  443. package/dist/types/design-tokens.js +5 -0
  444. package/dist/types/design-tokens.js.map +1 -0
  445. package/dist/types/page.d.ts +16 -0
  446. package/dist/types/page.d.ts.map +1 -0
  447. package/dist/types/page.js +2 -0
  448. package/dist/types/page.js.map +1 -0
  449. package/dist/types/plan.d.ts +50 -0
  450. package/dist/types/plan.d.ts.map +1 -0
  451. package/dist/types/plan.js +2 -0
  452. package/dist/types/plan.js.map +1 -0
  453. package/dist/types/preview.d.ts +9 -0
  454. package/dist/types/preview.d.ts.map +1 -0
  455. package/dist/types/preview.js +5 -0
  456. package/dist/types/preview.js.map +1 -0
  457. package/dist/types/result.d.ts +62 -0
  458. package/dist/types/result.d.ts.map +1 -0
  459. package/dist/types/result.js +2 -0
  460. package/dist/types/result.js.map +1 -0
  461. package/dist/types/scenario.d.ts +20 -0
  462. package/dist/types/scenario.d.ts.map +1 -0
  463. package/dist/types/scenario.js +5 -0
  464. package/dist/types/scenario.js.map +1 -0
  465. package/dist/types/test-runner.d.ts +145 -0
  466. package/dist/types/test-runner.d.ts.map +1 -0
  467. package/dist/types/test-runner.js +10 -0
  468. package/dist/types/test-runner.js.map +1 -0
  469. package/dist/types/trajectory.d.ts +38 -0
  470. package/dist/types/trajectory.d.ts.map +1 -0
  471. package/dist/types/trajectory.js +2 -0
  472. package/dist/types/trajectory.js.map +1 -0
  473. package/dist/types/turn.d.ts +50 -0
  474. package/dist/types/turn.d.ts.map +1 -0
  475. package/dist/types/turn.js +2 -0
  476. package/dist/types/turn.js.map +1 -0
  477. package/dist/types.d.ts +16 -1017
  478. package/dist/types.d.ts.map +1 -1
  479. package/dist/types.js +15 -8
  480. package/dist/types.js.map +1 -1
  481. package/dist/wallet/rpc-interception.d.ts +15 -0
  482. package/dist/wallet/rpc-interception.d.ts.map +1 -0
  483. package/dist/wallet/rpc-interception.js +95 -0
  484. package/dist/wallet/rpc-interception.js.map +1 -0
  485. package/package.json +6 -2
package/dist/cli.js CHANGED
@@ -10,69 +10,14 @@
10
10
  * Designed for sandbox/container execution:
11
11
  * docker run bad run --cases /data/cases.json --sink /output/
12
12
  */
13
- import { parseArgs } from 'node:util';
14
13
  import * as fs from 'node:fs';
15
- import * as path from 'node:path';
16
- import { spawn } from 'node:child_process';
17
- import { loadConfig, mergeConfig, toAgentConfig } from './config.js';
18
- import { buildBrowserLaunchPlan } from './browser-launch.js';
19
- import { runWalletPreflight, startWalletAutoApprover } from './wallet/automation.js';
20
- import { isPersonaId, listPersonaIds, withPersonaDirective } from './personas.js';
21
- import { resolveProviderApiKey, resolveProviderModelName } from './provider-defaults.js';
22
14
  import { loadLocalEnvFiles } from './env-loader.js';
23
- import { CliRenderer, cliError, cliWarn, cliLog, printStyledHelp } from './cli-ui.js';
24
- import { ProjectStore } from './memory/project-store.js';
25
- import { RunRegistry } from './memory/run-registry.js';
15
+ import { cliError, printStyledHelp } from './cli-ui.js';
16
+ import { listPersonaIds } from './personas.js';
26
17
  import { setCliVersion, setInvocation, getTelemetry } from './telemetry/index.js';
27
- const RUN_MODES = ['fast-explore', 'full-evidence'];
28
- const DRIVER_PROFILES = ['default', 'stealth', 'benchmark-webbench', 'benchmark-webbench-stealth', 'benchmark-webvoyager'];
29
- async function applyStorageStateToPersistentContext(context, storageStatePath) {
30
- if (!storageStatePath)
31
- return;
32
- const parsed = JSON.parse(fs.readFileSync(storageStatePath, 'utf-8'));
33
- const cookies = parsed.cookies ?? [];
34
- if (cookies.length > 0) {
35
- await context.addCookies(cookies);
36
- }
37
- const origins = parsed.origins ?? [];
38
- if (origins.length === 0)
39
- return;
40
- const existingPages = context.pages();
41
- const page = existingPages[0] ?? await context.newPage();
42
- const createdTempPage = existingPages.length === 0;
43
- try {
44
- for (const originState of origins) {
45
- if (!originState?.origin || !Array.isArray(originState.localStorage) || originState.localStorage.length === 0) {
46
- continue;
47
- }
48
- await page.goto(originState.origin, { waitUntil: 'domcontentloaded' }).catch(() => { });
49
- await page.evaluate((entries) => {
50
- for (const entry of entries) {
51
- try {
52
- localStorage.setItem(entry.name, entry.value);
53
- }
54
- catch {
55
- // Best effort: some origins may block storage writes.
56
- }
57
- }
58
- }, originState.localStorage);
59
- }
60
- }
61
- finally {
62
- if (createdTempPage) {
63
- await page.close().catch(() => { });
64
- }
65
- }
66
- }
67
- function readCliVersion() {
68
- try {
69
- const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
70
- return pkg.version ?? '0.0.0';
71
- }
72
- catch {
73
- return '0.0.0';
74
- }
75
- }
18
+ import { parseCliArgs } from './cli/args.js';
19
+ import { readCliVersion } from './cli/version.js';
20
+ import { RUN_MODES, DRIVER_PROFILES } from './cli/constants.js';
76
21
  async function main() {
77
22
  loadLocalEnvFiles(process.cwd());
78
23
  setCliVersion(readCliVersion());
@@ -90,150 +35,7 @@ async function main() {
90
35
  await runReportsCli(subArgs.slice(1));
91
36
  process.exit(0);
92
37
  }
93
- const { values, positionals } = parseArgs({
94
- allowPositionals: true,
95
- allowNegative: true,
96
- options: {
97
- // Config file
98
- config: { type: 'string' },
99
- // Test specification
100
- goal: { type: 'string', short: 'g' },
101
- url: { type: 'string', short: 'u' },
102
- cases: { type: 'string', short: 'c' },
103
- 'cases-json': { type: 'string' },
104
- 'allowed-domains': { type: 'string' },
105
- // LLM configuration
106
- model: { type: 'string', short: 'm' },
107
- provider: { type: 'string' },
108
- 'model-adaptive': { type: 'boolean' },
109
- 'nav-model': { type: 'string' },
110
- 'nav-provider': { type: 'string' },
111
- persona: { type: 'string' },
112
- mode: { type: 'string' },
113
- profile: { type: 'string' },
114
- 'prompt-file': { type: 'string' },
115
- 'sandbox-backend-type': { type: 'string' },
116
- 'sandbox-backend-profile': { type: 'string' },
117
- 'sandbox-backend-provider': { type: 'string' },
118
- 'api-key': { type: 'string' },
119
- 'base-url': { type: 'string' },
120
- // Execution
121
- browser: { type: 'string' },
122
- 'storage-state': { type: 'string' },
123
- concurrency: { type: 'string' },
124
- 'max-turns': { type: 'string' },
125
- 'session-id': { type: 'string' },
126
- 'resume-run': { type: 'string' },
127
- 'fork-run': { type: 'string' },
128
- pages: { type: 'string' },
129
- 'extract-tokens': { type: 'boolean' },
130
- rip: { type: 'boolean' },
131
- 'design-compare': { type: 'boolean' },
132
- 'compare-url': { type: 'string' },
133
- evolve: { type: 'string' },
134
- 'evolve-rounds': { type: 'string' },
135
- 'project-dir': { type: 'string' },
136
- reproducibility: { type: 'boolean' },
137
- 'rubrics-dir': { type: 'string' },
138
- 'audit-passes': { type: 'string' },
139
- // Layer 7 — domain ethics gate. --skip-ethics bypasses the rollup floor
140
- // for testing scenarios; --ethics-rules-dir overrides the builtin rule set.
141
- 'skip-ethics': { type: 'boolean' },
142
- 'ethics-rules-dir': { type: 'string' },
143
- // Layer 6 / 7 — audience predicate hints. Comma-separated.
144
- audience: { type: 'string' },
145
- 'regulatory-context': { type: 'string' },
146
- 'audience-vulnerability': { type: 'string' },
147
- modality: { type: 'string' },
148
- // bad view
149
- port: { type: 'string' },
150
- 'no-open': { type: 'boolean' },
151
- // bad run --show-cursor (overlay)
152
- 'show-cursor': { type: 'boolean' },
153
- // bad run --live (open SSE-streaming live viewer alongside the run)
154
- live: { type: 'boolean' },
155
- // bad run --planner: one LLM call generates the full action sequence,
156
- // then the runner executes it deterministically.
157
- planner: { type: 'boolean' },
158
- 'planner-mode': { type: 'string' },
159
- // showcase
160
- script: { type: 'string' },
161
- capture: { type: 'string' },
162
- crop: { type: 'string' },
163
- highlight: { type: 'string' },
164
- format: { type: 'string' },
165
- viewport: { type: 'string' },
166
- scale: { type: 'string' },
167
- 'color-scheme': { type: 'string' },
168
- 'llm-timeout': { type: 'string' },
169
- retries: { type: 'string' },
170
- 'retry-delay-ms': { type: 'string' },
171
- 'screenshot-interval': { type: 'string' },
172
- scout: { type: 'boolean' },
173
- 'scout-model': { type: 'string' },
174
- 'scout-provider': { type: 'string' },
175
- 'scout-vision': { type: 'boolean' },
176
- 'scout-max-candidates': { type: 'string' },
177
- 'scout-min-top-score': { type: 'string' },
178
- 'scout-max-score-gap': { type: 'string' },
179
- headless: { type: 'boolean' },
180
- proxy: { type: 'string' },
181
- timeout: { type: 'string' },
182
- extension: { type: 'string', multiple: true },
183
- 'user-data-dir': { type: 'string' },
184
- 'profile-dir': { type: 'string' },
185
- 'cdp-url': { type: 'string' },
186
- attach: { type: 'boolean' },
187
- 'attach-port': { type: 'string' },
188
- wallet: { type: 'boolean' },
189
- 'wallet-auto-approve': { type: 'boolean' },
190
- 'wallet-password': { type: 'string' },
191
- 'wallet-seed-url': { type: 'string', multiple: true },
192
- 'wallet-preflight': { type: 'boolean' },
193
- 'wallet-chain-id': { type: 'string' },
194
- 'wallet-chain-rpc-url': { type: 'string' },
195
- memory: { type: 'boolean' },
196
- 'memory-dir': { type: 'string' },
197
- // Output
198
- sink: { type: 'string', short: 's' },
199
- json: { type: 'boolean', default: false },
200
- quiet: { type: 'boolean', short: 'q', default: false },
201
- // Feature flags
202
- 'goal-verification': { type: 'boolean' },
203
- 'quality-threshold': { type: 'string' },
204
- 'trace-scoring': { type: 'boolean' },
205
- 'trace-ttl-days': { type: 'string' },
206
- vision: { type: 'boolean' },
207
- 'vision-strategy': { type: 'string' },
208
- 'observation-mode': { type: 'string' },
209
- debug: { type: 'boolean', short: 'd', default: false },
210
- // Resource blocking
211
- 'block-analytics': { type: 'boolean', default: false },
212
- 'block-images': { type: 'boolean', default: false },
213
- 'block-media': { type: 'boolean', default: false },
214
- // Auth
215
- fill: { type: 'string', multiple: true },
216
- cookie: { type: 'string', multiple: true },
217
- 'wait-for': { type: 'string' },
218
- 'wait-timeout': { type: 'string' },
219
- // `bad share` flags
220
- visibility: { type: 'string' },
221
- 'bad-app-url': { type: 'string' },
222
- 'no-copy': { type: 'boolean' },
223
- // preview / stream / interrupt
224
- 'max-steps': { type: 'string' },
225
- headed: { type: 'boolean' },
226
- stream: { type: 'string' },
227
- 'stream-token': { type: 'string' },
228
- interrupt: { type: 'boolean' },
229
- // `bad snapshot` — headless, no-LLM accessibility dump
230
- out: { type: 'string' },
231
- wait: { type: 'string' },
232
- 'dismiss-modals': { type: 'boolean' },
233
- help: { type: 'boolean', short: 'h', default: false },
234
- version: { type: 'boolean', short: 'v', default: false },
235
- },
236
- });
38
+ const { values, positionals } = parseCliArgs();
237
39
  if (values.version) {
238
40
  const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
239
41
  console.log(pkg.version);
@@ -245,146 +47,46 @@ async function main() {
245
47
  }
246
48
  const command = positionals[0];
247
49
  if (command === 'view') {
248
- const runDir = positionals[1];
249
- if (!runDir) {
250
- cliError('usage: bad view <run-directory>');
251
- process.exit(1);
252
- }
253
- const { runViewCli, ViewError } = await import('./cli-view.js');
254
- try {
255
- await runViewCli({
256
- runDir,
257
- port: values.port ? parseInt(values.port) : undefined,
258
- noOpen: values['no-open'],
259
- });
260
- }
261
- catch (err) {
262
- if (err instanceof ViewError) {
263
- cliError(err.message);
264
- process.exit(1);
265
- }
266
- throw err;
267
- }
50
+ const { runViewCommand } = await import('./cli/commands/view.js');
51
+ await runViewCommand({
52
+ runDir: positionals[1],
53
+ port: values.port,
54
+ noOpen: values['no-open'],
55
+ });
268
56
  return;
269
57
  }
270
58
  if (command === 'design-audit') {
271
- if (!values.url) {
272
- cliError('--url is required for design-audit.');
273
- process.exit(1);
274
- }
275
- // --design-compare mode
276
- if (values['design-compare']) {
277
- if (!values['compare-url']) {
278
- cliError('--compare-url is required with --design-compare.');
279
- process.exit(1);
280
- }
281
- const { runDesignCompare } = await import('./design/compare.js');
282
- await runDesignCompare({
283
- urlA: values.url,
284
- urlB: values['compare-url'],
285
- headless: values.headless,
286
- outputDir: values.sink,
287
- });
288
- process.exit(0);
289
- }
290
- // --rip mode
291
- if (values.rip) {
292
- const { ripSite } = await import('./design/rip.js');
293
- await ripSite({
294
- url: values.url,
295
- pages: values.pages ? parseInt(values.pages) : undefined,
296
- headless: values.headless,
297
- outputDir: values.sink,
298
- });
299
- process.exit(0);
300
- }
301
- const { runDesignAudit } = await import('./cli-design-audit.js');
302
- await runDesignAudit({
303
- url: values.url,
304
- pages: values.pages ? parseInt(values.pages) : undefined,
305
- profile: values.profile,
306
- model: values.model,
307
- provider: values.provider,
308
- apiKey: values['api-key'],
309
- baseUrl: values['base-url'],
310
- output: values.sink,
311
- json: values.json,
312
- headless: values.headless,
313
- debug: values.debug,
314
- storageState: values['storage-state'],
315
- extractTokens: values['extract-tokens'],
316
- evolve: values.evolve,
317
- evolveRounds: values['evolve-rounds'] ? parseInt(values['evolve-rounds']) : undefined,
318
- projectDir: values['project-dir'],
319
- reproducibility: values.reproducibility,
320
- rubricsDir: values['rubrics-dir'],
321
- auditPasses: values['audit-passes'],
322
- skipEthics: values['skip-ethics'],
323
- ethicsRulesDir: values['ethics-rules-dir'],
324
- audience: values.audience,
325
- regulatoryContext: values['regulatory-context'],
326
- audienceVulnerability: values['audience-vulnerability'],
327
- modality: values.modality,
328
- });
329
- process.exit(0);
59
+ const { runDesignAuditCommand } = await import('./cli/commands/design-audit.js');
60
+ await runDesignAuditCommand(values);
61
+ return;
330
62
  }
331
63
  if (command === 'snapshot') {
332
- if (!values.url) {
333
- cliError('usage: bad snapshot --url <url> [--json] [--out file.json] [--wait networkidle|load|domcontentloaded|commit] [--timeout <ms>] [--no-dismiss-modals] [--headed]');
334
- process.exit(2);
335
- }
336
- const waitArg = values.wait;
337
- const wait = waitArg === 'load' || waitArg === 'domcontentloaded' || waitArg === 'networkidle' || waitArg === 'commit'
338
- ? waitArg
339
- : undefined;
340
- const { handleSnapshotCommand } = await import('./cli-snapshot.js');
341
- const rc = await handleSnapshotCommand({
64
+ const { runSnapshotCommand } = await import('./cli/commands/snapshot.js');
65
+ await runSnapshotCommand({
342
66
  url: values.url,
343
67
  json: values.json,
344
68
  out: values.out,
345
- timeout: values.timeout ? parseInt(values.timeout, 10) : undefined,
346
- wait,
69
+ timeout: values.timeout,
70
+ wait: values.wait,
347
71
  dismissModals: values['dismiss-modals'],
348
72
  headed: values.headed,
349
73
  debug: values.debug,
350
74
  });
351
- process.exit(rc);
75
+ return;
352
76
  }
353
77
  if (command === 'runs') {
354
- const store = new ProjectStore(values['memory-dir']);
355
- const registry = new RunRegistry(store.getRoot());
356
- const runs = registry.listRuns({
357
- domain: values.url ? new URL(values.url).hostname : undefined,
78
+ const { runRunsCommand } = await import('./cli/commands/runs.js');
79
+ await runRunsCommand({
80
+ memoryDir: values['memory-dir'],
81
+ url: values.url,
358
82
  sessionId: values['session-id'],
359
- limit: 20,
83
+ json: values.json,
360
84
  });
361
- if (runs.length === 0) {
362
- console.log(' No runs found.');
363
- }
364
- else if (values.json) {
365
- console.log(JSON.stringify(runs, null, 2));
366
- }
367
- else {
368
- for (const r of runs) {
369
- const icon = r.status === 'completed' ? (r.success ? '\u2713' : '\u2717') : '\u25cb';
370
- const ts = r.startedAt.slice(0, 16).replace('T', ' ');
371
- const dur = r.completedAt
372
- ? `${Math.round((new Date(r.completedAt).getTime() - new Date(r.startedAt).getTime()) / 1000)}s`
373
- : 'running';
374
- const session = r.sessionId ? ` [${r.sessionId}]` : '';
375
- const parent = r.parentRunId ? ` \u2190 ${r.parentRunId.slice(0, 20)}` : '';
376
- console.log(` ${icon} ${r.runId.slice(0, 30)} ${ts} ${dur} ${r.goal.slice(0, 50)}${session}${parent}`);
377
- if (r.summary)
378
- console.log(` ${r.summary.slice(0, 80)}`);
379
- if (r.finalUrl)
380
- console.log(` ${r.finalUrl}`);
381
- }
382
- }
383
- process.exit(0);
85
+ return;
384
86
  }
385
87
  if (command === 'showcase') {
386
- const { handleShowcase } = await import('./cli-showcase.js');
387
- await handleShowcase({
88
+ const { runShowcaseCommand } = await import('./cli/commands/showcase.js');
89
+ await runShowcaseCommand({
388
90
  url: values.url,
389
91
  script: values.script,
390
92
  capture: values.capture,
@@ -392,58 +94,28 @@ async function main() {
392
94
  highlight: values.highlight,
393
95
  format: values.format,
394
96
  viewport: values.viewport,
395
- output: values.sink,
396
- headless: values.headless ?? true,
97
+ sink: values.sink,
98
+ headless: values.headless,
397
99
  colorScheme: values['color-scheme'],
398
- scale: values.scale ? parseFloat(values.scale) : undefined,
100
+ scale: values.scale,
399
101
  storageState: values['storage-state'],
400
- quality: values['quality-threshold'] ? parseInt(values['quality-threshold']) : undefined,
102
+ qualityThreshold: values['quality-threshold'],
401
103
  });
402
- process.exit(0);
104
+ return;
403
105
  }
404
106
  if (command === 'chrome-debug') {
405
- const { runChromeDebugCommand } = await import('./cli-attach.js');
406
- const port = values['attach-port'] ? parseInt(values['attach-port'], 10) : undefined;
407
- const rc = await runChromeDebugCommand({
408
- port,
107
+ const { handleChromeDebugCommand } = await import('./cli/commands/chrome-debug.js');
108
+ await handleChromeDebugCommand({
109
+ attachPort: values['attach-port'],
409
110
  userDataDir: values['user-data-dir'],
410
111
  quiet: values.quiet,
411
112
  });
412
- process.exit(rc);
113
+ return;
413
114
  }
414
115
  if (command === 'auth') {
415
- const sub = positionals[1];
416
- if (sub === 'save') {
417
- const { handleAuthSave } = await import('./cli-auth.js');
418
- await handleAuthSave({
419
- url: values.url || positionals[2],
420
- output: values['storage-state'] || positionals[3],
421
- });
422
- process.exit(0);
423
- }
424
- if (sub === 'login') {
425
- const { handleAuthLogin } = await import('./cli-auth.js');
426
- await handleAuthLogin({
427
- url: values.url || positionals[2],
428
- output: values['storage-state'],
429
- fill: values.fill,
430
- cookie: values.cookie,
431
- waitFor: values['wait-for'],
432
- waitTimeout: values['wait-timeout'] ? parseInt(values['wait-timeout'], 10) : undefined,
433
- headless: values.headless,
434
- });
435
- process.exit(0);
436
- }
437
- if (sub === 'check') {
438
- const { handleAuthCheck } = await import('./cli-auth.js');
439
- await handleAuthCheck({
440
- path: values['storage-state'] || positionals[2],
441
- origin: positionals[3],
442
- });
443
- process.exit(0);
444
- }
445
- cliError(`Unknown auth subcommand: ${sub || '(none)'}. Use "auth save", "auth login", or "auth check".`);
446
- process.exit(1);
116
+ const { runAuthCommand } = await import('./cli/commands/auth.js');
117
+ await runAuthCommand(values, positionals);
118
+ return;
447
119
  }
448
120
  // `bad attach` is a top-level alias for `bad run --attach`. Every other
449
121
  // flag on `run` (--goal, --url, --model, --provider, --base-url,
@@ -458,1343 +130,40 @@ async function main() {
458
130
  // planner to emit a structured plan, render it, exit. No execution.
459
131
  // terraform plan for browser agents.
460
132
  if (command === 'preview') {
461
- if (!values.goal || !values.url) {
462
- cliError('usage: bad preview --goal "..." --url <url> [--max-steps 12] [--headed] [--json] [--out plan.json]');
463
- process.exit(1);
464
- }
465
- const { handlePreviewCommand, PreviewError } = await import('./cli-preview.js');
466
- try {
467
- const result = await handlePreviewCommand({
468
- goal: values.goal,
469
- url: values.url,
470
- model: values.model,
471
- provider: values.provider,
472
- apiKey: values['api-key'],
473
- baseUrl: values['base-url'],
474
- output: values.sink,
475
- json: values.json,
476
- maxSteps: values['max-steps'] ? parseInt(values['max-steps'], 10) : undefined,
477
- headed: values.headed,
478
- });
479
- process.exit(result.plan ? 0 : 1);
480
- }
481
- catch (err) {
482
- if (err instanceof PreviewError) {
483
- cliError(err.message);
484
- process.exit(1);
485
- }
486
- throw err;
487
- }
133
+ const { runPreviewCommand } = await import('./cli/commands/preview.js');
134
+ await runPreviewCommand({
135
+ goal: values.goal,
136
+ url: values.url,
137
+ model: values.model,
138
+ provider: values.provider,
139
+ apiKey: values['api-key'],
140
+ baseUrl: values['base-url'],
141
+ sink: values.sink,
142
+ json: values.json,
143
+ maxSteps: values['max-steps'],
144
+ headed: values.headed,
145
+ });
146
+ return;
488
147
  }
489
148
  // `bad share <run-id>` — create a bad-app share link, copy to clipboard.
490
149
  if (command === 'share') {
491
- const runId = positionals[1];
492
- if (!runId) {
493
- cliError('usage: bad share <run-id> [--visibility metadata|full|artifacts] [--json]');
494
- process.exit(1);
495
- }
496
- const { handleShareCommand, ShareError } = await import('./cli-share.js');
497
- const visArg = values.visibility;
498
- const visibility = visArg === 'full' || visArg === 'artifacts' || visArg === 'metadata'
499
- ? visArg
500
- : undefined;
501
- try {
502
- await handleShareCommand({
503
- runId,
504
- visibility,
505
- baseUrl: values['bad-app-url'],
506
- apiKey: values['api-key'],
507
- noCopy: values['no-copy'],
508
- json: values.json,
509
- });
510
- process.exit(0);
511
- }
512
- catch (err) {
513
- if (err instanceof ShareError) {
514
- cliError(err.message);
515
- process.exit(1);
516
- }
517
- throw err;
518
- }
150
+ const { runShareCommand } = await import('./cli/commands/share.js');
151
+ await runShareCommand({
152
+ runId: positionals[1],
153
+ visibility: values.visibility,
154
+ badAppUrl: values['bad-app-url'],
155
+ apiKey: values['api-key'],
156
+ noCopy: values['no-copy'],
157
+ json: values.json,
158
+ });
159
+ return;
519
160
  }
520
161
  if (command !== 'run' && command !== 'attach') {
521
162
  cliError(`Unknown command: ${command}. Use "run", "attach", "preview", "runs", "view", "share", "chrome-debug", "design-audit", "showcase", "auth", "jobs", or "reports".`);
522
163
  process.exit(1);
523
164
  }
524
- // Validate inputs
525
- if (!values.goal && !values.cases && !values['cases-json'] && !values['resume-run'] && !values['fork-run']) {
526
- cliError('provide --goal "..." --url "..." for a single task, --cases ./cases.json (or --cases-json \'[...]\') for a suite, or --resume-run / --fork-run <runId>.');
527
- process.exit(1);
528
- }
529
- // Load config file, then overlay CLI flags
530
- const fileConfig = await loadConfig(values.config);
531
- const mode = values.mode;
532
- if (mode && !RUN_MODES.includes(mode)) {
533
- cliError(`unknown mode "${mode}". Valid modes: ${RUN_MODES.join(', ')}`);
534
- process.exit(1);
535
- }
536
- const profile = values.profile;
537
- if (profile && !DRIVER_PROFILES.includes(profile)) {
538
- cliError(`unknown profile "${profile}". Valid profiles: ${DRIVER_PROFILES.join(', ')}`);
539
- process.exit(1);
540
- }
541
- // Build CLI overrides (only set values that were explicitly passed)
542
- const cliOverrides = {};
543
- if (values.model)
544
- cliOverrides.model = values.model;
545
- if (values.provider)
546
- cliOverrides.provider = values.provider;
547
- if (values['model-adaptive'] !== undefined)
548
- cliOverrides.adaptiveModelRouting = values['model-adaptive'];
549
- if (values['nav-model'])
550
- cliOverrides.navModel = values['nav-model'];
551
- if (values['nav-provider'])
552
- cliOverrides.navProvider = values['nav-provider'];
553
- if (values['sandbox-backend-type'])
554
- cliOverrides.sandboxBackendType = values['sandbox-backend-type'];
555
- if (values['sandbox-backend-profile'])
556
- cliOverrides.sandboxBackendProfile = values['sandbox-backend-profile'];
557
- if (values['sandbox-backend-provider'])
558
- cliOverrides.sandboxBackendProvider = values['sandbox-backend-provider'];
559
- if (values['api-key'])
560
- cliOverrides.apiKey = values['api-key'];
561
- if (values['base-url'])
562
- cliOverrides.baseUrl = values['base-url'];
563
- if (values['prompt-file']) {
564
- const promptPath = path.resolve(values['prompt-file']);
565
- if (!fs.existsSync(promptPath)) {
566
- cliError(`prompt file not found: ${promptPath}`);
567
- process.exit(1);
568
- }
569
- cliOverrides.systemPrompt = fs.readFileSync(promptPath, 'utf-8').trim();
570
- if (!cliOverrides.systemPrompt) {
571
- cliError(`prompt file is empty: ${promptPath}`);
572
- process.exit(1);
573
- }
574
- }
575
- if (profile)
576
- cliOverrides.profile = profile;
577
- if (values.browser)
578
- cliOverrides.browser = values.browser;
579
- if (values['storage-state'])
580
- cliOverrides.storageState = values['storage-state'];
581
- if (values.concurrency)
582
- cliOverrides.concurrency = parseInt(values.concurrency, 10);
583
- if (values['max-turns'])
584
- cliOverrides.maxTurns = parseInt(values['max-turns'], 10);
585
- if (values['llm-timeout'])
586
- cliOverrides.llmTimeoutMs = parseInt(values['llm-timeout'], 10);
587
- if (values.retries)
588
- cliOverrides.retries = parseInt(values.retries, 10);
589
- if (values['retry-delay-ms'])
590
- cliOverrides.retryDelayMs = parseInt(values['retry-delay-ms'], 10);
591
- if (values['screenshot-interval'])
592
- cliOverrides.screenshotInterval = parseInt(values['screenshot-interval'], 10);
593
- if (values.scout !== undefined ||
594
- values['scout-model'] ||
595
- values['scout-provider'] ||
596
- values['scout-vision'] !== undefined ||
597
- values['scout-max-candidates'] ||
598
- values['scout-min-top-score'] ||
599
- values['scout-max-score-gap']) {
600
- cliOverrides.scout = {
601
- ...(cliOverrides.scout ?? {}),
602
- };
603
- if (values.scout !== undefined)
604
- cliOverrides.scout.enabled = values.scout;
605
- if (values['scout-model'])
606
- cliOverrides.scout.model = values['scout-model'];
607
- if (values['scout-provider']) {
608
- cliOverrides.scout.provider = values['scout-provider'];
609
- }
610
- if (values['scout-vision'] !== undefined)
611
- cliOverrides.scout.useVision = values['scout-vision'];
612
- if (values['scout-max-candidates'])
613
- cliOverrides.scout.maxCandidates = parseInt(values['scout-max-candidates'], 10);
614
- if (values['scout-min-top-score'])
615
- cliOverrides.scout.minTopScore = parseInt(values['scout-min-top-score'], 10);
616
- if (values['scout-max-score-gap'])
617
- cliOverrides.scout.maxScoreGap = parseInt(values['scout-max-score-gap'], 10);
618
- }
619
- if (values.timeout)
620
- cliOverrides.timeoutMs = parseInt(values.timeout, 10);
621
- if (values['quality-threshold'])
622
- cliOverrides.qualityThreshold = parseInt(values['quality-threshold'], 10);
623
- if (values['trace-scoring'] !== undefined || values['trace-ttl-days']) {
624
- cliOverrides.memory = {
625
- ...(cliOverrides.memory ?? {}),
626
- };
627
- if (values['trace-scoring'] !== undefined)
628
- cliOverrides.memory.traceScoring = values['trace-scoring'];
629
- if (values['trace-ttl-days'])
630
- cliOverrides.memory.traceTtlDays = parseInt(values['trace-ttl-days'], 10);
631
- }
632
- if (values.sink)
633
- cliOverrides.outputDir = values.sink;
634
- if (values.headless !== undefined)
635
- cliOverrides.headless = values.headless;
636
- if (values.proxy)
637
- cliOverrides.proxy = values.proxy;
638
- if (values.vision !== undefined)
639
- cliOverrides.vision = values.vision;
640
- if (values['vision-strategy'])
641
- cliOverrides.visionStrategy = values['vision-strategy'];
642
- if (values['observation-mode'])
643
- cliOverrides.observationMode = values['observation-mode'];
644
- if (values['goal-verification'] !== undefined)
645
- cliOverrides.goalVerification = values['goal-verification'];
646
- if (values.planner === true)
647
- cliOverrides.plannerEnabled = true;
648
- if (values['planner-mode']) {
649
- const plannerMode = values['planner-mode'];
650
- if (plannerMode !== 'always' && plannerMode !== 'auto') {
651
- cliError('--planner-mode must be "always" or "auto"');
652
- process.exit(1);
653
- }
654
- cliOverrides.plannerMode = plannerMode;
655
- }
656
- if (values.extension?.length ||
657
- values['user-data-dir'] ||
658
- values.wallet !== undefined ||
659
- values['wallet-auto-approve'] !== undefined ||
660
- values['wallet-password'] ||
661
- values['wallet-seed-url']?.length ||
662
- values['wallet-preflight'] !== undefined ||
663
- values['wallet-chain-id'] ||
664
- values['wallet-chain-rpc-url']) {
665
- cliOverrides.wallet = {};
666
- if (values.extension?.length)
667
- cliOverrides.wallet.extensionPaths = values.extension;
668
- if (values['user-data-dir'])
669
- cliOverrides.wallet.userDataDir = values['user-data-dir'];
670
- if (values.wallet !== undefined)
671
- cliOverrides.wallet.enabled = values.wallet;
672
- if (values['wallet-auto-approve'] !== undefined) {
673
- cliOverrides.wallet.autoApprove = values['wallet-auto-approve'];
674
- }
675
- if (values['wallet-password']) {
676
- cliOverrides.wallet.password = values['wallet-password'];
677
- }
678
- if (values['wallet-seed-url']?.length ||
679
- values['wallet-preflight'] !== undefined ||
680
- values['wallet-chain-id'] ||
681
- values['wallet-chain-rpc-url']) {
682
- cliOverrides.wallet.preflight = {};
683
- if (values['wallet-seed-url']?.length) {
684
- cliOverrides.wallet.preflight.seedUrls = values['wallet-seed-url'];
685
- }
686
- if (values['wallet-preflight'] !== undefined) {
687
- cliOverrides.wallet.preflight.enabled = values['wallet-preflight'];
688
- }
689
- if (values['wallet-chain-id'] || values['wallet-chain-rpc-url']) {
690
- cliOverrides.wallet.preflight.chain = {};
691
- if (values['wallet-chain-id']) {
692
- const parsedChainId = parseInt(values['wallet-chain-id'], 10);
693
- if (!Number.isFinite(parsedChainId)) {
694
- throw new Error(`Invalid --wallet-chain-id value: ${values['wallet-chain-id']}`);
695
- }
696
- cliOverrides.wallet.preflight.chain.id = parsedChainId;
697
- }
698
- if (values['wallet-chain-rpc-url']) {
699
- cliOverrides.wallet.preflight.chain.rpcUrl = values['wallet-chain-rpc-url'];
700
- }
701
- }
702
- }
703
- }
704
- if (values['profile-dir'])
705
- cliOverrides.profileDir = values['profile-dir'];
706
- if (values['cdp-url'])
707
- cliOverrides.cdpUrl = values['cdp-url'];
708
- // --attach resolves to cdpUrl by probing a running Chrome's DevTools
709
- // endpoint. Done here (pre-config-merge) so the existing cdpUrl path at
710
- // cli.ts:916 takes over unchanged. Conflicts with wallet/extension/profile
711
- // flags are surfaced up-front rather than silently ignored.
712
- if (values.attach) {
713
- if (values['cdp-url']) {
714
- cliError('--attach and --cdp-url are mutually exclusive (both select a CDP endpoint). Use one.');
715
- process.exit(1);
716
- }
717
- const { resolveAttachEndpoint, validateAttachConflicts } = await import('./cli-attach.js');
718
- const conflicts = validateAttachConflicts({
719
- walletEnabled: Boolean(cliOverrides.wallet?.enabled) || Boolean(values.extension?.length),
720
- profileDir: values['profile-dir'],
721
- extensionPaths: values.extension,
722
- userDataDir: values['user-data-dir'],
723
- });
724
- if (!conflicts.ok) {
725
- for (const err of conflicts.errors)
726
- cliError(err);
727
- process.exit(1);
728
- }
729
- const port = values['attach-port'] ? parseInt(values['attach-port'], 10) : undefined;
730
- if (port !== undefined && !Number.isFinite(port)) {
731
- cliError(`Invalid --attach-port value: ${values['attach-port']}`);
732
- process.exit(1);
733
- }
734
- const info = await resolveAttachEndpoint({ port }).catch((err) => {
735
- cliError(err instanceof Error ? err.message : String(err));
736
- process.exit(1);
737
- });
738
- cliOverrides.cdpUrl = info.webSocketDebuggerUrl;
739
- if (!values.quiet)
740
- cliLog('attach', `connected to ${info.browser ?? 'Chrome'}`);
741
- }
742
- if (values.memory !== undefined || values['memory-dir']) {
743
- cliOverrides.memory = {
744
- ...(cliOverrides.memory ?? {}),
745
- };
746
- if (values.memory !== undefined)
747
- cliOverrides.memory.enabled = values.memory;
748
- if (values['memory-dir'])
749
- cliOverrides.memory.dir = values['memory-dir'];
750
- }
751
- // Resource blocking
752
- if (values['block-analytics'] || values['block-images'] || values['block-media']) {
753
- cliOverrides.resourceBlocking = {};
754
- if (values['block-analytics'])
755
- cliOverrides.resourceBlocking.blockAnalytics = true;
756
- if (values['block-images'])
757
- cliOverrides.resourceBlocking.blockImages = true;
758
- if (values['block-media'])
759
- cliOverrides.resourceBlocking.blockMedia = true;
760
- }
761
- // Profile presets apply only when equivalent flags were not explicitly set.
762
- if (profile === 'stealth') {
763
- if (values.headless === undefined)
764
- cliOverrides.headless = false;
765
- if (values['goal-verification'] === undefined)
766
- cliOverrides.goalVerification = true;
767
- if (!values['screenshot-interval'])
768
- cliOverrides.screenshotInterval = 2;
769
- if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
770
- cliOverrides.resourceBlocking = {
771
- ...(cliOverrides.resourceBlocking ?? {}),
772
- blockAnalytics: true,
773
- };
774
- }
775
- cliOverrides.microPlan = {
776
- ...(cliOverrides.microPlan ?? {}),
777
- enabled: true,
778
- maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
779
- };
780
- }
781
- else if (profile === 'benchmark-webbench' || profile === 'benchmark-webbench-stealth') {
782
- if (!values['llm-timeout'])
783
- cliOverrides.llmTimeoutMs = 20_000;
784
- cliOverrides.compactFirstTurn = true;
785
- if (!values.retries)
786
- cliOverrides.retries = 1;
787
- if (!values['retry-delay-ms'])
788
- cliOverrides.retryDelayMs = 250;
789
- if (values.vision === undefined)
790
- cliOverrides.vision = false;
791
- if (!values['screenshot-interval'])
792
- cliOverrides.screenshotInterval = 0;
793
- if (values['goal-verification'] === undefined)
794
- cliOverrides.goalVerification = true;
795
- if (profile === 'benchmark-webbench-stealth' && values.headless === undefined) {
796
- cliOverrides.headless = false;
797
- }
798
- if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
799
- cliOverrides.resourceBlocking = {
800
- ...(cliOverrides.resourceBlocking ?? {}),
801
- blockAnalytics: true,
802
- blockImages: true,
803
- blockMedia: true,
804
- };
805
- }
806
- cliOverrides.microPlan = {
807
- ...(cliOverrides.microPlan ?? {}),
808
- enabled: true,
809
- maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
810
- };
811
- }
812
- else if (profile === 'benchmark-webvoyager') {
813
- if (values.vision === undefined)
814
- cliOverrides.vision = true;
815
- if (!values['screenshot-interval'])
816
- cliOverrides.screenshotInterval = 2;
817
- if (values['goal-verification'] === undefined)
818
- cliOverrides.goalVerification = true;
819
- cliOverrides.microPlan = {
820
- ...(cliOverrides.microPlan ?? {}),
821
- enabled: true,
822
- maxActionsPerTurn: cliOverrides.microPlan?.maxActionsPerTurn ?? 2,
823
- };
824
- }
825
- // Mode presets apply only when equivalent flags were not explicitly set
826
- // AND the profile didn't already set them. This lets benchmark profiles
827
- // (e.g. benchmark-webvoyager) enable vision/screenshots without fast-explore
828
- // clobbering those values.
829
- if (mode === 'fast-explore') {
830
- if (values.vision === undefined && cliOverrides.vision === undefined)
831
- cliOverrides.vision = false;
832
- if (!values['screenshot-interval'] && cliOverrides.screenshotInterval === undefined)
833
- cliOverrides.screenshotInterval = 0;
834
- if (values['goal-verification'] === undefined)
835
- cliOverrides.goalVerification = true;
836
- if (values['quality-threshold'] === undefined)
837
- cliOverrides.qualityThreshold = 0;
838
- if (!values['block-analytics'] && !values['block-images'] && !values['block-media']) {
839
- cliOverrides.resourceBlocking = {
840
- ...(cliOverrides.resourceBlocking ?? {}),
841
- blockAnalytics: true,
842
- };
843
- }
844
- }
845
- else if (mode === 'full-evidence') {
846
- if (values.vision === undefined && cliOverrides.vision === undefined)
847
- cliOverrides.vision = true;
848
- if (!values['screenshot-interval'] && cliOverrides.screenshotInterval === undefined)
849
- cliOverrides.screenshotInterval = 3;
850
- if (values['goal-verification'] === undefined)
851
- cliOverrides.goalVerification = true;
852
- }
853
- // Vision and hybrid observation modes require screenshots and a stable
854
- // 1024×768 coordinate space.
855
- if (cliOverrides.observationMode === 'vision' || cliOverrides.observationMode === 'hybrid') {
856
- if (cliOverrides.vision === undefined)
857
- cliOverrides.vision = true;
858
- if (cliOverrides.visionStrategy === undefined)
859
- cliOverrides.visionStrategy = 'always';
860
- if (cliOverrides.screenshotInterval === undefined || cliOverrides.screenshotInterval === 0) {
861
- cliOverrides.screenshotInterval = 1;
862
- }
863
- if (!cliOverrides.viewport) {
864
- cliOverrides.viewport = { width: 1024, height: 768 };
865
- }
866
- }
867
- const driverConfig = mergeConfig(fileConfig, cliOverrides);
868
- const launchPlan = buildBrowserLaunchPlan(driverConfig);
869
- const quiet = values.quiet;
870
- for (const warning of launchPlan.warnings) {
871
- if (!quiet) {
872
- cliWarn(warning);
873
- }
874
- }
875
- if (launchPlan.errors.length > 0) {
876
- for (const error of launchPlan.errors) {
877
- cliError(error);
878
- }
879
- process.exit(1);
880
- }
881
- // Dynamic imports keep startup fast. Prefer patchright for lower browser
882
- // automation fingerprinting, falling back to regular Playwright.
883
- const isStealthProfile = launchPlan.profile.includes('stealth');
884
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
885
- let browserLib;
886
- try {
887
- browserLib = await import('patchright');
888
- }
889
- catch {
890
- browserLib = await import('playwright');
891
- }
892
- const { chromium, firefox, webkit } = browserLib;
893
- const { PlaywrightDriver } = await import('./drivers/playwright.js');
894
- const { TestRunner } = await import('./test-runner.js');
895
- const { FilesystemSink } = await import('./artifacts/filesystem-sink.js');
896
- const { loadExtensions } = await import('./extensions/loader.js');
897
- const { TurnEventBus } = await import('./runner/events.js');
898
- // Optional live event bus + SSE viewer. Constructed only when `--live` is
899
- // passed; otherwise the runner uses an internal no-op bus and pays nothing.
900
- // The bus is shared across the entire suite so a single SSE connection
901
- // observes all turns of all test cases. When `--stream <url>` is passed
902
- // the bus is always created so the webhook streamer can subscribe.
903
- const liveEnabled = values.live === true;
904
- const streamUrl = typeof values.stream === 'string' && values.stream.length > 0 ? values.stream : undefined;
905
- const needsBus = liveEnabled || !!streamUrl;
906
- const liveBus = needsBus ? new TurnEventBus() : undefined;
907
- const liveCancelController = liveEnabled ? new AbortController() : undefined;
908
- let liveViewHandle;
909
- if (liveEnabled && liveBus) {
910
- const { runLiveView } = await import('./cli-view-live.js');
911
- liveViewHandle = await runLiveView({
912
- bus: liveBus,
913
- ...(liveCancelController ? { cancelController: liveCancelController } : {}),
914
- port: values.port ? parseInt(values.port, 10) : undefined,
915
- noOpen: values['no-open'] === true,
916
- });
917
- }
918
- // Webhook streamer. When `--stream <url>` is passed, subscribe
919
- // to the bus and POST every event to <url> as it fires. Auth via
920
- // `--stream-token` or $BAD_STREAM_TOKEN. Non-fatal on failure — the
921
- // canonical record is always events.jsonl on disk.
922
- let webhookStreamer;
923
- if (streamUrl && liveBus) {
924
- const { WebhookStreamer } = await import('./runner/stream-webhook.js');
925
- const token = values['stream-token'] || process.env.BAD_STREAM_TOKEN;
926
- const streamId = `stream_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
927
- webhookStreamer = new WebhookStreamer({
928
- url: streamUrl,
929
- authToken: token,
930
- streamId,
931
- onError: (err, dropped) => {
932
- if (!values.json)
933
- cliWarn(`stream: ${err.message} (dropped ${dropped} event${dropped === 1 ? '' : 's'})`);
934
- },
935
- }).attach(liveBus);
936
- if (!values.json)
937
- cliLog('stream', `POST → ${streamUrl} (id ${streamId})`);
938
- }
939
- // Interrupt controller. `--interrupt` enables keyboard pause/
940
- // resume/abort during a run. No-op in non-TTY (CI, piped output).
941
- let interruptController;
942
- let detachInterrupt;
943
- if (values.interrupt === true && process.stdin.isTTY) {
944
- const { InterruptController } = await import('./runner/interrupt-controller.js');
945
- interruptController = new InterruptController({
946
- onStatus: (msg) => { if (!values.json)
947
- cliLog('interrupt', msg); },
948
- });
949
- detachInterrupt = interruptController.attach();
950
- }
951
- // Auto-discover bad.config.{ts,mjs,js,...} from cwd plus any explicit
952
- // --extension paths. Failures are reported but never fatal: a broken user
953
- // config should warn, not abort the run.
954
- const explicitExtPaths = (values.extension ?? []);
955
- const explicitExtArr = Array.isArray(explicitExtPaths)
956
- ? explicitExtPaths
957
- : explicitExtPaths
958
- ? [explicitExtPaths]
959
- : [];
960
- // Domain skills: markdown-based per-host rule libraries under
961
- // skills/domain/<host>/SKILL.md. They plumb into the same BadExtension
962
- // pipeline as user `bad.config.mjs` files via addRulesForDomain, so the
963
- // existing setExtensionRules injection at brain/index.ts:899 picks them up
964
- // with no second injection site. Gated by BAD_DOMAIN_SKILLS_DISABLED=1.
965
- const domainSkillsDisabled = process.env.BAD_DOMAIN_SKILLS_DISABLED === '1';
966
- let domainSkillExtension;
967
- let domainSkillsLoadedCount = 0;
968
- if (!domainSkillsDisabled) {
969
- const { loadDomainSkills, buildDomainSkillExtension } = await import('./skills/domain-loader.js');
970
- const domainLoad = await loadDomainSkills();
971
- if (domainLoad.skills.length > 0) {
972
- domainSkillExtension = buildDomainSkillExtension(domainLoad.skills);
973
- domainSkillsLoadedCount = domainLoad.skills.length;
974
- }
975
- if (domainLoad.errors.length > 0 && !values.json) {
976
- for (const err of domainLoad.errors) {
977
- cliWarn(`domain-skill load failed: ${err.path} — ${err.error}`);
978
- }
979
- }
980
- }
981
- const extensionLoad = await loadExtensions({ explicitPaths: explicitExtArr });
982
- if (extensionLoad.loadedFrom.length > 0 && !values.json) {
983
- cliLog('extensions', `loaded ${extensionLoad.loadedFrom.length}: ${extensionLoad.loadedFrom.join(', ')}`);
984
- }
985
- if (extensionLoad.errors.length > 0 && !values.json) {
986
- for (const err of extensionLoad.errors) {
987
- cliWarn(`extension load failed: ${err.path} — ${err.error}`);
988
- }
989
- }
990
- if (domainSkillExtension && domainSkillsLoadedCount > 0) {
991
- // Re-resolve the bundle with the domain-skill synthetic extension
992
- // merged in. Resolving twice is cheap (the list is short) and keeps
993
- // domainSkillExtension from having to plumb through a separate wire.
994
- const { resolveExtensions } = await import('./extensions/types.js');
995
- const combined = resolveExtensions([...extensionLoad.resolved.extensions, domainSkillExtension]);
996
- extensionLoad.resolved = combined;
997
- if (!values.json)
998
- cliLog('skills', `domain: ${domainSkillsLoadedCount} loaded`);
999
- }
1000
- // Macros. Loaded alongside domain skills; gated by BAD_MACROS_DISABLED=1.
1001
- // The registry is passed to PlaywrightDriver and its promptBlock to BrowserAgent.
1002
- const macrosDisabled = process.env.BAD_MACROS_DISABLED === '1';
1003
- let macroRegistry;
1004
- if (!macrosDisabled) {
1005
- const { loadMacros, buildMacroRegistry } = await import('./skills/macro-loader.js');
1006
- const macroLoad = await loadMacros();
1007
- if (macroLoad.errors.length > 0 && !values.json) {
1008
- for (const err of macroLoad.errors) {
1009
- cliWarn(`macro load failed: ${err.path} — ${err.error}`);
1010
- }
1011
- }
1012
- if (macroLoad.macros.length > 0) {
1013
- macroRegistry = buildMacroRegistry(macroLoad.macros);
1014
- if (!values.json)
1015
- cliLog('skills', `macros: ${macroLoad.macros.length} loaded`);
1016
- }
1017
- }
1018
- const concurrency = launchPlan.concurrency;
1019
- const maxTurns = driverConfig.maxTurns ?? 30;
1020
- const screenshotInterval = driverConfig.screenshotInterval ?? 5;
1021
- const timeoutMs = driverConfig.timeoutMs ?? 600_000;
1022
- const browserName = driverConfig.browser ?? 'chromium';
1023
- const debug = values.debug;
1024
- const sinkDir = driverConfig.outputDir ?? './agent-results';
1025
- const resolvedProvider = driverConfig.provider || 'openai';
1026
- const resolvedApiKey = resolveProviderApiKey(resolvedProvider, driverConfig.apiKey);
1027
- const resolvedModel = resolveProviderModelName(resolvedProvider, driverConfig.model, {
1028
- sandboxBackendType: resolvedProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
1029
- });
1030
- const resolvedNavProvider = driverConfig.navProvider || resolvedProvider;
1031
- const resolvedNavModel = driverConfig.navModel
1032
- ? resolveProviderModelName(resolvedNavProvider, driverConfig.navModel, {
1033
- sandboxBackendType: resolvedNavProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
1034
- })
1035
- : undefined;
1036
- const resolvedSupervisorProvider = driverConfig.supervisor?.provider || resolvedProvider;
1037
- const resolvedSupervisorModel = resolveProviderModelName(resolvedSupervisorProvider, driverConfig.supervisor?.model || resolvedModel, {
1038
- sandboxBackendType: resolvedSupervisorProvider === 'sandbox-backend' ? driverConfig.sandboxBackendType : undefined,
1039
- });
1040
- const config = {
1041
- ...toAgentConfig(driverConfig),
1042
- model: resolvedModel,
1043
- ...(resolvedNavModel ? { navModel: resolvedNavModel } : {}),
1044
- apiKey: resolvedApiKey,
1045
- baseUrl: driverConfig.baseUrl || process.env.LLM_BASE_URL,
1046
- supervisor: driverConfig.supervisor
1047
- ? {
1048
- ...driverConfig.supervisor,
1049
- provider: resolvedSupervisorProvider,
1050
- model: resolvedSupervisorModel,
1051
- }
1052
- : undefined,
1053
- debug,
1054
- walletMode: Boolean(driverConfig.wallet?.enabled),
1055
- walletAddress: driverConfig.wallet?.address,
1056
- };
1057
- // Create project store for memory + run registry
1058
- const memoryEnabled = driverConfig.memory?.enabled === true;
1059
- const projectStore = memoryEnabled
1060
- ? new ProjectStore(driverConfig.memory?.dir)
1061
- : undefined;
1062
- const runRegistry = projectStore
1063
- ? new RunRegistry(projectStore.getRoot())
1064
- : undefined;
1065
- // Build test cases
1066
- let cases;
1067
- if (values['resume-run'] || values['fork-run']) {
1068
- // Resume or fork from a previous run
1069
- if (!runRegistry) {
1070
- cliError('--resume-run and --fork-run require memory to be enabled');
1071
- process.exit(1);
1072
- }
1073
- const isResume = Boolean(values['resume-run']);
1074
- const sourceRunId = (values['resume-run'] || values['fork-run']);
1075
- const scenario = isResume
1076
- ? runRegistry.buildResumeScenario(sourceRunId, values.goal)
1077
- : runRegistry.buildForkScenario(sourceRunId, values.goal || '');
1078
- if (!scenario) {
1079
- cliError(`run "${sourceRunId}" not found in registry`);
1080
- process.exit(1);
1081
- }
1082
- if (!isResume && !values.goal) {
1083
- cliError('--fork-run requires --goal');
1084
- process.exit(1);
1085
- }
1086
- cases = [{
1087
- id: `${isResume ? 'resume' : 'fork'}-${sourceRunId.slice(0, 20)}`,
1088
- name: scenario.goal.slice(0, 60),
1089
- startUrl: values.url || scenario.startUrl,
1090
- goal: scenario.goal,
1091
- allowedDomains: parseAllowedDomains(values['allowed-domains']),
1092
- maxTurns,
1093
- timeoutMs,
1094
- priority: 0,
1095
- sessionId: scenario.sessionId,
1096
- parentRunId: scenario.parentRunId,
1097
- }];
1098
- }
1099
- else if (values['cases-json'] || values.cases) {
1100
- const raw = values['cases-json'] || fs.readFileSync(path.resolve(values.cases), 'utf-8');
1101
- const parsed = JSON.parse(raw);
1102
- const rawCases = Array.isArray(parsed) ? parsed : [parsed];
1103
- // Ensure required fields — spread raw case first so explicit fields become defaults
1104
- cases = rawCases.map((c, i) => ({
1105
- id: c.id || `case-${i}`,
1106
- name: c.name || c.goal?.slice(0, 60) || `Case ${i}`,
1107
- startUrl: c.startUrl || c.url || values.url || '',
1108
- goal: c.goal || '',
1109
- allowedDomains: Array.isArray(c.allowedDomains)
1110
- ? c.allowedDomains.filter((domain) => typeof domain === 'string' && domain.length > 0)
1111
- : parseAllowedDomains(values['allowed-domains']),
1112
- maxTurns: c.maxTurns || maxTurns,
1113
- timeoutMs: c.timeoutMs || timeoutMs,
1114
- priority: c.priority ?? i,
1115
- }));
1116
- }
1117
- else {
1118
- cases = [{
1119
- id: 'cli-task',
1120
- name: values.goal.slice(0, 60),
1121
- startUrl: values.url || '',
1122
- goal: values.goal,
1123
- allowedDomains: parseAllowedDomains(values['allowed-domains']),
1124
- maxTurns,
1125
- timeoutMs,
1126
- priority: 0,
1127
- sessionId: values['session-id'],
1128
- }];
1129
- }
1130
- // Apply --session-id to all cases from file too
1131
- if (values['session-id'] && cases.length > 0 && cases[0].id !== 'cli-task') {
1132
- const sid = values['session-id'];
1133
- cases = cases.map(c => ({ ...c, sessionId: c.sessionId || sid }));
1134
- }
1135
- const persona = values.persona;
1136
- if (persona) {
1137
- if (!isPersonaId(persona)) {
1138
- cliError(`unknown persona "${persona}". Valid personas: ${listPersonaIds().join(', ')}`);
1139
- process.exit(1);
1140
- }
1141
- cases = cases.map((c) => ({
1142
- ...c,
1143
- goal: withPersonaDirective({
1144
- persona,
1145
- goal: c.goal,
1146
- startUrl: c.startUrl,
1147
- }),
1148
- }));
1149
- }
1150
- const renderer = (!quiet && !values.json) ? new CliRenderer({ debug }) : null;
1151
- if (renderer) {
1152
- const version = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version;
1153
- renderer.banner({
1154
- version,
1155
- provider: config.provider || 'openai',
1156
- model: config.model,
1157
- browser: browserName,
1158
- testCount: cases.length,
1159
- concurrency,
1160
- mode: mode || undefined,
1161
- profile: driverConfig.profile,
1162
- adaptiveRouting: config.adaptiveModelRouting ? {
1163
- navProvider: config.navProvider || config.provider || 'openai',
1164
- navModel: config.navModel || 'gpt-4.1-mini',
1165
- } : undefined,
1166
- outputDir: sinkDir,
1167
- });
1168
- }
1169
- // Set up artifact sink
1170
- const sink = new FilesystemSink(path.resolve(sinkDir));
1171
- const videoDir = path.join(sinkDir, '_videos');
1172
- const viewport = launchPlan.viewport;
1173
- const storageStatePath = driverConfig.storageState
1174
- ? path.resolve(driverConfig.storageState)
1175
- : undefined;
1176
- if (storageStatePath && !fs.existsSync(storageStatePath)) {
1177
- cliError(`storage state file not found: ${storageStatePath}`);
1178
- process.exit(1);
1179
- }
1180
- if (launchPlan.persistentContext && !launchPlan.cdpUrl && browserName !== 'chromium') {
1181
- const feature = launchPlan.walletMode ? 'Wallet mode' : '--profile-dir';
1182
- throw new Error(`${feature} requires Chromium. Set --browser chromium.`);
1183
- }
1184
- // Ensure clean exit on interrupt
1185
- process.on('SIGINT', () => { renderer?.destroy(); process.exit(130); });
1186
- process.on('SIGTERM', () => { renderer?.destroy(); process.exit(143); });
1187
- renderer?.launchStart(browserName);
1188
- // Set up browser
1189
- let browser;
1190
- let persistentContext;
1191
- let stopWalletAutoApprover;
1192
- const launchDiagnostics = {};
1193
- // CDP connection — attach to an existing browser (Atlas, Chrome, Brave, etc.)
1194
- let cdpUrl = launchPlan.cdpUrl || process.env.BROWSER_ENDPOINT;
1195
- let cdpConnected = false;
1196
- if (cdpUrl) {
1197
- // Auto-discover WebSocket URL from HTTP endpoint
1198
- if (cdpUrl.startsWith('http://') || cdpUrl.startsWith('https://')) {
1199
- try {
1200
- const versionUrl = cdpUrl.replace(/\/$/, '') + '/json/version';
1201
- const res = await fetch(versionUrl);
1202
- const info = await res.json();
1203
- if (info.webSocketDebuggerUrl) {
1204
- if (info.Browser && !quiet)
1205
- cliLog('cdp', `connected to ${info.Browser}`);
1206
- cdpUrl = info.webSocketDebuggerUrl;
1207
- }
1208
- }
1209
- catch {
1210
- // Fall through — try the URL as-is
1211
- }
1212
- }
1213
- const cdpStartedAt = Date.now();
1214
- if (cdpUrl.includes('/devtools/') || browserName === 'chromium') {
1215
- browser = await chromium.connectOverCDP(cdpUrl);
1216
- }
1217
- else {
1218
- const browserType = browserName === 'firefox' ? firefox : browserName === 'webkit' ? webkit : chromium;
1219
- browser = await browserType.connect(cdpUrl);
1220
- }
1221
- launchDiagnostics.browserLaunchMs = Date.now() - cdpStartedAt;
1222
- launchDiagnostics.cdpUrl = cdpUrl;
1223
- cdpConnected = true;
1224
- }
1225
- else if (launchPlan.persistentContext) {
1226
- for (const extensionPath of launchPlan.extensionPaths) {
1227
- if (!fs.existsSync(extensionPath)) {
1228
- throw new Error(`Wallet extension path does not exist: ${extensionPath}`);
1229
- }
1230
- }
1231
- const userDataDir = launchPlan.userDataDir ?? path.resolve(launchPlan.walletMode ? '.agent-wallet-profile' : '.agent-profile');
1232
- fs.mkdirSync(userDataDir, { recursive: true });
1233
- const persistentLaunchStartedAt = Date.now();
1234
- persistentContext = await chromium.launchPersistentContext(userDataDir, {
1235
- channel: isStealthProfile ? 'chrome' : 'chromium',
1236
- headless: launchPlan.headless,
1237
- args: launchPlan.browserArgs,
1238
- viewport,
1239
- recordVideo: { dir: videoDir, size: viewport },
1240
- ...(launchPlan.proxyServer
1241
- ? { proxy: { server: launchPlan.proxyServer, ...(launchPlan.proxyBypass ? { bypass: launchPlan.proxyBypass } : {}) } }
1242
- : {}),
1243
- ...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
1244
- });
1245
- launchDiagnostics.browserLaunchMs = Date.now() - persistentLaunchStartedAt;
1246
- await applyStorageStateToPersistentContext(persistentContext, storageStatePath);
1247
- if (launchPlan.walletMode) {
1248
- const walletConfig = driverConfig.wallet ?? {};
1249
- // Intercept page-level JSON-RPC so dApps see wallet balances from the
1250
- // local Anvil fork. Only forward user-specific calls (eth_getBalance
1251
- // for the wallet, eth_call with wallet address in calldata). Pool data
1252
- // and protocol calls go to real endpoints for reliability.
1253
- const walletRpcUrl = walletConfig.preflight?.chain?.rpcUrl;
1254
- if (walletRpcUrl) {
1255
- // Default to Anvil's first derived address if no wallet address configured
1256
- const walletAddrFull = (walletConfig.address ?? '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266').toLowerCase();
1257
- const walletAddrHex = walletAddrFull.replace('0x', '');
1258
- await persistentContext.route('**/*', async (route) => {
1259
- try {
1260
- const frame = route.request().frame();
1261
- if (frame && frame.url().startsWith('chrome-extension://')) {
1262
- await route.continue();
1263
- return;
1264
- }
1265
- }
1266
- catch {
1267
- await route.continue();
1268
- return;
1269
- }
1270
- if (route.request().method() !== 'POST') {
1271
- await route.continue();
1272
- return;
1273
- }
1274
- const ct = route.request().headers()['content-type'] ?? '';
1275
- if (!ct.includes('json')) {
1276
- await route.continue();
1277
- return;
1278
- }
1279
- const postData = route.request().postData();
1280
- if (!postData) {
1281
- await route.continue();
1282
- return;
1283
- }
1284
- try {
1285
- const body = JSON.parse(postData);
1286
- const items = Array.isArray(body) ? body : [body];
1287
- // Check if any item involves the wallet (balance, contract call, simulation)
1288
- const isUserQuery = items.some((item) => {
1289
- const method = item.method;
1290
- if (!method)
1291
- return false;
1292
- if (method === 'eth_getBalance') {
1293
- const params = item.params;
1294
- return params?.[0]?.toLowerCase() === walletAddrFull;
1295
- }
1296
- if (method === 'eth_call' || method === 'eth_estimateGas') {
1297
- const params = item.params;
1298
- const txObj = params?.[0];
1299
- if (!txObj)
1300
- return false;
1301
- const from = txObj.from?.toLowerCase() ?? '';
1302
- const data = txObj.data?.toLowerCase() ?? '';
1303
- return from === walletAddrFull || data.includes(walletAddrHex);
1304
- }
1305
- if (method === 'eth_getTransactionCount') {
1306
- const params = item.params;
1307
- return params?.[0]?.toLowerCase() === walletAddrFull;
1308
- }
1309
- return false;
1310
- });
1311
- if (!isUserQuery) {
1312
- await route.continue();
1313
- return;
1314
- }
1315
- // Normalize: some dApps (Aave) omit jsonrpc/id — Anvil requires them
1316
- let nextId = 1;
1317
- const normalized = items.map((item) => {
1318
- const out = { ...item, jsonrpc: '2.0', id: item.id ?? nextId++ };
1319
- delete out.chainId;
1320
- return out;
1321
- });
1322
- const payload = Array.isArray(body) ? normalized : normalized[0];
1323
- const res = await fetch(walletRpcUrl, {
1324
- method: 'POST',
1325
- headers: { 'Content-Type': 'application/json' },
1326
- body: JSON.stringify(payload),
1327
- });
1328
- await route.fulfill({
1329
- status: res.status,
1330
- contentType: 'application/json',
1331
- body: await res.text(),
1332
- });
1333
- }
1334
- catch {
1335
- await route.continue();
1336
- }
1337
- });
1338
- }
1339
- const shouldAutoApprove = walletConfig.autoApprove ?? true;
1340
- if (shouldAutoApprove) {
1341
- stopWalletAutoApprover = await startWalletAutoApprover(persistentContext, {
1342
- password: walletConfig.password,
1343
- tickMs: walletConfig.tickMs,
1344
- actionSelectors: walletConfig.actionSelectors,
1345
- });
1346
- }
1347
- const preflightEnabled = walletConfig.preflight?.enabled ?? true;
1348
- const preflightSeedUrls = walletConfig.preflight?.seedUrls && walletConfig.preflight.seedUrls.length > 0
1349
- ? walletConfig.preflight.seedUrls
1350
- : [...new Set(cases.map((testCase) => testCase.startUrl).filter(Boolean))];
1351
- if (preflightEnabled && preflightSeedUrls.length > 0) {
1352
- const preflight = await runWalletPreflight(persistentContext, {
1353
- seedUrls: preflightSeedUrls,
1354
- password: walletConfig.password,
1355
- actionSelectors: walletConfig.actionSelectors,
1356
- promptPaths: walletConfig.promptPaths,
1357
- connectSelectors: walletConfig.connectSelectors,
1358
- connectorSelectors: walletConfig.connectorSelectors,
1359
- requestAccounts: walletConfig.preflight?.requestAccounts,
1360
- accountsTimeoutMs: walletConfig.preflight?.accountsTimeoutMs,
1361
- maxChainSwitchAttempts: walletConfig.preflight?.maxChainSwitchAttempts,
1362
- chain: walletConfig.preflight?.chain,
1363
- log: quiet ? undefined : (message) => cliLog('wallet', message),
1364
- });
1365
- if (!preflight.ok) {
1366
- const failed = preflight.results.find((resultEntry) => !resultEntry.ready);
1367
- const details = failed?.details ?? 'unknown reason';
1368
- throw new Error(`Wallet preflight failed for ${preflight.failedUrl ?? 'unknown origin'} (${details})`);
1369
- }
1370
- }
1371
- }
1372
- }
1373
- else {
1374
- const browserType = browserName === 'firefox'
1375
- ? firefox
1376
- : browserName === 'webkit'
1377
- ? webkit
1378
- : chromium;
1379
- const browserLaunchStartedAt = Date.now();
1380
- browser = await browserType.launch({
1381
- headless: launchPlan.headless,
1382
- ...(browserName === 'chromium' ? { args: launchPlan.browserArgs } : {}),
1383
- // System Chrome for stealth profiles only — real TLS/JA3 fingerprint
1384
- // fixes anti-bot blocking. But system Chrome renders differently than
1385
- // bundled Chromium on some sites (Allrecipes click timeouts, Amazon
1386
- // layout shifts), so only enable when anti-bot evasion is needed.
1387
- ...(isStealthProfile && browserName === 'chromium' ? { channel: 'chrome' } : {}),
1388
- // Residential/SOCKS5/HTTP proxy — routes all traffic through the proxy
1389
- ...(launchPlan.proxyServer
1390
- ? { proxy: { server: launchPlan.proxyServer, ...(launchPlan.proxyBypass ? { bypass: launchPlan.proxyBypass } : {}) } }
1391
- : {}),
1392
- });
1393
- launchDiagnostics.browserLaunchMs = Date.now() - browserLaunchStartedAt;
1394
- }
1395
- // Headless Chromium sends "HeadlessChrome/..." in the default User-Agent.
1396
- // CDNs like Akamai reject this with ERR_HTTP2_PROTOCOL_ERROR before any JS
1397
- // stealth patches can run. Build a clean UA from the browser version.
1398
- const headlessUserAgent = launchPlan.headless && browser
1399
- ? (() => {
1400
- const ver = browser.version();
1401
- const plat = process.platform;
1402
- const platformToken = plat === 'win32'
1403
- ? 'Windows NT 10.0; Win64; x64'
1404
- : plat === 'linux'
1405
- ? 'X11; Linux x86_64'
1406
- : 'Macintosh; Intel Mac OS X 10_15_7';
1407
- return `Mozilla/5.0 (${platformToken}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${ver} Safari/537.36`;
1408
- })()
1409
- : undefined;
1410
- const driverFactory = async () => {
1411
- const contextStartedAt = Date.now();
1412
- // CDP: reuse the browser's default context (preserves user cookies/sessions).
1413
- // Persistent context: use the already-opened persistent context.
1414
- // Default: create a fresh isolated context.
1415
- let context;
1416
- if (cdpConnected) {
1417
- // Reuse the user's existing browser context — cookies, localStorage, extensions intact
1418
- const contexts = browser.contexts();
1419
- // A freshly-created CDP context still honors egress; the reused contexts[0] is the user's own
1420
- // browser context (see the --cdp-url egress warning in buildBrowserLaunchPlan), left untouched.
1421
- context = contexts[0] ?? await browser.newContext({
1422
- viewport,
1423
- ...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
1424
- });
1425
- }
1426
- else if (persistentContext) {
1427
- context = persistentContext;
1428
- }
1429
- else {
1430
- context = await browser.newContext({
1431
- viewport,
1432
- recordVideo: { dir: videoDir, size: viewport },
1433
- storageState: storageStatePath,
1434
- locale: 'en-US',
1435
- timezoneId: 'America/New_York',
1436
- ...(headlessUserAgent ? { userAgent: headlessUserAgent } : {}),
1437
- // Accept the managed egress proxy's MITM cert (its CA isn't in Chromium's trust store).
1438
- ...(launchPlan.ignoreHTTPSErrors ? { ignoreHTTPSErrors: true } : {}),
1439
- extraHTTPHeaders: {
1440
- 'Accept-Language': 'en-US,en;q=0.9',
1441
- },
1442
- });
1443
- }
1444
- // Stealth patches: only for Playwright-controlled contexts (not real user browsers)
1445
- if (!cdpConnected) {
1446
- await context.addInitScript(`
1447
- // navigator.webdriver — explicit override (backup for --disable-blink-features)
1448
- Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
1449
- // navigator.plugins — empty in headless, non-empty in real browsers
1450
- Object.defineProperty(navigator, 'plugins', {
1451
- get: () => [
1452
- { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
1453
- { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
1454
- { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
1455
- ],
1456
- });
1457
- // navigator.languages — must match Accept-Language header
1458
- Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
1459
- // hardware signals — realistic desktop values
1460
- Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 });
1461
- Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 });
1462
- // window.chrome — full stub matching real Chrome
1463
- if (!window.chrome) window.chrome = {};
1464
- if (!window.chrome.runtime) window.chrome.runtime = { id: undefined };
1465
- if (!window.chrome.app) window.chrome.app = { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } };
1466
- if (!window.chrome.csi) window.chrome.csi = function() { return { onloadT: Date.now(), startE: Date.now(), pageT: Date.now() - performance.timing.navigationStart }; };
1467
- if (!window.chrome.loadTimes) window.chrome.loadTimes = function() { return { commitLoadTime: Date.now() / 1000, connectionInfo: 'h2', finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: Date.now() / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: Date.now() / 1000 - 0.16, startLoadTime: Date.now() / 1000 - 0.16, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true }; };
1468
- // WebGL vendor/renderer — match real GPU values
1469
- try {
1470
- const getParameter = WebGLRenderingContext.prototype.getParameter;
1471
- WebGLRenderingContext.prototype.getParameter = function(parameter) {
1472
- if (parameter === 37445) return 'Intel Inc.';
1473
- if (parameter === 37446) return 'Intel Iris OpenGL Engine';
1474
- return getParameter.call(this, parameter);
1475
- };
1476
- } catch (_) {}
1477
- try {
1478
- const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
1479
- WebGL2RenderingContext.prototype.getParameter = function(parameter) {
1480
- if (parameter === 37445) return 'Intel Inc.';
1481
- if (parameter === 37446) return 'Intel Iris OpenGL Engine';
1482
- return getParameter2.call(this, parameter);
1483
- };
1484
- } catch (_) {}
1485
- // window.outerWidth/outerHeight — 0 in headless, match viewport in real browsers
1486
- if (window.outerWidth === 0) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth });
1487
- if (window.outerHeight === 0) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85 });
1488
- // Patch permissions API — cover all permission types bots commonly mis-handle
1489
- try {
1490
- const origQuery = navigator.permissions.query.bind(navigator.permissions);
1491
- navigator.permissions.query = (params) => {
1492
- const deny = ['notifications', 'geolocation', 'camera', 'microphone', 'payment-handler'];
1493
- if (deny.includes(params.name))
1494
- return Promise.resolve({ state: 'denied', onchange: null });
1495
- return origQuery(params);
1496
- };
1497
- } catch (_) {}
1498
- // Canvas fingerprint noise — add imperceptible per-session noise to canvas readback
1499
- // so each session produces a unique fingerprint (defeats static fingerprint matching)
1500
- try {
1501
- const seed = Math.random() * 0xffff | 0;
1502
- const noisify = (canvas) => {
1503
- try {
1504
- const ctx = canvas.getContext('2d');
1505
- if (!ctx) return;
1506
- const { width: w, height: h } = canvas;
1507
- if (w === 0 || h === 0) return;
1508
- const img = ctx.getImageData(0, 0, w, h);
1509
- const d = img.data;
1510
- for (let i = 0; i < d.length; i += 4) {
1511
- // deterministic per-pixel noise from seed + position
1512
- d[i] = d[i] ^ ((seed + i) & 1);
1513
- }
1514
- ctx.putImageData(img, 0, 0);
1515
- } catch (_) {}
1516
- };
1517
- const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
1518
- HTMLCanvasElement.prototype.toDataURL = function(...args) {
1519
- noisify(this);
1520
- return origToDataURL.apply(this, args);
1521
- };
1522
- const origToBlob = HTMLCanvasElement.prototype.toBlob;
1523
- HTMLCanvasElement.prototype.toBlob = function(...args) {
1524
- noisify(this);
1525
- return origToBlob.apply(this, args);
1526
- };
1527
- } catch (_) {}
1528
- // Fix CDP screenX/screenY bug — CDP Input.dispatchMouseEvent sets
1529
- // screenX=clientX, screenY=clientY which never happens in real browsers.
1530
- // Cloudflare Turnstile actively checks this. Add a per-session window
1531
- // offset so screenX/screenY are realistic and internally consistent.
1532
- try {
1533
- const winX = Math.floor(Math.random() * 200) + 50;
1534
- const winY = Math.floor(Math.random() * 100) + 50;
1535
- const chrome = 85;
1536
- Object.defineProperty(MouseEvent.prototype, 'screenX', {
1537
- get() { return this.clientX + winX; },
1538
- configurable: true,
1539
- });
1540
- Object.defineProperty(MouseEvent.prototype, 'screenY', {
1541
- get() { return this.clientY + winY + chrome; },
1542
- configurable: true,
1543
- });
1544
- } catch (_) {}
1545
- // navigator.connection — missing in headless, present in real Chrome
1546
- try {
1547
- if (!navigator.connection) {
1548
- Object.defineProperty(navigator, 'connection', {
1549
- get: () => ({
1550
- effectiveType: '4g',
1551
- rtt: 50,
1552
- downlink: 10,
1553
- saveData: false,
1554
- onchange: null,
1555
- }),
1556
- });
1557
- }
1558
- } catch (_) {}
1559
- // Notification.permission — default differs in headless
1560
- try {
1561
- if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
1562
- Object.defineProperty(Notification, 'permission', { get: () => 'denied' });
1563
- }
1564
- } catch (_) {}
1565
- `);
1566
- }
1567
- const contextCreateMs = Date.now() - contextStartedAt;
1568
- const pageStartedAt = Date.now();
1569
- const page = await context.newPage();
1570
- const pageCreateMs = Date.now() - pageStartedAt;
1571
- // Cap per-action timeout so one stuck click can't consume the whole case budget.
1572
- // Default 30s is fine for long runs; for short cases (120s) use at most 15s.
1573
- const actionTimeout = Math.min(30_000, Math.max(5_000, Math.floor(timeoutMs / 8)));
1574
- const driver = new PlaywrightDriver(page, {
1575
- captureScreenshots: config.vision,
1576
- screenshotQuality: 50,
1577
- disableCdp: driverConfig.disableCdp,
1578
- timeout: actionTimeout,
1579
- visionStrategy: config.visionStrategy,
1580
- screenshotInterval,
1581
- showCursor: values['show-cursor'],
1582
- ...(macroRegistry ? { macros: macroRegistry } : {}),
1583
- });
1584
- // Apply resource blocking if configured
1585
- const resourceBlockingStartedAt = Date.now();
1586
- if (driverConfig.resourceBlocking) {
1587
- await driver.setupResourceBlocking(driverConfig.resourceBlocking);
1588
- }
1589
- const resourceBlockingSetupMs = driverConfig.resourceBlocking
1590
- ? Date.now() - resourceBlockingStartedAt
1591
- : 0;
1592
- const diagnostics = {
1593
- browserName,
1594
- headless: launchPlan.headless,
1595
- walletMode: launchPlan.walletMode,
1596
- browserLaunchMs: Number(launchDiagnostics.browserLaunchMs ?? 0),
1597
- contextCreateMs,
1598
- pageCreateMs,
1599
- resourceBlockingSetupMs,
1600
- storageStateApplied: Boolean(storageStatePath) && !cdpConnected,
1601
- persistentContext: Boolean(persistentContext),
1602
- cdpConnected,
1603
- };
1604
- // Wrap in a Driver that properly tears down context on close
1605
- const wrappedDriver = {
1606
- observe: () => driver.observe(),
1607
- execute: (action) => driver.execute(action),
1608
- getPage: () => driver.getPage?.(),
1609
- screenshot: () => driver.screenshot(),
1610
- getDiagnostics: () => diagnostics,
1611
- async close() {
1612
- await driver.close().catch(() => { });
1613
- await page.close().catch(() => { });
1614
- if (!persistentContext && !cdpConnected) {
1615
- await context.close().catch(() => { });
1616
- }
1617
- },
1618
- };
1619
- return wrappedDriver;
1620
- };
1621
- // Create a single driver for sequential mode
1622
- let singleDriver;
1623
- if (concurrency <= 1) {
1624
- singleDriver = await driverFactory();
1625
- }
1626
- renderer?.launchDone();
1627
- const runner = new TestRunner({
1628
- config,
1629
- defaultTimeoutMs: timeoutMs,
1630
- driver: singleDriver,
1631
- driverFactory: concurrency > 1 ? driverFactory : undefined,
1632
- enableMemory: memoryEnabled,
1633
- trajectoryStorePath: driverConfig.memory?.dir,
1634
- projectStore,
1635
- concurrency,
1636
- screenshotInterval,
1637
- artifactSink: sink,
1638
- extensions: extensionLoad.resolved,
1639
- ...(macroRegistry ? { macroPromptBlock: macroRegistry.promptBlock } : {}),
1640
- ...(liveBus ? { eventBus: liveBus } : {}),
1641
- ...(interruptController
1642
- ? { beforeTurn: async () => { await interruptController.waitIfPaused(); } }
1643
- : {}),
1644
- onProgress: (event) => {
1645
- if (values.json) {
1646
- console.log(JSON.stringify(event));
1647
- return;
1648
- }
1649
- if (!renderer)
1650
- return;
1651
- switch (event.type) {
1652
- case 'suite:start':
1653
- renderer.suiteStart(event.totalTests);
1654
- break;
1655
- case 'test:start':
1656
- renderer.testStart(event.testId, event.testName);
1657
- break;
1658
- case 'test:turn':
1659
- renderer.testTurn(event.testId, event.turn, event.action, event.durationMs, event.modelUsed);
1660
- break;
1661
- case 'test:complete':
1662
- renderer.testComplete(event.testId, event.passed, event.verdict, event.turnsUsed, event.durationMs, event.estimatedCostUsd);
1663
- break;
1664
- case 'suite:complete':
1665
- renderer.suiteComplete(event.passed, event.failed, event.skipped, event.totalMs, event.totalCostUsd, event.manifestUri);
1666
- break;
1667
- }
1668
- },
1669
- });
1670
- let result;
1671
- let runError;
1672
- try {
1673
- // Wire interrupt controller. Pressing `q` aborts via the suite-level signal.
1674
- const interruptSignal = interruptController
1675
- ? (() => {
1676
- const ac = new AbortController();
1677
- interruptController.on('abort', () => ac.abort('interrupted by user'));
1678
- if (interruptController.isAborted)
1679
- ac.abort('interrupted by user');
1680
- return ac.signal;
1681
- })()
1682
- : undefined;
1683
- // If --live already provided a signal, merge both. Prefer the user
1684
- // interrupt signal so `q` wins even when --live is set.
1685
- const mergedSignal = interruptSignal ?? liveCancelController?.signal;
1686
- result = await runner.runSuite(cases, mergedSignal ? { signal: mergedSignal } : undefined);
1687
- // Write reports for each configured format
1688
- const { generateReport } = await import('./test-report.js');
1689
- const reporters = driverConfig.reporters ?? ['json'];
1690
- const reportDir = path.resolve(sinkDir);
1691
- fs.mkdirSync(reportDir, { recursive: true });
1692
- const formatMeta = {
1693
- json: { ext: 'json', contentType: 'application/json' },
1694
- markdown: { ext: 'md', contentType: 'text/markdown' },
1695
- html: { ext: 'html', contentType: 'text/html' },
1696
- junit: { ext: 'xml', contentType: 'application/xml' },
1697
- };
1698
- for (const format of reporters) {
1699
- const meta = formatMeta[format];
1700
- if (!meta)
1701
- continue;
1702
- try {
1703
- const report = generateReport(result, { format, includeTurns: format === 'markdown' });
1704
- const reportPath = path.join(reportDir, `report.${meta.ext}`);
1705
- fs.writeFileSync(reportPath, report);
1706
- renderer?.report(reportPath);
1707
- }
1708
- catch {
1709
- // Report generation is best-effort
1710
- }
1711
- }
1712
- // Also write report to stdout if JSON mode
1713
- if (values.json && !quiet) {
1714
- console.log(JSON.stringify(result, null, 2));
1715
- }
1716
- }
1717
- catch (err) {
1718
- runError = err;
1719
- }
1720
- finally {
1721
- renderer?.destroy();
1722
- // Detach interrupt controller first so stdin leaves raw mode before
1723
- // shutdown logs are written.
1724
- if (detachInterrupt)
1725
- detachInterrupt();
1726
- // Flush and close the stream webhook so the final events (run-completed,
1727
- // suite-complete) reach the endpoint before the CLI exits.
1728
- if (webhookStreamer)
1729
- await webhookStreamer.close().catch(() => { });
1730
- await singleDriver?.close?.().catch(() => { });
1731
- stopWalletAutoApprover?.();
1732
- await persistentContext?.close().catch(() => { });
1733
- await browser?.close().catch(() => { });
1734
- }
1735
- if (runError) {
1736
- if (liveViewHandle) {
1737
- await liveViewHandle.close().catch(() => { });
1738
- }
1739
- throw runError;
1740
- }
1741
- const runLabel = values.cases
1742
- ? `${path.basename(values.cases)} · cli run`
1743
- : `${(values.goal || values['resume-run'] || values['fork-run'] || 'run').slice(0, 80)} · cli run`;
1744
- await syncLocalBenchmarkRun(path.resolve(sinkDir), runLabel);
1745
- // In --live mode, the user usually wants to scrub the final state in the
1746
- // viewer after the run finishes. Hold the process open until SIGINT and
1747
- // shut down the live server cleanly when the user hits Ctrl+C.
1748
- if (liveViewHandle) {
1749
- cliLog('live', `run complete — viewer at ${liveViewHandle.url} (Ctrl+C to exit)`);
1750
- await new Promise((resolve) => {
1751
- const onSigint = () => {
1752
- liveViewHandle.close().finally(() => resolve());
1753
- };
1754
- process.once('SIGINT', onSigint);
1755
- });
1756
- }
1757
- process.exit((result?.summary.failed ?? 1) > 0 ? 1 : 0);
1758
- }
1759
- function parseAllowedDomains(value) {
1760
- if (!value)
1761
- return undefined;
1762
- const domains = value
1763
- .split(',')
1764
- .map((entry) => entry.trim().toLowerCase())
1765
- .filter(Boolean);
1766
- return domains.length > 0 ? [...new Set(domains)] : undefined;
1767
- }
1768
- async function syncLocalBenchmarkRun(outPath, label) {
1769
- if (process.env.ABD_BENCHMARK_SYNC === '0')
1770
- return;
1771
- const importerPath = path.resolve(path.join(new URL('.', import.meta.url).pathname, '..', '..', 'abd-app', 'worker', 'scripts', 'import-local-benchmarks.mjs'));
1772
- if (!fs.existsSync(importerPath))
1773
- return;
1774
- if (!fs.existsSync(outPath))
1775
- return;
1776
- const args = [importerPath, '--path', outPath, '--label', label];
1777
- const userEmail = process.env.ABD_BENCHMARK_USER_EMAIL;
1778
- if (userEmail)
1779
- args.push('--user-email', userEmail);
1780
- const exitCode = await new Promise((resolve) => {
1781
- const child = spawn('node', args, {
1782
- cwd: path.dirname(importerPath),
1783
- env: {
1784
- ...process.env,
1785
- ABD_BENCHMARK_SYNC: '0',
1786
- },
1787
- stdio: 'inherit',
1788
- });
1789
- child.once('error', () => resolve(1));
1790
- child.once('close', (code) => resolve(code ?? 1));
1791
- });
1792
- if (exitCode !== 0) {
1793
- if (process.env.ABD_BENCHMARK_SYNC_STRICT === '1') {
1794
- throw new Error(`abd-app benchmark import failed for ${outPath}`);
1795
- }
1796
- cliWarn(`abd-app benchmark import skipped after non-zero exit for ${outPath}`);
1797
- }
165
+ const { runRunCommand } = await import('./cli/commands/run.js');
166
+ await runRunCommand(values);
1798
167
  }
1799
168
  // Flush telemetry on every exit path (`process.exit` from inside main, throw, or
1800
169
  // natural completion). beforeExit fires before Node decides to terminate, so