harnery 0.0.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/bin/agent-coord +42 -0
  4. package/bin/agent-hook +44 -0
  5. package/bin/harn +40 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +18 -0
  9. package/dist/commander.d.ts +128 -0
  10. package/dist/commander.d.ts.map +1 -0
  11. package/dist/commander.js +126 -0
  12. package/dist/commands/agents.d.ts +18 -0
  13. package/dist/commands/agents.d.ts.map +1 -0
  14. package/dist/commands/agents.js +3946 -0
  15. package/dist/commands/backup.d.ts +22 -0
  16. package/dist/commands/backup.d.ts.map +1 -0
  17. package/dist/commands/backup.js +262 -0
  18. package/dist/commands/browse-ai.d.ts +4 -0
  19. package/dist/commands/browse-ai.d.ts.map +1 -0
  20. package/dist/commands/browse-ai.js +156 -0
  21. package/dist/commands/browse.d.ts +4 -0
  22. package/dist/commands/browse.d.ts.map +1 -0
  23. package/dist/commands/browse.js +590 -0
  24. package/dist/commands/callers.d.ts +4 -0
  25. package/dist/commands/callers.d.ts.map +1 -0
  26. package/dist/commands/callers.js +276 -0
  27. package/dist/commands/completion.d.ts +17 -0
  28. package/dist/commands/completion.d.ts.map +1 -0
  29. package/dist/commands/completion.js +158 -0
  30. package/dist/commands/config-get.d.ts +4 -0
  31. package/dist/commands/config-get.d.ts.map +1 -0
  32. package/dist/commands/config-get.js +131 -0
  33. package/dist/commands/context.d.ts +11 -0
  34. package/dist/commands/context.d.ts.map +1 -0
  35. package/dist/commands/context.js +185 -0
  36. package/dist/commands/cookies.d.ts +4 -0
  37. package/dist/commands/cookies.d.ts.map +1 -0
  38. package/dist/commands/cookies.js +140 -0
  39. package/dist/commands/docs.d.ts +4 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +137 -0
  42. package/dist/commands/doctor.d.ts +25 -0
  43. package/dist/commands/doctor.d.ts.map +1 -0
  44. package/dist/commands/doctor.js +200 -0
  45. package/dist/commands/edit-batch.d.ts +18 -0
  46. package/dist/commands/edit-batch.d.ts.map +1 -0
  47. package/dist/commands/edit-batch.js +172 -0
  48. package/dist/commands/eml.d.ts +4 -0
  49. package/dist/commands/eml.d.ts.map +1 -0
  50. package/dist/commands/eml.js +428 -0
  51. package/dist/commands/env.d.ts +4 -0
  52. package/dist/commands/env.d.ts.map +1 -0
  53. package/dist/commands/env.js +201 -0
  54. package/dist/commands/fetch.d.ts +4 -0
  55. package/dist/commands/fetch.d.ts.map +1 -0
  56. package/dist/commands/fetch.js +99 -0
  57. package/dist/commands/file-history.d.ts +4 -0
  58. package/dist/commands/file-history.d.ts.map +1 -0
  59. package/dist/commands/file-history.js +152 -0
  60. package/dist/commands/grep.d.ts +4 -0
  61. package/dist/commands/grep.d.ts.map +1 -0
  62. package/dist/commands/grep.js +317 -0
  63. package/dist/commands/init.d.ts +82 -0
  64. package/dist/commands/init.d.ts.map +1 -0
  65. package/dist/commands/init.js +288 -0
  66. package/dist/commands/outline.d.ts +4 -0
  67. package/dist/commands/outline.d.ts.map +1 -0
  68. package/dist/commands/outline.js +509 -0
  69. package/dist/commands/presence.d.ts +12 -0
  70. package/dist/commands/presence.d.ts.map +1 -0
  71. package/dist/commands/presence.js +123 -0
  72. package/dist/commands/read.d.ts +7 -0
  73. package/dist/commands/read.d.ts.map +1 -0
  74. package/dist/commands/read.js +46 -0
  75. package/dist/commands/scratch.d.ts +4 -0
  76. package/dist/commands/scratch.d.ts.map +1 -0
  77. package/dist/commands/scratch.js +426 -0
  78. package/dist/commands/session.d.ts +4 -0
  79. package/dist/commands/session.d.ts.map +1 -0
  80. package/dist/commands/session.js +162 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.d.ts.map +1 -0
  83. package/dist/commands/sync.js +275 -0
  84. package/dist/commands/toc.d.ts +5 -0
  85. package/dist/commands/toc.d.ts.map +1 -0
  86. package/dist/commands/toc.js +153 -0
  87. package/dist/commands/tokens.d.ts +4 -0
  88. package/dist/commands/tokens.d.ts.map +1 -0
  89. package/dist/commands/tokens.js +48 -0
  90. package/dist/commands/tunnel.d.ts +4 -0
  91. package/dist/commands/tunnel.d.ts.map +1 -0
  92. package/dist/commands/tunnel.js +513 -0
  93. package/dist/commands/uninstall.d.ts +22 -0
  94. package/dist/commands/uninstall.d.ts.map +1 -0
  95. package/dist/commands/uninstall.js +126 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +165 -0
  99. package/dist/core/agents/canonical-emit.d.ts +27 -0
  100. package/dist/core/agents/canonical-emit.d.ts.map +1 -0
  101. package/dist/core/agents/canonical-emit.js +72 -0
  102. package/dist/core/agents/cli-emit.d.ts +27 -0
  103. package/dist/core/agents/cli-emit.d.ts.map +1 -0
  104. package/dist/core/agents/cli-emit.js +57 -0
  105. package/dist/core/agents/cli.d.ts +10 -0
  106. package/dist/core/agents/cli.d.ts.map +1 -0
  107. package/dist/core/agents/cli.js +757 -0
  108. package/dist/core/agents/codex-replay.d.ts +29 -0
  109. package/dist/core/agents/codex-replay.d.ts.map +1 -0
  110. package/dist/core/agents/codex-replay.js +138 -0
  111. package/dist/core/agents/coord-client.d.ts +98 -0
  112. package/dist/core/agents/coord-client.d.ts.map +1 -0
  113. package/dist/core/agents/coord-client.js +212 -0
  114. package/dist/core/agents/events/consume.d.ts +59 -0
  115. package/dist/core/agents/events/consume.d.ts.map +1 -0
  116. package/dist/core/agents/events/consume.js +147 -0
  117. package/dist/core/agents/events/emit.d.ts +42 -0
  118. package/dist/core/agents/events/emit.d.ts.map +1 -0
  119. package/dist/core/agents/events/emit.js +70 -0
  120. package/dist/core/agents/events/ulid.d.ts +11 -0
  121. package/dist/core/agents/events/ulid.d.ts.map +1 -0
  122. package/dist/core/agents/events/ulid.js +47 -0
  123. package/dist/core/agents/index.d.ts +14 -0
  124. package/dist/core/agents/index.d.ts.map +1 -0
  125. package/dist/core/agents/index.js +13 -0
  126. package/dist/core/agents/paths.d.ts +6 -0
  127. package/dist/core/agents/paths.d.ts.map +1 -0
  128. package/dist/core/agents/paths.js +17 -0
  129. package/dist/core/agents/render/prompt-context.d.ts +43 -0
  130. package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
  131. package/dist/core/agents/render/prompt-context.js +335 -0
  132. package/dist/core/agents/render/session-context.d.ts +39 -0
  133. package/dist/core/agents/render/session-context.d.ts.map +1 -0
  134. package/dist/core/agents/render/session-context.js +283 -0
  135. package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
  136. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
  137. package/dist/core/agents/rules/claim-conflict.js +244 -0
  138. package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
  139. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
  140. package/dist/core/agents/rules/commit-conflict.js +244 -0
  141. package/dist/core/agents/rules/stop-hook.d.ts +44 -0
  142. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
  143. package/dist/core/agents/rules/stop-hook.js +161 -0
  144. package/dist/core/agents/session-events.d.ts +41 -0
  145. package/dist/core/agents/session-events.d.ts.map +1 -0
  146. package/dist/core/agents/session-events.js +205 -0
  147. package/dist/core/agents/state/activity-log.d.ts +18 -0
  148. package/dist/core/agents/state/activity-log.d.ts.map +1 -0
  149. package/dist/core/agents/state/activity-log.js +34 -0
  150. package/dist/core/agents/state/council.d.ts +39 -0
  151. package/dist/core/agents/state/council.d.ts.map +1 -0
  152. package/dist/core/agents/state/council.js +216 -0
  153. package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
  154. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
  155. package/dist/core/agents/state/heartbeat-projector.js +436 -0
  156. package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
  157. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
  158. package/dist/core/agents/state/heartbeat-writer.js +271 -0
  159. package/dist/core/agents/state/names.d.ts +35 -0
  160. package/dist/core/agents/state/names.d.ts.map +1 -0
  161. package/dist/core/agents/state/names.js +376 -0
  162. package/dist/core/agents/state/pidmap.d.ts +11 -0
  163. package/dist/core/agents/state/pidmap.d.ts.map +1 -0
  164. package/dist/core/agents/state/pidmap.js +32 -0
  165. package/dist/core/agents/state/scratch.d.ts +27 -0
  166. package/dist/core/agents/state/scratch.d.ts.map +1 -0
  167. package/dist/core/agents/state/scratch.js +90 -0
  168. package/dist/core/agents/state/shell-mutation.d.ts +17 -0
  169. package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
  170. package/dist/core/agents/state/shell-mutation.js +41 -0
  171. package/dist/core/agents/state/stale-sweep.d.ts +16 -0
  172. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
  173. package/dist/core/agents/state/stale-sweep.js +166 -0
  174. package/dist/core/config.d.ts +29 -0
  175. package/dist/core/config.d.ts.map +1 -0
  176. package/dist/core/config.js +108 -0
  177. package/dist/core/hooks/cli.d.ts +21 -0
  178. package/dist/core/hooks/cli.d.ts.map +1 -0
  179. package/dist/core/hooks/cli.js +1123 -0
  180. package/dist/core/hooks/effects/image-capture.d.ts +43 -0
  181. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
  182. package/dist/core/hooks/effects/image-capture.js +288 -0
  183. package/dist/core/hooks/effects/index.d.ts +64 -0
  184. package/dist/core/hooks/effects/index.d.ts.map +1 -0
  185. package/dist/core/hooks/effects/index.js +197 -0
  186. package/dist/core/hooks/events/emit.d.ts +31 -0
  187. package/dist/core/hooks/events/emit.d.ts.map +1 -0
  188. package/dist/core/hooks/events/emit.js +89 -0
  189. package/dist/core/hooks/events/schema.d.ts +235 -0
  190. package/dist/core/hooks/events/schema.d.ts.map +1 -0
  191. package/dist/core/hooks/events/schema.js +12 -0
  192. package/dist/core/hooks/events/ulid.d.ts +10 -0
  193. package/dist/core/hooks/events/ulid.d.ts.map +1 -0
  194. package/dist/core/hooks/events/ulid.js +47 -0
  195. package/dist/core/hooks/harness/detect.d.ts +9 -0
  196. package/dist/core/hooks/harness/detect.d.ts.map +1 -0
  197. package/dist/core/hooks/harness/detect.js +29 -0
  198. package/dist/core/hooks/harness/events.d.ts +45 -0
  199. package/dist/core/hooks/harness/events.d.ts.map +1 -0
  200. package/dist/core/hooks/harness/events.js +71 -0
  201. package/dist/core/hooks/harness/output.d.ts +46 -0
  202. package/dist/core/hooks/harness/output.d.ts.map +1 -0
  203. package/dist/core/hooks/harness/output.js +87 -0
  204. package/dist/core/hooks/harness/parse.d.ts +67 -0
  205. package/dist/core/hooks/harness/parse.d.ts.map +1 -0
  206. package/dist/core/hooks/harness/parse.js +132 -0
  207. package/dist/core/hooks/index.d.ts +8 -0
  208. package/dist/core/hooks/index.d.ts.map +1 -0
  209. package/dist/core/hooks/index.js +7 -0
  210. package/dist/core/hooks/resolve/anchor.d.ts +37 -0
  211. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
  212. package/dist/core/hooks/resolve/anchor.js +48 -0
  213. package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
  214. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
  215. package/dist/core/hooks/resolve/coord-root.js +27 -0
  216. package/dist/core/hooks/resolve/intent.d.ts +33 -0
  217. package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
  218. package/dist/core/hooks/resolve/intent.js +79 -0
  219. package/dist/core/hooks/resolve/owner.d.ts +42 -0
  220. package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
  221. package/dist/core/hooks/resolve/owner.js +140 -0
  222. package/dist/core/hooks/resolve/transcript.d.ts +26 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
  224. package/dist/core/hooks/resolve/transcript.js +73 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +13 -0
  228. package/dist/lib/agent-browser/client.d.ts +99 -0
  229. package/dist/lib/agent-browser/client.d.ts.map +1 -0
  230. package/dist/lib/agent-browser/client.js +177 -0
  231. package/dist/lib/agent-browser/index.d.ts +2 -0
  232. package/dist/lib/agent-browser/index.d.ts.map +1 -0
  233. package/dist/lib/agent-browser/index.js +1 -0
  234. package/dist/lib/browser/client.d.ts +193 -0
  235. package/dist/lib/browser/client.d.ts.map +1 -0
  236. package/dist/lib/browser/client.js +325 -0
  237. package/dist/lib/browser/dev-overlay.d.ts +23 -0
  238. package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
  239. package/dist/lib/browser/dev-overlay.js +153 -0
  240. package/dist/lib/browser/index.d.ts +5 -0
  241. package/dist/lib/browser/index.d.ts.map +1 -0
  242. package/dist/lib/browser/index.js +2 -0
  243. package/dist/lib/browser/layout.d.ts +79 -0
  244. package/dist/lib/browser/layout.d.ts.map +1 -0
  245. package/dist/lib/browser/layout.js +220 -0
  246. package/dist/lib/browser/visibility.d.ts +86 -0
  247. package/dist/lib/browser/visibility.d.ts.map +1 -0
  248. package/dist/lib/browser/visibility.js +333 -0
  249. package/dist/lib/browser/visual-diff.d.ts +38 -0
  250. package/dist/lib/browser/visual-diff.d.ts.map +1 -0
  251. package/dist/lib/browser/visual-diff.js +107 -0
  252. package/dist/lib/completion/bash.d.ts +25 -0
  253. package/dist/lib/completion/bash.d.ts.map +1 -0
  254. package/dist/lib/completion/bash.js +284 -0
  255. package/dist/lib/completion/fish.d.ts +16 -0
  256. package/dist/lib/completion/fish.d.ts.map +1 -0
  257. package/dist/lib/completion/fish.js +118 -0
  258. package/dist/lib/completion/index.d.ts +5 -0
  259. package/dist/lib/completion/index.d.ts.map +1 -0
  260. package/dist/lib/completion/index.js +4 -0
  261. package/dist/lib/completion/walk.d.ts +68 -0
  262. package/dist/lib/completion/walk.d.ts.map +1 -0
  263. package/dist/lib/completion/walk.js +102 -0
  264. package/dist/lib/completion/zsh.d.ts +13 -0
  265. package/dist/lib/completion/zsh.d.ts.map +1 -0
  266. package/dist/lib/completion/zsh.js +249 -0
  267. package/dist/lib/context/index.d.ts +107 -0
  268. package/dist/lib/context/index.d.ts.map +1 -0
  269. package/dist/lib/context/index.js +275 -0
  270. package/dist/lib/cookies/client.d.ts +131 -0
  271. package/dist/lib/cookies/client.d.ts.map +1 -0
  272. package/dist/lib/cookies/client.js +239 -0
  273. package/dist/lib/cookies/index.d.ts +2 -0
  274. package/dist/lib/cookies/index.d.ts.map +1 -0
  275. package/dist/lib/cookies/index.js +1 -0
  276. package/dist/lib/council/index.d.ts +266 -0
  277. package/dist/lib/council/index.d.ts.map +1 -0
  278. package/dist/lib/council/index.js +674 -0
  279. package/dist/lib/docs-index.d.ts +28 -0
  280. package/dist/lib/docs-index.d.ts.map +1 -0
  281. package/dist/lib/docs-index.js +169 -0
  282. package/dist/lib/docs-lint.d.ts +26 -0
  283. package/dist/lib/docs-lint.d.ts.map +1 -0
  284. package/dist/lib/docs-lint.js +378 -0
  285. package/dist/lib/docs-sweep.d.ts +34 -0
  286. package/dist/lib/docs-sweep.d.ts.map +1 -0
  287. package/dist/lib/docs-sweep.js +304 -0
  288. package/dist/lib/docs.d.ts +27 -0
  289. package/dist/lib/docs.d.ts.map +1 -0
  290. package/dist/lib/docs.js +142 -0
  291. package/dist/lib/env.d.ts +11 -0
  292. package/dist/lib/env.d.ts.map +1 -0
  293. package/dist/lib/env.js +12 -0
  294. package/dist/lib/exec.d.ts +32 -0
  295. package/dist/lib/exec.d.ts.map +1 -0
  296. package/dist/lib/exec.js +54 -0
  297. package/dist/lib/format.d.ts +29 -0
  298. package/dist/lib/format.d.ts.map +1 -0
  299. package/dist/lib/format.js +139 -0
  300. package/dist/lib/http/client.d.ts +56 -0
  301. package/dist/lib/http/client.d.ts.map +1 -0
  302. package/dist/lib/http/client.js +160 -0
  303. package/dist/lib/http/index.d.ts +2 -0
  304. package/dist/lib/http/index.d.ts.map +1 -0
  305. package/dist/lib/http/index.js +1 -0
  306. package/dist/lib/identities/index.d.ts +77 -0
  307. package/dist/lib/identities/index.d.ts.map +1 -0
  308. package/dist/lib/identities/index.js +190 -0
  309. package/dist/lib/machine.d.ts +19 -0
  310. package/dist/lib/machine.d.ts.map +1 -0
  311. package/dist/lib/machine.js +61 -0
  312. package/dist/lib/presence.d.ts +48 -0
  313. package/dist/lib/presence.d.ts.map +1 -0
  314. package/dist/lib/presence.js +123 -0
  315. package/dist/lib/readability/client.d.ts +39 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +121 -0
  318. package/dist/lib/readability/index.d.ts +2 -0
  319. package/dist/lib/readability/index.d.ts.map +1 -0
  320. package/dist/lib/readability/index.js +1 -0
  321. package/dist/lib/scratch/index.d.ts +74 -0
  322. package/dist/lib/scratch/index.d.ts.map +1 -0
  323. package/dist/lib/scratch/index.js +393 -0
  324. package/dist/lib/tunnel/gate.d.ts +12 -0
  325. package/dist/lib/tunnel/gate.d.ts.map +1 -0
  326. package/dist/lib/tunnel/gate.js +101 -0
  327. package/dist/lib/tunnel/state.d.ts +34 -0
  328. package/dist/lib/tunnel/state.d.ts.map +1 -0
  329. package/dist/lib/tunnel/state.js +132 -0
  330. package/package.json +160 -8
  331. package/schemas/.gitkeep +0 -0
  332. package/schemas/config.schema.json +109 -0
  333. package/src/cli.ts +22 -0
  334. package/src/commander.ts +242 -0
  335. package/src/commands/.gitkeep +0 -0
  336. package/src/commands/agents.ts +4567 -0
  337. package/src/commands/backup.ts +305 -0
  338. package/src/commands/browse-ai.ts +198 -0
  339. package/src/commands/browse.ts +849 -0
  340. package/src/commands/callers.ts +363 -0
  341. package/src/commands/completion.ts +193 -0
  342. package/src/commands/config-get.ts +161 -0
  343. package/src/commands/context.ts +209 -0
  344. package/src/commands/cookies.ts +198 -0
  345. package/src/commands/docs.ts +174 -0
  346. package/src/commands/doctor.ts +231 -0
  347. package/src/commands/edit-batch.ts +233 -0
  348. package/src/commands/eml.ts +519 -0
  349. package/src/commands/env.ts +254 -0
  350. package/src/commands/fetch.ts +136 -0
  351. package/src/commands/file-history.ts +202 -0
  352. package/src/commands/grep.ts +371 -0
  353. package/src/commands/init.ts +335 -0
  354. package/src/commands/outline.ts +583 -0
  355. package/src/commands/presence.ts +152 -0
  356. package/src/commands/read.ts +64 -0
  357. package/src/commands/scratch.ts +445 -0
  358. package/src/commands/session.ts +187 -0
  359. package/src/commands/sync.ts +306 -0
  360. package/src/commands/toc.ts +218 -0
  361. package/src/commands/tokens.ts +79 -0
  362. package/src/commands/tunnel.ts +633 -0
  363. package/src/commands/uninstall.ts +144 -0
  364. package/src/commands/web.ts +193 -0
  365. package/src/core/agents/canonical-emit.ts +77 -0
  366. package/src/core/agents/cli-emit.ts +64 -0
  367. package/src/core/agents/cli.ts +838 -0
  368. package/src/core/agents/codex-replay.ts +163 -0
  369. package/src/core/agents/coord-client.ts +249 -0
  370. package/src/core/agents/events/consume.ts +196 -0
  371. package/src/core/agents/events/emit.ts +108 -0
  372. package/src/core/agents/events/ulid.ts +51 -0
  373. package/src/core/agents/index.ts +14 -0
  374. package/src/core/agents/paths.ts +16 -0
  375. package/src/core/agents/render/prompt-context.ts +401 -0
  376. package/src/core/agents/render/session-context.ts +341 -0
  377. package/src/core/agents/rules/claim-conflict.ts +282 -0
  378. package/src/core/agents/rules/commit-conflict.ts +303 -0
  379. package/src/core/agents/rules/stop-hook.ts +229 -0
  380. package/src/core/agents/session-events.ts +228 -0
  381. package/src/core/agents/state/activity-log.ts +33 -0
  382. package/src/core/agents/state/council.ts +265 -0
  383. package/src/core/agents/state/heartbeat-projector.ts +488 -0
  384. package/src/core/agents/state/heartbeat-writer.ts +333 -0
  385. package/src/core/agents/state/names.ts +399 -0
  386. package/src/core/agents/state/pidmap.ts +38 -0
  387. package/src/core/agents/state/scratch.ts +121 -0
  388. package/src/core/agents/state/shell-mutation.ts +44 -0
  389. package/src/core/agents/state/stale-sweep.ts +190 -0
  390. package/src/core/config.ts +111 -0
  391. package/src/core/hooks/cli.ts +1247 -0
  392. package/src/core/hooks/effects/image-capture.ts +330 -0
  393. package/src/core/hooks/effects/index.ts +210 -0
  394. package/src/core/hooks/events/emit.ts +120 -0
  395. package/src/core/hooks/events/schema.ts +430 -0
  396. package/src/core/hooks/events/ulid.ts +51 -0
  397. package/src/core/hooks/harness/detect.ts +30 -0
  398. package/src/core/hooks/harness/events.ts +102 -0
  399. package/src/core/hooks/harness/output.ts +100 -0
  400. package/src/core/hooks/harness/parse.ts +180 -0
  401. package/src/core/hooks/index.ts +16 -0
  402. package/src/core/hooks/resolve/anchor.ts +51 -0
  403. package/src/core/hooks/resolve/coord-root.ts +25 -0
  404. package/src/core/hooks/resolve/intent.ts +89 -0
  405. package/src/core/hooks/resolve/owner.ts +140 -0
  406. package/src/core/hooks/resolve/transcript.ts +72 -0
  407. package/src/hooks/.gitkeep +0 -0
  408. package/src/index.ts +15 -0
  409. package/src/lib/agent-browser/client.ts +239 -0
  410. package/src/lib/agent-browser/index.ts +1 -0
  411. package/src/lib/browser/client.ts +449 -0
  412. package/src/lib/browser/dev-overlay.ts +207 -0
  413. package/src/lib/browser/index.ts +24 -0
  414. package/src/lib/browser/layout.ts +288 -0
  415. package/src/lib/browser/visibility.ts +419 -0
  416. package/src/lib/browser/visual-diff.ts +150 -0
  417. package/src/lib/completion/bash.ts +291 -0
  418. package/src/lib/completion/fish.ts +134 -0
  419. package/src/lib/completion/index.ts +10 -0
  420. package/src/lib/completion/walk.ts +184 -0
  421. package/src/lib/completion/zsh.ts +262 -0
  422. package/src/lib/context/index.ts +386 -0
  423. package/src/lib/cookies/client.ts +301 -0
  424. package/src/lib/cookies/index.ts +13 -0
  425. package/src/lib/council/index.ts +803 -0
  426. package/src/lib/docs-index.ts +216 -0
  427. package/src/lib/docs-lint.ts +413 -0
  428. package/src/lib/docs-sweep.ts +348 -0
  429. package/src/lib/docs.ts +199 -0
  430. package/src/lib/env.ts +12 -0
  431. package/src/lib/exec.ts +74 -0
  432. package/src/lib/format.ts +147 -0
  433. package/src/lib/http/client.ts +211 -0
  434. package/src/lib/http/index.ts +1 -0
  435. package/src/lib/identities/index.ts +210 -0
  436. package/src/lib/machine.ts +61 -0
  437. package/src/lib/presence.ts +154 -0
  438. package/src/lib/readability/client.ts +169 -0
  439. package/src/lib/readability/index.ts +5 -0
  440. package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
  441. package/src/lib/scratch/index.ts +470 -0
  442. package/src/lib/tunnel/gate.ts +113 -0
  443. package/src/lib/tunnel/state.ts +167 -0
  444. package/src/web/.gitkeep +0 -0
  445. package/index.js +0 -1
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Public library entry. Re-exports the harn-able surface for consumers that
3
+ * import harnery as a library rather than calling the CLI.
4
+ *
5
+ * Examples of intended consumption:
6
+ * import { createHarneryProgram } from 'harnery'; // CLI composition
7
+ * import { evaluateClaim } from 'harnery/core/agents'; // verdict rule directly
8
+ * import type { Heartbeat } from 'harnery/core/agents'; // schema types
9
+ *
10
+ * Heavy modules (web UI, hook scripts) are not re-exported from here; they're
11
+ * reachable via their own subpath entries in package.json#exports.
12
+ */
13
+
14
+ export type { HarneryContextOpts, HarneryProgramContext } from "./commander.ts";
15
+ export { createHarneryProgram } from "./commander.ts";
@@ -0,0 +1,239 @@
1
+ import { type SpawnSyncOptions, spawnSync } from "node:child_process";
2
+ import { writeFileSync } from "node:fs";
3
+ import {
4
+ type CookieJar,
5
+ type CookieStore,
6
+ type Cookie as JarCookie,
7
+ mergeCookies,
8
+ } from "../cookies/index.ts";
9
+
10
+ /**
11
+ * Thin wrapper over Vercel Labs' `agent-browser` Rust CLI.
12
+ *
13
+ * agent-browser is a process that wraps a managed Chrome for Testing
14
+ * binary and exposes verbs over its own daemon (state persists across
15
+ * invocations within a shell). This wrapper exec's the binary; it does
16
+ * **not** manage the daemon itself.
17
+ *
18
+ * Optional cookie-jar integration: if a CookieJar is passed in the
19
+ * options, the wrapper will write the jar contents to a temp state file
20
+ * and `state load` it before any nav, then `cookies get` and merge the
21
+ * result back into the jar after work is done. Cookies are best-effort:
22
+ * the wrapper never fails because a load/save round-trip blew up.
23
+ *
24
+ * Canonical for `harn browse-ai` (dev side); ready to be the shared core of
25
+ * the agent-side `browse` wrapper (sandbox side) when that wrapper gets refactored.
26
+ */
27
+
28
+ export interface AgentBrowserOptions {
29
+ /** Override the binary path. Default: looks up `agent-browser` on PATH. */
30
+ binary?: string;
31
+ /** Cookie jar for cross-tool sharing. Pass `null` to skip. */
32
+ jar?: CookieJar | null;
33
+ /** Default per-call timeout in ms. Default 60000. */
34
+ timeoutMs?: number;
35
+ /** Extra env vars to merge into every spawn. */
36
+ env?: Record<string, string>;
37
+ /**
38
+ * Path used as the `state load` source when seeding cookies into the
39
+ * agent-browser session. Defaults to a tmp file derived from the jar.
40
+ */
41
+ stateFilePath?: string;
42
+ }
43
+
44
+ export interface ExecResult {
45
+ stdout: string;
46
+ stderr: string;
47
+ ok: boolean;
48
+ exitCode: number | null;
49
+ }
50
+
51
+ const DEFAULT_TIMEOUT_MS = 60_000;
52
+
53
+ export class AgentBrowser {
54
+ private readonly opts: AgentBrowserOptions;
55
+ private cookiesSeeded = false;
56
+
57
+ constructor(opts: AgentBrowserOptions = {}) {
58
+ this.opts = opts;
59
+ }
60
+
61
+ /**
62
+ * Run an `agent-browser` subcommand. Returns a structured result.
63
+ * Throws only on spawn failure (e.g., binary not found); non-zero exit
64
+ * returns ok=false.
65
+ */
66
+ exec(args: string[], timeoutMs?: number): ExecResult {
67
+ const binary = this.opts.binary ?? "agent-browser";
68
+ const env = this.scrubEnv();
69
+ const result = spawnSync(binary, args, {
70
+ timeout: timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
71
+ encoding: "utf8",
72
+ env,
73
+ } satisfies SpawnSyncOptions);
74
+
75
+ if (result.error && (result.error as NodeJS.ErrnoException).code === "ENOENT") {
76
+ throw new Error(
77
+ `agent-browser binary not found at "${binary}". Install once with:\n curl -fsSL https://github.com/vercel-labs/agent-browser/releases/download/v0.25.4/agent-browser-linux-x64 \\\n -o ~/.local/bin/agent-browser && chmod +x ~/.local/bin/agent-browser\n agent-browser install`,
78
+ );
79
+ }
80
+ if (result.error) {
81
+ throw result.error;
82
+ }
83
+ return {
84
+ stdout: (result.stdout ?? "").toString(),
85
+ stderr: (result.stderr ?? "").toString(),
86
+ ok: result.status === 0,
87
+ exitCode: result.status,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Same as `exec` but throws on non-zero exit code, with stderr in the
93
+ * error message. Use when the caller can't recover from a failure.
94
+ */
95
+ execOrThrow(args: string[], timeoutMs?: number): ExecResult {
96
+ const result = this.exec(args, timeoutMs);
97
+ if (!result.ok) {
98
+ throw new Error(
99
+ `agent-browser ${args.join(" ")} failed (exit ${result.exitCode ?? "?"}): ${result.stderr.trim() || result.stdout.trim()}`,
100
+ );
101
+ }
102
+ return result;
103
+ }
104
+
105
+ // -------------------------------------------------------------------------
106
+ // High-level verbs
107
+ // -------------------------------------------------------------------------
108
+
109
+ /**
110
+ * Seed the jar's cookies into the agent-browser session via `state load`.
111
+ * Called automatically by `open()`. Safe to call multiple times; only
112
+ * the first call does work.
113
+ */
114
+ seedCookies(): void {
115
+ if (this.cookiesSeeded) return;
116
+ if (!this.opts.jar) {
117
+ this.cookiesSeeded = true;
118
+ return;
119
+ }
120
+ try {
121
+ const jarStore = this.opts.jar.load();
122
+ if (jarStore.cookies.length === 0) {
123
+ this.cookiesSeeded = true;
124
+ return;
125
+ }
126
+ const stateFile =
127
+ this.opts.stateFilePath ?? `/tmp/bp-agent-browser-state-${process.pid}.json`;
128
+ writeFileSync(stateFile, JSON.stringify(jarStore, null, 2));
129
+ this.exec(["state", "load", stateFile], 10_000);
130
+ this.cookiesSeeded = true;
131
+ } catch {
132
+ // Cookie seeding is best-effort; never fail the session because
133
+ // the jar couldn't be written/loaded.
134
+ this.cookiesSeeded = true;
135
+ }
136
+ }
137
+
138
+ open(url: string, timeoutMs?: number): ExecResult {
139
+ this.seedCookies();
140
+ return this.execOrThrow(["open", url], timeoutMs);
141
+ }
142
+
143
+ snapshot(opts: { interactive?: boolean } = {}): string {
144
+ const args = ["snapshot"];
145
+ if (opts.interactive) args.push("-i");
146
+ return this.execOrThrow(args).stdout.trim();
147
+ }
148
+
149
+ screenshot(path: string, opts: { full?: boolean; annotate?: boolean } = {}): void {
150
+ const args = ["screenshot"];
151
+ if (opts.full) args.push("--full");
152
+ if (opts.annotate) args.push("--annotate");
153
+ args.push(path);
154
+ this.execOrThrow(args);
155
+ }
156
+
157
+ click(refOrSelector: string): ExecResult {
158
+ return this.execOrThrow(["click", refOrSelector]);
159
+ }
160
+
161
+ fill(refOrSelector: string, value: string): ExecResult {
162
+ return this.execOrThrow(["fill", refOrSelector, value]);
163
+ }
164
+
165
+ press(key: string): ExecResult {
166
+ return this.execOrThrow(["press", key]);
167
+ }
168
+
169
+ wait(selectorOrMs: string): ExecResult {
170
+ return this.execOrThrow(["wait", selectorOrMs]);
171
+ }
172
+
173
+ evaluate(script: string): string {
174
+ return this.execOrThrow(["eval", script]).stdout.trim();
175
+ }
176
+
177
+ /**
178
+ * Run a semicolon-separated batch of agent-browser sub-commands in a
179
+ * single session. Each step is exec'd with the same daemon, so state
180
+ * persists. Returns one ExecResult per step in order.
181
+ */
182
+ batch(steps: string[]): ExecResult[] {
183
+ this.seedCookies();
184
+ const results: ExecResult[] = [];
185
+ for (const step of steps) {
186
+ const trimmed = step.trim();
187
+ if (!trimmed) continue;
188
+ // Naive shell-like splitting; agent-browser's own argv parser handles
189
+ // the actual command; we just split on whitespace for the wrapper.
190
+ const args = trimmed.split(/\s+/);
191
+ results.push(this.exec(args));
192
+ }
193
+ return results;
194
+ }
195
+
196
+ /** Start HAR recording. Pair with `harStop`. */
197
+ harStart(path: string): ExecResult {
198
+ return this.execOrThrow(["network", "har", "start", path]);
199
+ }
200
+
201
+ /** Stop HAR recording. Returns the same result for symmetry with start. */
202
+ harStop(path: string): ExecResult {
203
+ return this.execOrThrow(["network", "har", "stop", path]);
204
+ }
205
+
206
+ /**
207
+ * Pull cookies out of the agent-browser session via `cookies get --json`,
208
+ * merge them into the jar (if one was provided), and persist. Best-effort:
209
+ * a parse failure or missing jar returns 0 without raising.
210
+ */
211
+ syncCookiesToJar(): { saved: number } {
212
+ if (!this.opts.jar) return { saved: 0 };
213
+ try {
214
+ const result = this.exec(["cookies", "get", "--json"], 10_000);
215
+ if (!result.ok) return { saved: 0 };
216
+ const parsed = JSON.parse(result.stdout);
217
+ const cookies: JarCookie[] = parsed.data?.cookies ?? parsed.cookies ?? [];
218
+ if (!Array.isArray(cookies) || cookies.length === 0) return { saved: 0 };
219
+ const jar = this.opts.jar;
220
+ const store: CookieStore = jar.load();
221
+ const merged = mergeCookies(store, cookies);
222
+ jar.save(merged);
223
+ return { saved: cookies.length };
224
+ } catch {
225
+ return { saved: 0 };
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Some shell environments leak XDG_CONFIG_HOME into Chrome's crashpad
231
+ * lookup, which can cause SIGTRAP if the path is read-only (e.g. in
232
+ * containerized sandbox environments). Strip it here defensively.
233
+ */
234
+ private scrubEnv(): NodeJS.ProcessEnv {
235
+ const env: NodeJS.ProcessEnv = { ...process.env, ...this.opts.env };
236
+ env.XDG_CONFIG_HOME = undefined;
237
+ return env;
238
+ }
239
+ }
@@ -0,0 +1 @@
1
+ export { AgentBrowser, type AgentBrowserOptions, type ExecResult } from "./client.js";
@@ -0,0 +1,449 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { resolve } from "node:path";
4
+ import {
5
+ type BrowserContext,
6
+ type ConsoleMessage,
7
+ chromium,
8
+ type Page,
9
+ type Cookie as PWCookie,
10
+ type Request,
11
+ } from "playwright";
12
+ import type { CookieJar, Cookie as JarCookie } from "../cookies/index.ts";
13
+ import {
14
+ buildClearLayoutAnnotationsScript,
15
+ buildLayoutAnnotateScript,
16
+ buildOverflowCheck,
17
+ buildWidthCheck,
18
+ type OverflowResult,
19
+ type WidthResult,
20
+ } from "./layout.js";
21
+ import {
22
+ buildAnnotateScript,
23
+ buildClearAnnotationsScript,
24
+ buildVisibilityCheck,
25
+ type CheckVisibilityOptions,
26
+ type VisibilityResult,
27
+ } from "./visibility.js";
28
+
29
+ /**
30
+ * Headless-Chromium wrapper for the `browse` command.
31
+ *
32
+ * Two persistence layers:
33
+ * 1. Persistent profile (Playwright's `launchPersistentContext`) keeps
34
+ * browser state (localStorage, IndexedDB, login session) across runs.
35
+ * 2. Optional cookie jar, shared with `fetch`/`cookies` so a session
36
+ * built up in one tool is visible to the others.
37
+ *
38
+ * Designed to be opened, used for one or more navigations, and closed.
39
+ * It is not a long-lived service. For multi-step workflows, the caller drives
40
+ * `navigate`/`click`/`fill` directly between `open()` and `close()`.
41
+ */
42
+
43
+ export interface BrowserOptions {
44
+ /** Persistent profile dir. Default `~/.cache/harnery/browser-profile/`. Created if missing. */
45
+ profileDir?: string;
46
+ /** Launch headed (visible window). Default false. */
47
+ headed?: boolean;
48
+ /** Cookie jar to seed/sync with. Pass `null` to skip jar entirely. */
49
+ jar?: CookieJar | null;
50
+ /** Viewport. Default 1280x800. */
51
+ viewport?: { width: number; height: number };
52
+ /** Default navigation timeout in ms. Default 30000. */
53
+ navigationTimeout?: number;
54
+ /**
55
+ * `wait_until` strategy for `navigate`. Default `"load"`.
56
+ * Use `"domcontentloaded"` for sites with long-running analytics scripts
57
+ * that never let `"load"` fire.
58
+ */
59
+ waitUntil?: "load" | "domcontentloaded" | "networkidle" | "commit";
60
+ /**
61
+ * If set, record network traffic to a HAR file at this absolute path.
62
+ * The HAR is finalized when `close()` is called.
63
+ */
64
+ recordHarPath?: string;
65
+ /**
66
+ * Optional callback returning extra headers to attach to every request,
67
+ * keyed by request URL. Consumers can inject extra HTTP headers per-URL
68
+ * via this callback (e.g., a Cloudflare-bypass header for specific zones).
69
+ */
70
+ extraHeaders?: (url: string) => Record<string, string>;
71
+ }
72
+
73
+ export interface NavigateResult {
74
+ url: string;
75
+ title: string;
76
+ status: number | null;
77
+ }
78
+
79
+ export interface ConsoleEvent {
80
+ type: string; // 'log' | 'error' | 'warning' | ...
81
+ text: string;
82
+ location?: { url: string; lineNumber?: number; columnNumber?: number };
83
+ }
84
+
85
+ export interface PageErrorEvent {
86
+ message: string;
87
+ stack?: string;
88
+ }
89
+
90
+ export interface FailedRequest {
91
+ url: string;
92
+ method: string;
93
+ failure: string;
94
+ resourceType: string;
95
+ }
96
+
97
+ export interface Diagnostics {
98
+ consoleEvents: ConsoleEvent[];
99
+ consoleErrors: ConsoleEvent[];
100
+ pageErrors: PageErrorEvent[];
101
+ failedRequests: FailedRequest[];
102
+ viewport: { width: number; height: number } | null;
103
+ }
104
+
105
+ const DEFAULT_PROFILE = resolve(homedir(), ".cache", "harnery", "browser-profile");
106
+
107
+ export class Browser {
108
+ private context: BrowserContext | null = null;
109
+ private page: Page | null = null;
110
+ readonly profileDir: string;
111
+ private consoleEvents: ConsoleEvent[] = [];
112
+ private pageErrors: PageErrorEvent[] = [];
113
+ private failedRequests: FailedRequest[] = [];
114
+
115
+ constructor(private opts: BrowserOptions = {}) {
116
+ this.profileDir = opts.profileDir ?? DEFAULT_PROFILE;
117
+ }
118
+
119
+ /** Lazy: caller-side helper to find the active page if mid-flow. */
120
+ get currentPage(): Page {
121
+ if (!this.page) {
122
+ throw new Error("Browser not opened. Call open() first.");
123
+ }
124
+ return this.page;
125
+ }
126
+
127
+ async open(): Promise<void> {
128
+ if (this.context) return;
129
+ mkdirSync(this.profileDir, { recursive: true });
130
+
131
+ this.context = await chromium.launchPersistentContext(this.profileDir, {
132
+ headless: !this.opts.headed,
133
+ viewport: this.opts.viewport ?? { width: 1280, height: 800 },
134
+ ...(this.opts.recordHarPath
135
+ ? { recordHar: { path: this.opts.recordHarPath, mode: "full" as const } }
136
+ : {}),
137
+ });
138
+ this.context.setDefaultNavigationTimeout(this.opts.navigationTimeout ?? 30_000);
139
+
140
+ if (this.opts.jar) {
141
+ const jarCookies = this.opts.jar.list();
142
+ if (jarCookies.length > 0) {
143
+ await this.context.addCookies(jarCookies.map(toPWCookie));
144
+ }
145
+ }
146
+
147
+ // Caller-injected extraHeaders callback (e.g., for Cloudflare-bypass
148
+ // or custom auth headers). Per-request route handler so headers only
149
+ // attach when the callback returns non-empty.
150
+ const headersCb = this.opts.extraHeaders;
151
+ if (headersCb) {
152
+ await this.context.route("**/*", async (route, request) => {
153
+ const extra = headersCb(request.url());
154
+ if (Object.keys(extra).length === 0) return route.continue();
155
+ const headers = { ...request.headers(), ...extra };
156
+ return route.continue({ headers });
157
+ });
158
+ }
159
+
160
+ const pages = this.context.pages();
161
+ this.page = pages[0] ?? (await this.context.newPage());
162
+ this.attachDiagnosticListeners(this.page);
163
+ }
164
+
165
+ /**
166
+ * Hook console + pageerror + requestfailed events. Called on `open()`
167
+ * before any navigation so we don't miss early-fired events.
168
+ */
169
+ private attachDiagnosticListeners(page: Page): void {
170
+ page.on("console", (msg: ConsoleMessage) => {
171
+ const loc = msg.location();
172
+ this.consoleEvents.push({
173
+ type: msg.type(),
174
+ text: msg.text(),
175
+ location: loc.url
176
+ ? { url: loc.url, lineNumber: loc.lineNumber, columnNumber: loc.columnNumber }
177
+ : undefined,
178
+ });
179
+ });
180
+ page.on("pageerror", (err: Error) => {
181
+ this.pageErrors.push({ message: err.message, stack: err.stack });
182
+ });
183
+ page.on("requestfailed", (req: Request) => {
184
+ this.failedRequests.push({
185
+ url: req.url(),
186
+ method: req.method(),
187
+ failure: req.failure()?.errorText ?? "unknown",
188
+ resourceType: req.resourceType(),
189
+ });
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Snapshot of every event captured since `open()`. Returned objects are
195
+ * copies, so callers can safely store them after `close()`.
196
+ */
197
+ diagnostics(): Diagnostics {
198
+ return {
199
+ consoleEvents: [...this.consoleEvents],
200
+ consoleErrors: this.consoleEvents.filter((e) => e.type === "error"),
201
+ pageErrors: [...this.pageErrors],
202
+ failedRequests: [...this.failedRequests],
203
+ viewport: this.page?.viewportSize() ?? null,
204
+ };
205
+ }
206
+
207
+ async navigate(url: string): Promise<NavigateResult> {
208
+ const page = this.currentPage;
209
+ const response = await page.goto(url, { waitUntil: this.opts.waitUntil ?? "load" });
210
+ return {
211
+ url: page.url(),
212
+ title: await page.title(),
213
+ status: response?.status() ?? null,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Reload the current page. Preserves cookies + sessionStorage so callers can
219
+ * reproduce sessionStorage-restored UI state (e.g. drawers/modals that open
220
+ * automatically on reload, where Dialog auto-focus + Tooltip-on-focus may
221
+ * interact differently than the click-to-open path).
222
+ */
223
+ async reload(): Promise<NavigateResult> {
224
+ const page = this.currentPage;
225
+ const response = await page.reload({ waitUntil: this.opts.waitUntil ?? "load" });
226
+ return {
227
+ url: page.url(),
228
+ title: await page.title(),
229
+ status: response?.status() ?? null,
230
+ };
231
+ }
232
+
233
+ /** Full-page PNG screenshot. Returns the byte count written. */
234
+ async screenshot(path: string, opts: { fullPage?: boolean } = {}): Promise<number> {
235
+ const page = this.currentPage;
236
+ const buf = await page.screenshot({ path, fullPage: opts.fullPage ?? true, type: "png" });
237
+ return buf.length;
238
+ }
239
+
240
+ /**
241
+ * Plain-text snapshot of the document body. Suitable as a coarse "what's
242
+ * on screen" signal for LLM iteration loops. For richer extraction, use
243
+ * `htmlContent()` and pipe through a readability filter.
244
+ */
245
+ async textSnapshot(): Promise<string> {
246
+ const page = this.currentPage;
247
+ return await page.evaluate(() => document.body?.innerText ?? "");
248
+ }
249
+
250
+ /** Raw outer HTML of the page (or a selector if provided). */
251
+ async htmlContent(selector?: string): Promise<string> {
252
+ const page = this.currentPage;
253
+ if (selector) {
254
+ const el = await page.$(selector);
255
+ if (!el) throw new Error(`Selector matched nothing: ${selector}`);
256
+ return await el.evaluate((node) => (node as Element).outerHTML);
257
+ }
258
+ return await page.content();
259
+ }
260
+
261
+ async click(selector: string): Promise<void> {
262
+ await this.currentPage.click(selector);
263
+ }
264
+
265
+ async fill(selector: string, value: string): Promise<void> {
266
+ await this.currentPage.fill(selector, value);
267
+ }
268
+
269
+ async press(key: string): Promise<void> {
270
+ await this.currentPage.keyboard.press(key);
271
+ }
272
+
273
+ async waitForSelector(selector: string, timeout?: number): Promise<void> {
274
+ await this.currentPage.waitForSelector(selector, timeout ? { timeout } : undefined);
275
+ }
276
+
277
+ /** Evaluate JS in the page context. Caller is responsible for safety. */
278
+ async evaluate<T = unknown>(script: string): Promise<T> {
279
+ return await this.currentPage.evaluate(script);
280
+ }
281
+
282
+ /**
283
+ * Read the system clipboard via the page context. Grants `clipboard-read`
284
+ * to the page's origin first because Chromium gates `navigator.clipboard
285
+ * .readText()` behind a user-gesture + permission check; in headless
286
+ * Playwright there is no user gesture, so the permission grant is the
287
+ * substitute. Returns an empty string if the read returns nullish or
288
+ * throws (insecure context, focus race). Used by `browse --batch
289
+ * clipboard ...` to verify a UI Copy action end-to-end.
290
+ */
291
+ async readClipboard(): Promise<string> {
292
+ if (!this.context) throw new Error("Browser not opened. Call open() first.");
293
+ const url = this.currentPage.url();
294
+ try {
295
+ const origin = new URL(url).origin;
296
+ await this.context.grantPermissions(["clipboard-read"], { origin });
297
+ } catch {
298
+ /* about:blank / data: URL, skip permission; evaluate may still work */
299
+ }
300
+ return await this.currentPage.evaluate(async () => {
301
+ try {
302
+ const text = await navigator.clipboard.readText();
303
+ return typeof text === "string" ? text : "";
304
+ } catch {
305
+ return "";
306
+ }
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Run occlusion checks on one or more selectors. For each, samples a grid
312
+ * of points inside the element's bounding rect and uses
313
+ * `document.elementFromPoint` to detect whether the target is the topmost
314
+ * paintable element at each sample. Catches the class of UI bugs where
315
+ * an element's rect IS in-viewport but a higher-z-index sibling is
316
+ * painting over it.
317
+ */
318
+ async checkVisibility(
319
+ selectors: string[],
320
+ opts: CheckVisibilityOptions = {},
321
+ ): Promise<VisibilityResult[]> {
322
+ return await this.currentPage.evaluate(buildVisibilityCheck(), {
323
+ selectors,
324
+ sampleGrid: opts.sampleGrid ?? 3,
325
+ });
326
+ }
327
+
328
+ /** Inject annotation overlays for visibility results. Used before screenshot. */
329
+ async annotateVisibility(results: VisibilityResult[]): Promise<void> {
330
+ await this.currentPage.evaluate(buildAnnotateScript(), { results });
331
+ }
332
+
333
+ /** Remove visibility annotation overlays. */
334
+ async clearVisibilityAnnotations(): Promise<void> {
335
+ await this.currentPage.evaluate(buildClearAnnotationsScript());
336
+ }
337
+
338
+ /**
339
+ * Measure each selector's bounding rect + viewport-fill + parent-fill
340
+ * ratios. Catches the class of mobile-layout bug where a table sits at
341
+ * (say) 85% viewport fill because of stacked padding: every per-element
342
+ * check passes, but the user sees too-narrow content.
343
+ */
344
+ async checkWidth(selectors: string[]): Promise<WidthResult[]> {
345
+ return await this.currentPage.evaluate(buildWidthCheck(), { selectors });
346
+ }
347
+
348
+ /**
349
+ * Detect horizontal overflow at the document level. Returns viewport size,
350
+ * `document.scrollWidth`, and the top N elements protruding past the
351
+ * viewport's right edge. Catches the class of bug where a nav/table is
352
+ * wider than the viewport, forcing horizontal scroll on mobile.
353
+ */
354
+ async checkOverflow(opts: { sampleLimit?: number } = {}): Promise<OverflowResult> {
355
+ return await this.currentPage.evaluate(buildOverflowCheck(), {
356
+ sampleLimit: opts.sampleLimit ?? 5,
357
+ });
358
+ }
359
+
360
+ /** Inject annotation overlays for width + overflow results. Used before screenshot. */
361
+ async annotateLayout(args: {
362
+ widths: WidthResult[];
363
+ overflow: OverflowResult | null;
364
+ widthThreshold: number;
365
+ }): Promise<void> {
366
+ await this.currentPage.evaluate(buildLayoutAnnotateScript(), args);
367
+ }
368
+
369
+ /** Remove layout annotation overlays. */
370
+ async clearLayoutAnnotations(): Promise<void> {
371
+ await this.currentPage.evaluate(buildClearLayoutAnnotationsScript());
372
+ }
373
+
374
+ /**
375
+ * Inject a script that runs in every page context before page scripts
376
+ * execute. Useful for seeding localStorage before an SSR/CSR comparison;
377
+ * without this, state-dependent hydration mismatches are invisible to a
378
+ * clean-profile probe. Must be called after `open()` and before
379
+ * `navigate()`.
380
+ */
381
+ async addInitScript(script: string): Promise<void> {
382
+ if (!this.context) throw new Error("Browser not opened. Call open() first.");
383
+ await this.context.addInitScript(script);
384
+ }
385
+
386
+ /**
387
+ * Sync cookies from the live context back into the jar (if one was provided),
388
+ * then tear everything down. Safe to call multiple times.
389
+ */
390
+ async close(): Promise<void> {
391
+ if (!this.context) return;
392
+ if (this.opts.jar) {
393
+ try {
394
+ const live = await this.context.cookies();
395
+ for (const c of live) {
396
+ this.opts.jar.set(toJarCookie(c));
397
+ }
398
+ } catch {
399
+ // Cookie persist is best-effort; never block close on it.
400
+ }
401
+ }
402
+ await this.context.close().catch(() => {});
403
+ this.context = null;
404
+ this.page = null;
405
+ }
406
+ }
407
+
408
+ // ---------------------------------------------------------------------------
409
+ // Cookie shape conversion
410
+ // ---------------------------------------------------------------------------
411
+
412
+ function toPWCookie(c: JarCookie): PWCookie {
413
+ // Playwright's PWCookie type requires sameSite to be a literal; undefined
414
+ // is not allowed. Default to "Lax" (matches Chromium's modern default).
415
+ return {
416
+ name: c.name,
417
+ value: c.value,
418
+ domain: c.domain,
419
+ path: c.path,
420
+ expires: c.expires,
421
+ httpOnly: c.httpOnly,
422
+ secure: c.secure,
423
+ sameSite: normalizeSameSite(c.sameSite) ?? "Lax",
424
+ };
425
+ }
426
+
427
+ function toJarCookie(c: PWCookie): JarCookie {
428
+ return {
429
+ name: c.name,
430
+ value: c.value,
431
+ domain: c.domain,
432
+ path: c.path,
433
+ expires: c.expires,
434
+ httpOnly: c.httpOnly,
435
+ secure: c.secure,
436
+ sameSite: c.sameSite ?? undefined,
437
+ session: c.expires <= 0,
438
+ size: c.name.length + c.value.length,
439
+ };
440
+ }
441
+
442
+ function normalizeSameSite(s: string | undefined): "Strict" | "Lax" | "None" | undefined {
443
+ if (!s) return undefined;
444
+ const lower = s.toLowerCase();
445
+ if (lower === "strict") return "Strict";
446
+ if (lower === "lax") return "Lax";
447
+ if (lower === "none") return "None";
448
+ return undefined;
449
+ }