harnery 0.40.0 → 0.42.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 (595) hide show
  1. package/README.md +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +34 -20
  5. package/dist/commands/artifacts.d.ts.map +1 -1
  6. package/dist/commands/artifacts.js +113 -12
  7. package/dist/commands/browse-ai.d.ts.map +1 -1
  8. package/dist/commands/browse-ai.js +4 -0
  9. package/dist/commands/browse.d.ts +164 -0
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +388 -45
  12. package/dist/commands/checkpoint.js +8 -12
  13. package/dist/commands/claude-desktop.d.ts.map +1 -1
  14. package/dist/commands/claude-desktop.js +52 -0
  15. package/dist/commands/deinit.d.ts +1 -1
  16. package/dist/commands/deinit.d.ts.map +1 -1
  17. package/dist/commands/deinit.js +8 -4
  18. package/dist/commands/disk.d.ts +6 -0
  19. package/dist/commands/disk.d.ts.map +1 -0
  20. package/dist/commands/disk.js +80 -0
  21. package/dist/commands/docs.d.ts.map +1 -1
  22. package/dist/commands/docs.js +2 -0
  23. package/dist/commands/doctor.d.ts +8 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +58 -3
  26. package/dist/commands/fetch.d.ts.map +1 -1
  27. package/dist/commands/fetch.js +10 -0
  28. package/dist/commands/files.d.ts +16 -0
  29. package/dist/commands/files.d.ts.map +1 -1
  30. package/dist/commands/files.js +54 -2
  31. package/dist/commands/init.d.ts +21 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +271 -109
  34. package/dist/commands/instructions.js +5 -4
  35. package/dist/commands/ledger-v3.d.ts.map +1 -1
  36. package/dist/commands/ledger-v3.js +2 -1
  37. package/dist/commands/rm.d.ts +4 -0
  38. package/dist/commands/rm.d.ts.map +1 -0
  39. package/dist/commands/rm.js +87 -0
  40. package/dist/commands/servers.d.ts +7 -0
  41. package/dist/commands/servers.d.ts.map +1 -0
  42. package/dist/commands/servers.js +394 -0
  43. package/dist/commands/tunnel.d.ts.map +1 -1
  44. package/dist/commands/tunnel.js +82 -2
  45. package/dist/commands/webview.d.ts +4 -0
  46. package/dist/commands/webview.d.ts.map +1 -0
  47. package/dist/commands/webview.js +94 -0
  48. package/dist/core/adapter.d.ts +50 -1
  49. package/dist/core/adapter.d.ts.map +1 -1
  50. package/dist/core/adapter.js +59 -1
  51. package/dist/core/adapters/attestation.d.ts.map +1 -1
  52. package/dist/core/adapters/attestation.js +3 -2
  53. package/dist/core/adapters/profiles.d.ts +29 -3
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +53 -1
  56. package/dist/core/adapters/registry.d.ts.map +1 -1
  57. package/dist/core/adapters/registry.js +33 -0
  58. package/dist/core/adapters/types.d.ts +9 -0
  59. package/dist/core/adapters/types.d.ts.map +1 -1
  60. package/dist/core/agents/canonical-emit.d.ts +1 -1
  61. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  62. package/dist/core/agents/canonical-emit.js +2 -7
  63. package/dist/core/agents/cli.js +25 -16
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +7 -2
  66. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  67. package/dist/core/agents/render/session-context.js +2 -0
  68. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  69. package/dist/core/agents/rules/claim-conflict.js +3 -9
  70. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  71. package/dist/core/agents/rules/commit-conflict.js +2 -8
  72. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  73. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  74. package/dist/core/agents/rules/stop-hook.js +1 -1
  75. package/dist/core/agents/session-events.d.ts +6 -2
  76. package/dist/core/agents/session-events.d.ts.map +1 -1
  77. package/dist/core/agents/session-events.js +105 -26
  78. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
  79. package/dist/core/agents/session-finalization-state-v3.js +2 -1
  80. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  81. package/dist/core/agents/session-finalizer-v3.js +5 -4
  82. package/dist/core/agents/session-name-display.d.ts +28 -0
  83. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  84. package/dist/core/agents/session-name-display.js +43 -0
  85. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  86. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  87. package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
  88. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  89. package/dist/core/agents/state/heartbeat-writer.js +21 -2
  90. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  91. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  92. package/dist/core/agents/state/live-coordination-view.js +61 -0
  93. package/dist/core/agents/state/live-coordination-writer.js +3 -0
  94. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  95. package/dist/core/agents/state/stale-sweep.js +2 -10
  96. package/dist/core/agents/turn-changes.d.ts +17 -0
  97. package/dist/core/agents/turn-changes.d.ts.map +1 -0
  98. package/dist/core/agents/turn-changes.js +76 -0
  99. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  100. package/dist/core/artifacts/delivery-card.js +11 -3
  101. package/dist/core/artifacts/index.d.ts +140 -4
  102. package/dist/core/artifacts/index.d.ts.map +1 -1
  103. package/dist/core/artifacts/index.js +526 -52
  104. package/dist/core/config.d.ts +73 -11
  105. package/dist/core/config.d.ts.map +1 -1
  106. package/dist/core/config.js +142 -3
  107. package/dist/core/context/index.d.ts.map +1 -1
  108. package/dist/core/context/index.js +2 -1
  109. package/dist/core/conversations/archive.d.ts.map +1 -1
  110. package/dist/core/conversations/archive.js +4 -3
  111. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  112. package/dist/core/diagnostics/bundle.js +5 -4
  113. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
  114. package/dist/core/events/legacy-storage/canary.js +5 -4
  115. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
  116. package/dist/core/events/legacy-storage/compression.js +2 -1
  117. package/dist/core/events/v3/adapter-id.d.ts +10 -2
  118. package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
  119. package/dist/core/events/v3/adapter-id.js +16 -0
  120. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  121. package/dist/core/events/v3/authority-outbox.js +2 -1
  122. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  123. package/dist/core/events/v3/bootstrap.js +5 -4
  124. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  125. package/dist/core/events/v3/capabilities.js +17 -0
  126. package/dist/core/events/v3/contract.d.ts +5 -0
  127. package/dist/core/events/v3/contract.d.ts.map +1 -1
  128. package/dist/core/events/v3/contract.js +7 -1
  129. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  130. package/dist/core/events/v3/control-witness.js +3 -2
  131. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
  132. package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
  133. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
  134. package/dist/core/events/v3/fingerprint-keys.js +7 -6
  135. package/dist/core/events/v3/generated.d.ts +1 -1
  136. package/dist/core/events/v3/generated.js +1 -1
  137. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  138. package/dist/core/events/v3/live-feed.js +5 -4
  139. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  140. package/dist/core/events/v3/producers/command-recorder.js +8 -7
  141. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  142. package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
  143. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
  145. package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
  146. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  147. package/dist/core/events/v3/producers/hook-base.js +10 -4
  148. package/dist/core/events/v3/producers/hook.d.ts +2 -0
  149. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  150. package/dist/core/events/v3/producers/hook.js +16 -1
  151. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  152. package/dist/core/events/v3/producers/intake.js +16 -6
  153. package/dist/core/events/v3/producers/recorder.d.ts +2 -4
  154. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  155. package/dist/core/events/v3/producers/recorder.js +125 -101
  156. package/dist/core/events/v3/projection.js +9 -1
  157. package/dist/core/events/v3/recovery.d.ts.map +1 -1
  158. package/dist/core/events/v3/recovery.js +4 -3
  159. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
  160. package/dist/core/events/v3/support-storage/inventory.js +2 -1
  161. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
  162. package/dist/core/events/v3/support-storage/maintenance.js +4 -3
  163. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  164. package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
  165. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
  166. package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
  167. package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
  168. package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
  169. package/dist/core/events/v3/tool-line-changes.js +135 -0
  170. package/dist/core/events/v3/writer.d.ts.map +1 -1
  171. package/dist/core/events/v3/writer.js +8 -7
  172. package/dist/core/governor/planning.d.ts.map +1 -1
  173. package/dist/core/governor/planning.js +6 -5
  174. package/dist/core/governor/service.d.ts.map +1 -1
  175. package/dist/core/governor/service.js +12 -11
  176. package/dist/core/governor/state.d.ts.map +1 -1
  177. package/dist/core/governor/state.js +7 -6
  178. package/dist/core/guard/evaluator.js +1 -0
  179. package/dist/core/guard/storage.d.ts.map +1 -1
  180. package/dist/core/guard/storage.js +3 -2
  181. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
  182. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
  183. package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
  184. package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
  185. package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
  186. package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
  187. package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
  188. package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
  189. package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
  190. package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
  191. package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
  192. package/dist/core/hooks/adapter/behaviors/index.js +20 -0
  193. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
  194. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
  195. package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
  196. package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
  197. package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
  198. package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
  199. package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
  200. package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
  201. package/dist/core/hooks/adapter/behaviors/types.js +1 -0
  202. package/dist/core/hooks/adapter/detect.d.ts +1 -1
  203. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  204. package/dist/core/hooks/adapter/detect.js +2 -3
  205. package/dist/core/hooks/adapter/events.d.ts +46 -1
  206. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  207. package/dist/core/hooks/adapter/events.js +36 -0
  208. package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
  209. package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
  210. package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
  211. package/dist/core/hooks/adapter/output.d.ts +7 -0
  212. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  213. package/dist/core/hooks/adapter/output.js +8 -1
  214. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  215. package/dist/core/hooks/adapter/wiring.d.ts +52 -8
  216. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  217. package/dist/core/hooks/adapter/wiring.js +133 -12
  218. package/dist/core/hooks/cli.js +676 -531
  219. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  220. package/dist/core/hooks/effects/image-capture.js +2 -1
  221. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
  222. package/dist/core/hooks/prompt-context/contract.js +1 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts +1 -1
  224. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  225. package/dist/core/hooks/resolve/transcript.js +35 -4
  226. package/dist/core/hooks/run-markers.d.ts +37 -0
  227. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  228. package/dist/core/hooks/run-markers.js +109 -0
  229. package/dist/core/hooks/session-name-presence.d.ts +3 -1
  230. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  231. package/dist/core/hooks/session-name-presence.js +7 -2
  232. package/dist/core/hooks/shell-waiter.d.ts +2 -0
  233. package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
  234. package/dist/core/hooks/shell-waiter.js +173 -0
  235. package/dist/core/resources/service.d.ts.map +1 -1
  236. package/dist/core/resources/service.js +5 -4
  237. package/dist/core/semantic/review.d.ts.map +1 -1
  238. package/dist/core/semantic/review.js +5 -4
  239. package/dist/core/semantic/service.d.ts.map +1 -1
  240. package/dist/core/semantic/service.js +15 -14
  241. package/dist/core/semantic/storage.d.ts.map +1 -1
  242. package/dist/core/semantic/storage.js +5 -4
  243. package/dist/core/servers/index.d.ts +211 -0
  244. package/dist/core/servers/index.d.ts.map +1 -0
  245. package/dist/core/servers/index.js +560 -0
  246. package/dist/core/servers/net.d.ts +58 -0
  247. package/dist/core/servers/net.d.ts.map +1 -0
  248. package/dist/core/servers/net.js +270 -0
  249. package/dist/core/servers/tunnels.d.ts +14 -0
  250. package/dist/core/servers/tunnels.d.ts.map +1 -0
  251. package/dist/core/servers/tunnels.js +33 -0
  252. package/dist/core/storage/atomic-json.d.ts.map +1 -1
  253. package/dist/core/storage/atomic-json.js +7 -3
  254. package/dist/core/storage/builtins.js +1 -0
  255. package/dist/core/storage/durable-history.d.ts.map +1 -1
  256. package/dist/core/storage/durable-history.js +6 -5
  257. package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
  258. package/dist/core/storage/maintenance-providers.js +2 -1
  259. package/dist/core/storage/maintenance.d.ts.map +1 -1
  260. package/dist/core/storage/maintenance.js +7 -6
  261. package/dist/core/storage/metrics.d.ts.map +1 -1
  262. package/dist/core/storage/metrics.js +5 -1
  263. package/dist/core/storage/modes.d.ts +17 -0
  264. package/dist/core/storage/modes.d.ts.map +1 -0
  265. package/dist/core/storage/modes.js +47 -0
  266. package/dist/core/storage/process-log.d.ts.map +1 -1
  267. package/dist/core/storage/process-log.js +3 -2
  268. package/dist/core/storage/receipts.d.ts.map +1 -1
  269. package/dist/core/storage/receipts.js +4 -3
  270. package/dist/core/storage/segments.d.ts.map +1 -1
  271. package/dist/core/storage/segments.js +7 -6
  272. package/dist/core/supervisor/hook-health.d.ts +2 -1
  273. package/dist/core/supervisor/hook-health.d.ts.map +1 -1
  274. package/dist/core/supervisor/hook-health.js +4 -3
  275. package/dist/core/supervisor/service.d.ts.map +1 -1
  276. package/dist/core/supervisor/service.js +7 -6
  277. package/dist/core/work/state.d.ts.map +1 -1
  278. package/dist/core/work/state.js +8 -7
  279. package/dist/core/workflow/adapters.d.ts +7 -0
  280. package/dist/core/workflow/adapters.d.ts.map +1 -1
  281. package/dist/core/workflow/adapters.js +9 -0
  282. package/dist/core/workflow/approvals.d.ts.map +1 -1
  283. package/dist/core/workflow/approvals.js +6 -5
  284. package/dist/core/workflow/billing.d.ts.map +1 -1
  285. package/dist/core/workflow/billing.js +33 -4
  286. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  287. package/dist/core/workflow/durable-record.js +4 -3
  288. package/dist/core/workflow/engine.d.ts.map +1 -1
  289. package/dist/core/workflow/engine.js +30 -1
  290. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  291. package/dist/core/workflow/live-session-v3.js +2 -3
  292. package/dist/core/workflow/run-state.d.ts.map +1 -1
  293. package/dist/core/workflow/run-state.js +3 -2
  294. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  295. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  296. package/dist/core/workflow/sandbox-projection.js +23 -0
  297. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  298. package/dist/core/workflow/spawn-claude.js +6 -1
  299. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  300. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  301. package/dist/core/workflow/spawn-codex.js +20 -1
  302. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  303. package/dist/core/workflow/spawn-cursor.js +6 -1
  304. package/dist/core/workflow/spawn-opencode.d.ts +36 -0
  305. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
  306. package/dist/core/workflow/spawn-opencode.js +135 -0
  307. package/dist/core/workflow/transcript.d.ts.map +1 -1
  308. package/dist/core/workflow/transcript.js +3 -2
  309. package/dist/core/workflow/types.d.ts +47 -2
  310. package/dist/core/workflow/types.d.ts.map +1 -1
  311. package/dist/core/workflow/worker-access.d.ts +92 -0
  312. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  313. package/dist/core/workflow/worker-access.js +450 -0
  314. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  315. package/dist/core/workflow/workspaces/leases.js +5 -4
  316. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  317. package/dist/core/workflow/workspaces/local-git.js +3 -2
  318. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
  319. package/dist/core/workflow/workspaces/paths.js +2 -1
  320. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  321. package/dist/core/workflow/workspaces/state.js +10 -9
  322. package/dist/lazy-commands.js +8 -1
  323. package/dist/lib/agent-browser/client.d.ts +9 -0
  324. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  325. package/dist/lib/agent-browser/client.js +13 -0
  326. package/dist/lib/browser/batch-steps.d.ts +18 -0
  327. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  328. package/dist/lib/browser/batch-steps.js +82 -0
  329. package/dist/lib/browser/client.d.ts +35 -1
  330. package/dist/lib/browser/client.d.ts.map +1 -1
  331. package/dist/lib/browser/client.js +117 -8
  332. package/dist/lib/browser/collect/collector.d.ts +99 -0
  333. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  334. package/dist/lib/browser/collect/collector.js +202 -0
  335. package/dist/lib/browser/collect/fields.d.ts +31 -0
  336. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  337. package/dist/lib/browser/collect/fields.js +75 -0
  338. package/dist/lib/browser/collect/index.d.ts +6 -0
  339. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  340. package/dist/lib/browser/collect/index.js +5 -0
  341. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  342. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  343. package/dist/lib/browser/collect/page-driver.js +306 -0
  344. package/dist/lib/browser/collect/presets.d.ts +52 -0
  345. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  346. package/dist/lib/browser/collect/presets.js +176 -0
  347. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  348. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  349. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  350. package/dist/lib/browser/identity.d.ts +25 -0
  351. package/dist/lib/browser/identity.d.ts.map +1 -0
  352. package/dist/lib/browser/identity.js +66 -0
  353. package/dist/lib/browser/index.d.ts +3 -2
  354. package/dist/lib/browser/index.d.ts.map +1 -1
  355. package/dist/lib/browser/index.js +2 -1
  356. package/dist/lib/browser/launch-args.d.ts +26 -0
  357. package/dist/lib/browser/launch-args.d.ts.map +1 -1
  358. package/dist/lib/browser/launch-args.js +41 -1
  359. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  360. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  361. package/dist/lib/browser/page-review-judge.js +13 -0
  362. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  363. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  364. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  365. package/dist/lib/browser/qa-run.js +1 -0
  366. package/dist/lib/browser/user-agent.d.ts +53 -0
  367. package/dist/lib/browser/user-agent.d.ts.map +1 -0
  368. package/dist/lib/browser/user-agent.js +79 -0
  369. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  370. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  371. package/dist/lib/browser/windows-chrome.js +179 -0
  372. package/dist/lib/claude-desktop-share.d.ts +136 -0
  373. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  374. package/dist/lib/claude-desktop-share.js +394 -0
  375. package/dist/lib/disk-usage.d.ts +50 -0
  376. package/dist/lib/disk-usage.d.ts.map +1 -0
  377. package/dist/lib/disk-usage.js +303 -0
  378. package/dist/lib/docs-lint.d.ts +6 -0
  379. package/dist/lib/docs-lint.d.ts.map +1 -1
  380. package/dist/lib/docs-lint.js +8 -3
  381. package/dist/lib/docs-metadata-managed.d.ts +1 -0
  382. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  383. package/dist/lib/docs-metadata-managed.js +17 -1
  384. package/dist/lib/guarded-remove.d.ts +34 -0
  385. package/dist/lib/guarded-remove.d.ts.map +1 -0
  386. package/dist/lib/guarded-remove.js +310 -0
  387. package/dist/lib/identities/assume.d.ts.map +1 -1
  388. package/dist/lib/identities/assume.js +4 -9
  389. package/dist/lib/instructions/apply.d.ts.map +1 -1
  390. package/dist/lib/instructions/apply.js +2 -0
  391. package/dist/lib/instructions/templates.js +8 -8
  392. package/dist/lib/pace/index.d.ts +2 -0
  393. package/dist/lib/pace/index.d.ts.map +1 -0
  394. package/dist/lib/pace/index.js +1 -0
  395. package/dist/lib/pace/pace.d.ts +95 -0
  396. package/dist/lib/pace/pace.d.ts.map +1 -0
  397. package/dist/lib/pace/pace.js +313 -0
  398. package/dist/lib/tunnel/error-page.d.ts +1 -1
  399. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  400. package/dist/lib/tunnel/error-page.js +19 -8
  401. package/dist/lib/tunnel/forward-headers.d.ts +11 -0
  402. package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
  403. package/dist/lib/tunnel/forward-headers.js +17 -0
  404. package/dist/lib/tunnel/gate.js +34 -10
  405. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  406. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  407. package/dist/lib/tunnel/path-scope.js +62 -0
  408. package/dist/lib/tunnel/state.d.ts +5 -0
  409. package/dist/lib/tunnel/state.d.ts.map +1 -1
  410. package/dist/lib/tunnel/state.js +17 -3
  411. package/dist/lib/webview/index.d.ts +2 -0
  412. package/dist/lib/webview/index.d.ts.map +1 -0
  413. package/dist/lib/webview/index.js +1 -0
  414. package/dist/lib/webview/webview.d.ts +116 -0
  415. package/dist/lib/webview/webview.d.ts.map +1 -0
  416. package/dist/lib/webview/webview.js +289 -0
  417. package/opencode-plugin/package.json +19 -0
  418. package/opencode-plugin/src/index.ts +791 -0
  419. package/package.json +16 -4
  420. package/schemas/config.schema.json +104 -0
  421. package/schemas/event-v3.schema.json +164 -0
  422. package/src/commander.ts +26 -0
  423. package/src/commands/agents.ts +47 -18
  424. package/src/commands/artifacts.ts +171 -19
  425. package/src/commands/browse-ai.ts +8 -0
  426. package/src/commands/browse.ts +570 -48
  427. package/src/commands/checkpoint.ts +7 -9
  428. package/src/commands/claude-desktop.ts +61 -0
  429. package/src/commands/deinit.ts +9 -4
  430. package/src/commands/disk.ts +114 -0
  431. package/src/commands/docs.ts +2 -0
  432. package/src/commands/doctor.ts +63 -3
  433. package/src/commands/fetch.ts +14 -0
  434. package/src/commands/files.ts +93 -2
  435. package/src/commands/init.ts +334 -122
  436. package/src/commands/instructions.ts +8 -4
  437. package/src/commands/ledger-v3.ts +2 -1
  438. package/src/commands/rm.ts +115 -0
  439. package/src/commands/servers.ts +506 -0
  440. package/src/commands/tunnel.ts +102 -2
  441. package/src/commands/webview.ts +159 -0
  442. package/src/core/adapter.ts +102 -1
  443. package/src/core/adapters/attestation.ts +3 -2
  444. package/src/core/adapters/profiles.ts +63 -1
  445. package/src/core/adapters/registry.ts +37 -0
  446. package/src/core/adapters/types.ts +10 -0
  447. package/src/core/agents/canonical-emit.ts +2 -5
  448. package/src/core/agents/cli.ts +26 -17
  449. package/src/core/agents/coord-client.ts +9 -3
  450. package/src/core/agents/render/session-context.ts +1 -0
  451. package/src/core/agents/rules/claim-conflict.ts +3 -8
  452. package/src/core/agents/rules/commit-conflict.ts +2 -7
  453. package/src/core/agents/rules/stop-hook.ts +3 -1
  454. package/src/core/agents/session-events.ts +120 -26
  455. package/src/core/agents/session-finalization-state-v3.ts +2 -1
  456. package/src/core/agents/session-finalizer-v3.ts +5 -4
  457. package/src/core/agents/session-name-display.ts +48 -0
  458. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  459. package/src/core/agents/state/heartbeat-writer.ts +24 -2
  460. package/src/core/agents/state/live-coordination-view.ts +74 -0
  461. package/src/core/agents/state/live-coordination-writer.ts +3 -0
  462. package/src/core/agents/state/stale-sweep.ts +2 -9
  463. package/src/core/agents/turn-changes.ts +97 -0
  464. package/src/core/artifacts/delivery-card.ts +12 -3
  465. package/src/core/artifacts/index.ts +701 -47
  466. package/src/core/config.ts +206 -4
  467. package/src/core/context/index.ts +2 -1
  468. package/src/core/conversations/archive.ts +4 -3
  469. package/src/core/diagnostics/bundle.ts +5 -3
  470. package/src/core/events/legacy-storage/canary.ts +5 -4
  471. package/src/core/events/legacy-storage/compression.ts +2 -1
  472. package/src/core/events/v3/adapter-id.ts +29 -1
  473. package/src/core/events/v3/authority-outbox.ts +2 -1
  474. package/src/core/events/v3/bootstrap.ts +5 -4
  475. package/src/core/events/v3/capabilities.ts +17 -0
  476. package/src/core/events/v3/contract.ts +9 -1
  477. package/src/core/events/v3/control-witness.ts +3 -2
  478. package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
  479. package/src/core/events/v3/fingerprint-keys.ts +7 -6
  480. package/src/core/events/v3/generated.ts +1 -1
  481. package/src/core/events/v3/live-feed.ts +5 -4
  482. package/src/core/events/v3/producers/command-recorder.ts +8 -6
  483. package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
  484. package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
  485. package/src/core/events/v3/producers/hook-base.ts +11 -4
  486. package/src/core/events/v3/producers/hook.ts +19 -1
  487. package/src/core/events/v3/producers/intake.ts +18 -6
  488. package/src/core/events/v3/producers/recorder.ts +139 -114
  489. package/src/core/events/v3/projection.ts +11 -1
  490. package/src/core/events/v3/recovery.ts +4 -3
  491. package/src/core/events/v3/support-storage/inventory.ts +2 -1
  492. package/src/core/events/v3/support-storage/maintenance.ts +8 -3
  493. package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
  494. package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
  495. package/src/core/events/v3/tool-line-changes.ts +139 -0
  496. package/src/core/events/v3/writer.ts +8 -7
  497. package/src/core/governor/planning.ts +6 -5
  498. package/src/core/governor/service.ts +12 -11
  499. package/src/core/governor/state.ts +7 -6
  500. package/src/core/guard/evaluator.ts +1 -0
  501. package/src/core/guard/storage.ts +3 -2
  502. package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
  503. package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
  504. package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
  505. package/src/core/hooks/adapter/behaviors/index.ts +31 -0
  506. package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
  507. package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
  508. package/src/core/hooks/adapter/behaviors/types.ts +110 -0
  509. package/src/core/hooks/adapter/detect.ts +2 -3
  510. package/src/core/hooks/adapter/events.ts +72 -1
  511. package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
  512. package/src/core/hooks/adapter/output.ts +8 -1
  513. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  514. package/src/core/hooks/adapter/wiring.ts +198 -16
  515. package/src/core/hooks/cli.ts +776 -539
  516. package/src/core/hooks/effects/image-capture.ts +2 -1
  517. package/src/core/hooks/prompt-context/contract.ts +1 -0
  518. package/src/core/hooks/resolve/transcript.ts +42 -5
  519. package/src/core/hooks/run-markers.ts +132 -0
  520. package/src/core/hooks/session-name-presence.ts +10 -3
  521. package/src/core/hooks/shell-waiter.ts +166 -0
  522. package/src/core/resources/service.ts +5 -4
  523. package/src/core/semantic/review.ts +5 -4
  524. package/src/core/semantic/service.ts +15 -14
  525. package/src/core/semantic/storage.ts +5 -4
  526. package/src/core/servers/index.ts +712 -0
  527. package/src/core/servers/net.ts +283 -0
  528. package/src/core/servers/tunnels.ts +39 -0
  529. package/src/core/storage/atomic-json.ts +7 -3
  530. package/src/core/storage/builtins.ts +1 -0
  531. package/src/core/storage/durable-history.ts +6 -5
  532. package/src/core/storage/maintenance-providers.ts +2 -1
  533. package/src/core/storage/maintenance.ts +7 -6
  534. package/src/core/storage/metrics.ts +5 -1
  535. package/src/core/storage/modes.ts +53 -0
  536. package/src/core/storage/process-log.ts +3 -2
  537. package/src/core/storage/receipts.ts +4 -3
  538. package/src/core/storage/segments.ts +7 -6
  539. package/src/core/supervisor/hook-health.ts +4 -4
  540. package/src/core/supervisor/service.ts +7 -6
  541. package/src/core/work/state.ts +8 -7
  542. package/src/core/workflow/adapters.ts +15 -0
  543. package/src/core/workflow/approvals.ts +6 -5
  544. package/src/core/workflow/billing.ts +34 -4
  545. package/src/core/workflow/durable-record.ts +4 -3
  546. package/src/core/workflow/engine.ts +38 -1
  547. package/src/core/workflow/live-session-v3.ts +2 -3
  548. package/src/core/workflow/run-state.ts +3 -2
  549. package/src/core/workflow/sandbox-projection.ts +38 -2
  550. package/src/core/workflow/spawn-claude.ts +5 -1
  551. package/src/core/workflow/spawn-codex.ts +18 -1
  552. package/src/core/workflow/spawn-cursor.ts +5 -1
  553. package/src/core/workflow/spawn-opencode.ts +153 -0
  554. package/src/core/workflow/transcript.ts +3 -2
  555. package/src/core/workflow/types.ts +62 -2
  556. package/src/core/workflow/worker-access.ts +525 -0
  557. package/src/core/workflow/workspaces/leases.ts +5 -4
  558. package/src/core/workflow/workspaces/local-git.ts +3 -2
  559. package/src/core/workflow/workspaces/paths.ts +2 -1
  560. package/src/core/workflow/workspaces/state.ts +10 -9
  561. package/src/lazy-commands.ts +11 -1
  562. package/src/lib/agent-browser/client.ts +18 -0
  563. package/src/lib/browser/batch-steps.ts +79 -0
  564. package/src/lib/browser/client.ts +147 -9
  565. package/src/lib/browser/collect/collector.ts +306 -0
  566. package/src/lib/browser/collect/fields.ts +87 -0
  567. package/src/lib/browser/collect/index.ts +47 -0
  568. package/src/lib/browser/collect/page-driver.ts +327 -0
  569. package/src/lib/browser/collect/presets.ts +237 -0
  570. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  571. package/src/lib/browser/identity.ts +64 -0
  572. package/src/lib/browser/index.ts +16 -1
  573. package/src/lib/browser/launch-args.ts +53 -1
  574. package/src/lib/browser/page-review-judge.ts +16 -0
  575. package/src/lib/browser/qa-run-contracts.ts +11 -0
  576. package/src/lib/browser/qa-run.ts +1 -0
  577. package/src/lib/browser/user-agent.ts +130 -0
  578. package/src/lib/browser/windows-chrome.ts +227 -0
  579. package/src/lib/claude-desktop-share.ts +511 -0
  580. package/src/lib/disk-usage.ts +347 -0
  581. package/src/lib/docs-lint.ts +8 -3
  582. package/src/lib/docs-metadata-managed.ts +21 -0
  583. package/src/lib/guarded-remove.ts +376 -0
  584. package/src/lib/identities/assume.ts +7 -7
  585. package/src/lib/instructions/apply.ts +2 -0
  586. package/src/lib/instructions/templates.ts +8 -8
  587. package/src/lib/pace/index.ts +14 -0
  588. package/src/lib/pace/pace.ts +370 -0
  589. package/src/lib/tunnel/error-page.ts +25 -9
  590. package/src/lib/tunnel/forward-headers.ts +16 -0
  591. package/src/lib/tunnel/gate.ts +39 -10
  592. package/src/lib/tunnel/path-scope.ts +73 -0
  593. package/src/lib/tunnel/state.ts +20 -3
  594. package/src/lib/webview/index.ts +13 -0
  595. package/src/lib/webview/webview.ts +457 -0
@@ -1,10 +1,13 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
3
  import { homedir } from "node:os";
3
4
  import { dirname, isAbsolute, resolve } from "node:path";
4
5
  import type { Command } from "commander";
6
+ import { chromium } from "playwright";
5
7
  import { PNG } from "pngjs";
6
8
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
7
9
  import { resolveBinName } from "../core/config.ts";
10
+ import { splitBatchSteps } from "../lib/browser/batch-steps.ts";
8
11
  import {
9
12
  CAPTURE_FIDELITY_MISMATCH_THRESHOLD,
10
13
  compareBand,
@@ -13,6 +16,22 @@ import {
13
16
  type FidelityProbe,
14
17
  pngDimensions,
15
18
  } from "../lib/browser/capture-fidelity.ts";
19
+ import {
20
+ type CollectLimits,
21
+ createPageDriver,
22
+ createRng,
23
+ DEFAULT_COLLECT_LIMITS,
24
+ PACING_LIMITS,
25
+ parseProbability,
26
+ parseRange,
27
+ presetNames,
28
+ type ResolvedCollectSpec,
29
+ randomSeed,
30
+ resolveCollectSpec,
31
+ runCollect,
32
+ type ScrollPacing,
33
+ scrollPacingFromEnv,
34
+ } from "../lib/browser/collect/index.ts";
16
35
  import {
17
36
  type AssertResult,
18
37
  Browser,
@@ -24,15 +43,19 @@ import {
24
43
  type CritiqueResult,
25
44
  type CritiqueTile,
26
45
  captureDevOverlay,
46
+ chromeMajorFromExecutable,
27
47
  DEFAULT_CRITIQUE_RUBRIC,
28
48
  type DevOverlayResult,
29
49
  type Diagnostics,
30
50
  extractObservedIp,
51
+ installedChromeChannel,
52
+ installedChromePath,
31
53
  type LayoutAxis,
32
54
  type LayoutLintResult,
33
55
  type OverflowResult,
34
56
  parseAssertSpec,
35
57
  type RuntsResult,
58
+ resolveUserAgent,
36
59
  runCritique,
37
60
  type StandaloneHtmlResult,
38
61
  type TargetSizeProfile,
@@ -44,6 +67,7 @@ import {
44
67
  writeNetscapeCookieFile,
45
68
  wslHeadedLaunchArgs,
46
69
  } from "../lib/browser/index.ts";
70
+ import { isWSL } from "../lib/browser/launch-args.ts";
47
71
  import {
48
72
  allocateTileBudget,
49
73
  validatePageReviewAllocation,
@@ -107,7 +131,17 @@ import {
107
131
  type SaveBaselineResult,
108
132
  saveBaseline,
109
133
  } from "../lib/browser/visual-diff.ts";
110
- import { applyExtraCookies, CookieJar } from "../lib/cookies/index.ts";
134
+ import {
135
+ harvestDomain,
136
+ harvestWindowsChromeCookies,
137
+ stopWindowsChromeProfile,
138
+ windowsChromePath,
139
+ windowsLocalAppData,
140
+ windowsPlainProfile,
141
+ wslPathToWindows,
142
+ } from "../lib/browser/windows-chrome.ts";
143
+ import { applyExtraCookies, CookieJar, mergeCookies } from "../lib/cookies/index.ts";
144
+ import { commandPaceGate } from "../lib/pace/index.ts";
111
145
 
112
146
  /**
113
147
  * `harn browse <url>`: Playwright-backed page navigation with shared
@@ -160,15 +194,38 @@ interface BrowseOpts {
160
194
  evaluate?: string;
161
195
  captureEvaluate?: string;
162
196
  batch?: string;
197
+ // Scroll-and-collect mode (--collect [preset])
198
+ collect?: string | boolean;
199
+ collectItem?: string;
200
+ collectKey?: string;
201
+ collectKeyPattern?: string;
202
+ collectField?: string[];
203
+ collectExpand?: string | boolean;
204
+ collectMaxItems?: string;
205
+ collectMaxScrolls?: string;
206
+ collectMaxSeconds?: string;
207
+ collectIdle?: string;
208
+ collectStep?: string;
209
+ collectPause?: string;
210
+ collectReadChance?: string;
211
+ collectReadPause?: string;
212
+ collectBackChance?: string;
213
+ collectSeed?: string;
214
+ collectFormat?: string;
215
+ collectOut?: string;
163
216
  networkHar?: string;
164
217
  login?: boolean;
218
+ attached?: boolean;
165
219
  loginCloseFile?: string;
166
220
  controlFile?: string;
167
221
  headed?: boolean;
168
222
  browserArg?: string[];
223
+ browserChannel?: string;
224
+ userAgent?: string;
169
225
  proxyFromEnv?: boolean;
170
226
  exportCookies?: string;
171
227
  cookies?: boolean;
228
+ pace?: boolean;
172
229
  store?: string;
173
230
  profile?: string;
174
231
  viewport?: string;
@@ -334,13 +391,73 @@ export function registerBrowseCommand(
334
391
  "--batch <steps>",
335
392
  "Run multiple steps in one session, semicolon-separated. Each step is one of: " +
336
393
  "`click <selector>`, `fill <selector=>value>`, `press <key>`, `wait <selector|ms>`, `eval <js>`, `reload`. " +
337
- 'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.',
394
+ 'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state. ' +
395
+ "An `eval` step keeps semicolons inside JS quotes and (), [], {}; `\\;` is always a literal semicolon.",
396
+ )
397
+ .option(
398
+ "--collect [preset]",
399
+ "Scroll an infinite feed like a reader and collect every item that passes through the DOM, " +
400
+ `de-duplicated, as JSON. Presets: ${presetNames().join(", ")} (default generic). ` +
401
+ "Skips the trio, checks and --evaluate; runs after --wait-for and --batch.",
402
+ )
403
+ .option("--collect-item <css>", "Item selector (overrides the preset's).")
404
+ .option(
405
+ "--collect-key <extractor>",
406
+ "Dedupe key extractor, <selector>[@attr] inside the item. Default: the preset key, else the " +
407
+ "first permalink-like link, else a hash of the item text.",
408
+ )
409
+ .option("--collect-key-pattern <regex>", "Regex a link must match to count as a permalink key.")
410
+ .option(
411
+ "--collect-field <name=extractor>",
412
+ "Add or override a field: name=<selector>[@attr]. name[]= collects every match. Empty selector " +
413
+ "or :scope is the item itself; no @attr reads visible text (repeatable).",
414
+ (value: string, previous: string[] = []) => [...previous, value],
415
+ [] as string[],
416
+ )
417
+ .option(
418
+ "--collect-expand [selector]",
419
+ "Click 'show more' buttons inside visible items before harvesting (preset selector by default). " +
420
+ "At most 3 per step and 50 per run; links and action buttons are never clicked.",
421
+ )
422
+ .option("--collect-max-items <n>", "Stop after this many unique items.")
423
+ .option("--collect-max-scrolls <n>", "Stop after this many scroll steps (default 60).", "60")
424
+ .option("--collect-max-seconds <n>", "Stop after this much wall time (default 300).", "300")
425
+ .option(
426
+ "--collect-idle <n>",
427
+ "Stop after this many steps with no new items and no page growth (default 5).",
428
+ "5",
429
+ )
430
+ .option(
431
+ "--collect-step <min-max>",
432
+ "Scroll distance per step as a viewport fraction (default 0.6-1.1; env HARNERY_COLLECT_STEP).",
433
+ )
434
+ .option(
435
+ "--collect-pause <min-max>",
436
+ "Pause after each step in ms (default 1200-3500; env HARNERY_COLLECT_PAUSE_MS).",
437
+ )
438
+ .option(
439
+ "--collect-read-chance <p>",
440
+ "Chance of a longer reading pause per step (default 0.12).",
441
+ )
442
+ .option("--collect-read-pause <min-max>", "Reading pause in ms (default 4000-9000).")
443
+ .option(
444
+ "--collect-back-chance <p>",
445
+ "Chance a step starts with a small scroll back up (default 0.08).",
338
446
  )
447
+ .option(
448
+ "--collect-seed <n>",
449
+ "Seed for the scroll schedule; the envelope reports the seed used.",
450
+ )
451
+ .option(
452
+ "--collect-format <format>",
453
+ "json (envelope, default) or jsonl (one item per line).",
454
+ "json",
455
+ )
456
+ .option("--collect-out <path>", "Write the collection to this file instead of stdout.")
339
457
  .option("--network-har <path>", "Record network traffic to a HAR file (finalized on close)")
340
458
  .option(
341
459
  "--viewport <preset|WxH>",
342
- "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'",
343
- "desktop",
460
+ "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'. Default: real window in headed mode, desktop in headless mode",
344
461
  )
345
462
  .option(
346
463
  "--color-scheme <scheme>",
@@ -348,23 +465,52 @@ export function registerBrowseCommand(
348
465
  "Renders theme-aware pages in that scheme without page-specific toggles. " +
349
466
  "Absent = browser default (unchanged behavior).",
350
467
  )
351
- .option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
468
+ .option(
469
+ "--login",
470
+ "Open the URL in the installed Google Chrome with nothing attached (no DevTools protocol, no " +
471
+ "Playwright) so a person can sign in; under WSL this is the Windows host's Chrome. Sign in, then " +
472
+ "close the window or create the --login-close-file; later browse and browse-session runs reuse " +
473
+ "the cookies. Sites cannot tell this window from the person's own browser.",
474
+ )
475
+ .option(
476
+ "--attached",
477
+ "With --login: open an automation-controlled Chrome that this process and --control-file can drive. " +
478
+ "Sites can detect it and may lock the account, so use it only for agent-driven headed sessions on " +
479
+ "sites you own. Never use it to sign in to a third-party account.",
480
+ )
352
481
  .option(
353
482
  "--login-close-file <path>",
354
483
  "With --login, wait for this file instead of terminal Enter, then remove it and close cleanly",
355
484
  )
356
485
  .option(
357
486
  "--control-file <path>",
358
- "With --login, publish an owner-only descriptor for repeated browse-session control",
487
+ "With --login --attached, publish an owner-only descriptor for repeated browse-session control",
488
+ )
489
+ .option(
490
+ "--headed",
491
+ "Automation-controlled headed window for one-off visual checks. Not for signing in; use --login.",
492
+ )
493
+ .option(
494
+ "--browser-channel <name>",
495
+ "Browser to launch: chrome | chrome-beta | msedge | chromium. Headed launches (--login, --headed) " +
496
+ "default to the installed Google Chrome with the automation signals removed, because the bundled " +
497
+ '"Chrome for Testing" build advertises itself and sign-in flows refuse clicks; chromium forces the ' +
498
+ "bundled build. HARNERY_BROWSER_CHANNEL sets the default machine-wide.",
499
+ )
500
+ .option(
501
+ "--user-agent <ua|auto|native>",
502
+ "User agent to present. Default: a normal Windows (or macOS on a Mac) Chrome user agent at the launched " +
503
+ "Chrome's major version, stored at ~/.cache/harnery/user-agent.json and reused by every surface; never " +
504
+ "Linux, never HeadlessChrome. A string rewrites the store, auto refreshes it, native uses the browser's own. " +
505
+ "HARNERY_BROWSER_UA overrides for one environment.",
359
506
  )
360
- .option("--headed", "Headed mode for one-off (no auth-flow framing)")
361
507
  .option(
362
508
  "--browser-arg <flag>",
363
509
  "Extra Chromium launch flag, passed straight to the browser (repeatable). " +
364
510
  "e.g. --browser-arg --disable-gpu. Also settable machine-wide via the " +
365
- "HARNERY_BROWSER_ARGS env var (whitespace-separated). Under WSL, headed " +
366
- "launches auto-add --disable-gpu to fix blank-window paint; opt out with " +
367
- "HARNERY_BROWSER_NO_WSL_DEFAULTS=1.",
511
+ "HARNERY_BROWSER_ARGS env var (whitespace-separated). Under WSL, set " +
512
+ "HARNERY_BROWSER_WSL_DISABLE_GPU=1 to add --disable-gpu when a headed window paints blank; " +
513
+ "it is off by default because software WebGL is a bot tell.",
368
514
  (value: string, prev: string[] = []) => [...prev, value],
369
515
  [] as string[],
370
516
  )
@@ -376,6 +522,11 @@ export function registerBrowseCommand(
376
522
  "the expected IP is verified before the requested URL opens.",
377
523
  )
378
524
  .option("--no-cookies", "Skip cookie-jar attach and persist")
525
+ .option(
526
+ "--no-pace",
527
+ "Skip the human-pace gap (3 to 9 s) between page loads of the same site for this run. " +
528
+ "HARNERY_PACE=off disables it machine-wide; local and private hosts never wait.",
529
+ )
379
530
  .option("--store <path>", `Cookie store path (default ${DEFAULT_STORE})`)
380
531
  .option("--profile <dir>", `Persistent Chromium profile dir (default ${DEFAULT_PROFILE})`)
381
532
  .option(
@@ -856,7 +1007,8 @@ async function runBrowse(
856
1007
  ? null
857
1008
  : new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "harn-browse" });
858
1009
  if (jar) applyExtraCookies(url, jar, context?.extraCookies);
859
- const headed = opts.login || opts.headed;
1010
+ const loginMode = resolveLoginMode(opts);
1011
+ const headed = loginMode === "attached" || opts.headed;
860
1012
  const viewport = parseViewport(opts.viewport ?? "desktop");
861
1013
  const deviceScaleFactor = parseDeviceScaleFactor(opts.deviceScaleFactor);
862
1014
  if (opts.reviewPackAllocation && !opts.reviewPack) {
@@ -888,12 +1040,6 @@ async function runBrowse(
888
1040
  }
889
1041
  const proxy = opts.proxyFromEnv ? browserProxyFromEnv() : undefined;
890
1042
  const proxyGate = opts.proxyFromEnv ? browserProxyGateFromEnv() : null;
891
- if (opts.loginCloseFile && !opts.login) {
892
- throw new Error("--login-close-file requires --login.");
893
- }
894
- if (opts.controlFile && !opts.login) {
895
- throw new Error("--control-file requires --login.");
896
- }
897
1043
  const loginCloseFile = opts.loginCloseFile ? resolve(opts.loginCloseFile) : null;
898
1044
  if (loginCloseFile && existsSync(loginCloseFile)) {
899
1045
  throw new Error(
@@ -901,20 +1047,58 @@ async function runBrowse(
901
1047
  );
902
1048
  }
903
1049
 
1050
+ const collectPlan = opts.collect ? buildCollectPlan(opts) : null;
1051
+
1052
+ const pace = commandPaceGate(opts.pace !== false, (message) => emit.log(message, "info"));
1053
+ if (loginMode === "plain") {
1054
+ const plainExecutable = installedChromePath();
1055
+ const plainUa = resolveUserAgent({
1056
+ requested: opts.userAgent,
1057
+ env: process.env.HARNERY_BROWSER_UA,
1058
+ chromeMajor: plainExecutable ? chromeMajorFromExecutable(plainExecutable) : undefined,
1059
+ });
1060
+ await pace?.before(url);
1061
+ if (useWindowsPlainLogin()) {
1062
+ await runWindowsPlainLogin(url, opts.store ?? DEFAULT_STORE, loginCloseFile, emit);
1063
+ return;
1064
+ }
1065
+ await runPlainLogin(url, opts.profile ?? DEFAULT_PROFILE, loginCloseFile, plainUa, emit);
1066
+ return;
1067
+ }
1068
+
1069
+ const channel = resolveBrowserChannel(opts, Boolean(headed));
1070
+ const userAgent = resolveUserAgent({
1071
+ requested: opts.userAgent,
1072
+ env: process.env.HARNERY_BROWSER_UA,
1073
+ chromeMajor: launchedChromeMajor(channel),
1074
+ });
904
1075
  const browser = new Browser({
905
1076
  profileDir: opts.profile ?? DEFAULT_PROFILE,
906
1077
  headed,
907
1078
  jar,
908
- viewport,
1079
+ viewport: headed && !opts.viewport ? null : viewport,
909
1080
  ...(deviceScaleFactor !== undefined ? { deviceScaleFactor } : {}),
910
1081
  ...(colorScheme ? { colorScheme } : {}),
911
1082
  navigationTimeout: Number.parseInt(opts.timeout, 10),
912
1083
  waitUntil: opts.waitUntil as BrowseOpts["waitUntil"] as never,
913
1084
  recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
914
1085
  extraHeaders: context?.extraHeaders,
1086
+ pace,
915
1087
  launchArgs: resolveLaunchArgs(opts, Boolean(headed), Boolean(proxy)),
1088
+ ...(channel ? { channel } : {}),
1089
+ hideAutomation: Boolean(headed),
1090
+ ...(userAgent ? { userAgent } : {}),
916
1091
  proxy,
917
1092
  });
1093
+ if (userAgent) emit.log(`Presenting as: ${userAgent}`, "debug");
1094
+ if (headed) {
1095
+ emit.log(
1096
+ channel && channel !== "chromium"
1097
+ ? `Headed session uses the installed ${channel} channel with automation signals hidden.`
1098
+ : "Headed session uses Playwright's bundled Chromium (no Google Chrome found); automation signals hidden.",
1099
+ "info",
1100
+ );
1101
+ }
918
1102
 
919
1103
  // Print mode: --snapshot / --html / --json all suppress file writes.
920
1104
  const printMode = opts.snapshot || opts.html || opts.json;
@@ -955,6 +1139,11 @@ async function runBrowse(
955
1139
  batchResult = await runBatch(browser, opts.batch, Number.parseInt(opts.timeout, 10));
956
1140
  }
957
1141
 
1142
+ if (collectPlan) {
1143
+ await runCollectMode(browser, collectPlan);
1144
+ return;
1145
+ }
1146
+
958
1147
  let evalResult: unknown;
959
1148
  if (opts.evaluate) {
960
1149
  evalResult = await browser.evaluate<unknown>(opts.evaluate);
@@ -1409,13 +1598,13 @@ async function runBrowse(
1409
1598
  const closeWaits: Promise<void>[] = [];
1410
1599
  if (loginCloseFile) {
1411
1600
  emit.log(
1412
- `[--login] Headed Chromium is open. Drive the visible window now. Create ${loginCloseFile} to close cleanly.`,
1601
+ `[--login --attached] Automation-controlled Chrome is open. Drive the visible window now. Create ${loginCloseFile} to close cleanly.`,
1413
1602
  "info",
1414
1603
  );
1415
1604
  closeWaits.push(waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal));
1416
1605
  } else {
1417
1606
  emit.log(
1418
- "[--login] Headed Chromium is open. Walk through your auth flow now. Press Enter here to close + persist cookies into the profile.",
1607
+ "[--login --attached] Automation-controlled Chrome is open. Drive the visible window now. Press Enter here to close + persist cookies into the profile.",
1419
1608
  "info",
1420
1609
  );
1421
1610
  closeWaits.push(waitForTerminalEnter(lifecycleAbort.signal));
@@ -1612,9 +1801,161 @@ async function runBrowse(
1612
1801
  }
1613
1802
 
1614
1803
  // ---------------------------------------------------------------------------
1615
- // Batch mode parser. Each step is `<verb> <args>`; verbs are click, fill,
1616
- // press, wait, eval. Steps are separated by `;` (escape with `\;` if a value
1617
- // genuinely contains a semicolon, rare for the supported verbs).
1804
+ // Scroll-and-collect mode (--collect). Extends browse rather than adding a
1805
+ // sibling command so it reuses the exact session path: persistent profile,
1806
+ // cookie jar, browser channel, user agent, proxy, page-load pace gate,
1807
+ // --wait-for and --batch setup steps.
1808
+ // ---------------------------------------------------------------------------
1809
+
1810
+ interface CollectPlan {
1811
+ spec: ResolvedCollectSpec;
1812
+ limits: CollectLimits;
1813
+ pacing: ScrollPacing;
1814
+ seed: number;
1815
+ format: "json" | "jsonl";
1816
+ out: string | null;
1817
+ }
1818
+
1819
+ function parsePositiveInt(raw: string, flag: string): number {
1820
+ if (!/^\d+$/.test(raw.trim()) || Number.parseInt(raw, 10) < 1) {
1821
+ throw new Error(`${flag} must be a positive integer (got "${raw}").`);
1822
+ }
1823
+ return Number.parseInt(raw, 10);
1824
+ }
1825
+
1826
+ function buildCollectPlan(opts: BrowseOpts): CollectPlan {
1827
+ if (opts.login) throw new Error("--collect cannot run with --login.");
1828
+ if (opts.snapshot || opts.html || opts.json) {
1829
+ throw new Error("--collect prints its own JSON; drop --snapshot, --html and --json.");
1830
+ }
1831
+ const presetName = typeof opts.collect === "string" ? opts.collect : "generic";
1832
+ const spec = resolveCollectSpec(presetName, {
1833
+ item: opts.collectItem,
1834
+ key: opts.collectKey,
1835
+ keyPattern: opts.collectKeyPattern,
1836
+ fields: opts.collectField,
1837
+ expand: opts.collectExpand,
1838
+ });
1839
+ const limits: CollectLimits = {
1840
+ ...DEFAULT_COLLECT_LIMITS,
1841
+ maxItems:
1842
+ opts.collectMaxItems === undefined
1843
+ ? null
1844
+ : parsePositiveInt(opts.collectMaxItems, "--collect-max-items"),
1845
+ maxScrolls: parsePositiveInt(opts.collectMaxScrolls ?? "60", "--collect-max-scrolls"),
1846
+ maxMs: parsePositiveInt(opts.collectMaxSeconds ?? "300", "--collect-max-seconds") * 1_000,
1847
+ idleSteps: parsePositiveInt(opts.collectIdle ?? "5", "--collect-idle"),
1848
+ expandPerStep: spec.expand ? 3 : 0,
1849
+ };
1850
+ const pacing = scrollPacingFromEnv();
1851
+ const msBounds = { min: 0, max: PACING_LIMITS.maxPauseMs };
1852
+ if (opts.collectStep) {
1853
+ pacing.step = parseRange(opts.collectStep, "--collect-step", {
1854
+ min: 0.05,
1855
+ max: PACING_LIMITS.maxStepFraction,
1856
+ });
1857
+ }
1858
+ if (opts.collectPause)
1859
+ pacing.pauseMs = parseRange(opts.collectPause, "--collect-pause", msBounds);
1860
+ if (opts.collectReadPause) {
1861
+ pacing.readPauseMs = parseRange(opts.collectReadPause, "--collect-read-pause", msBounds);
1862
+ }
1863
+ if (opts.collectReadChance !== undefined) {
1864
+ pacing.readChance = parseProbability(opts.collectReadChance, "--collect-read-chance");
1865
+ }
1866
+ if (opts.collectBackChance !== undefined) {
1867
+ pacing.backChance = parseProbability(opts.collectBackChance, "--collect-back-chance");
1868
+ }
1869
+ let seed = randomSeed();
1870
+ if (opts.collectSeed !== undefined) {
1871
+ if (!/^\d+$/.test(opts.collectSeed.trim())) {
1872
+ throw new Error(`--collect-seed must be a non-negative integer (got "${opts.collectSeed}").`);
1873
+ }
1874
+ seed = Number(BigInt(opts.collectSeed.trim()) % 4294967296n);
1875
+ }
1876
+ const format = opts.collectFormat ?? "json";
1877
+ if (format !== "json" && format !== "jsonl") {
1878
+ throw new Error(`--collect-format must be json or jsonl (got "${format}").`);
1879
+ }
1880
+ return {
1881
+ spec,
1882
+ limits,
1883
+ pacing,
1884
+ seed,
1885
+ format,
1886
+ out: opts.collectOut ? resolve(opts.collectOut) : null,
1887
+ };
1888
+ }
1889
+
1890
+ async function runCollectMode(browser: Browser, plan: CollectPlan): Promise<void> {
1891
+ const page = browser.currentPage;
1892
+ const rng = createRng(plan.seed);
1893
+ let interrupted = false;
1894
+ const onSignal = () => {
1895
+ interrupted = true;
1896
+ };
1897
+ process.on("SIGINT", onSignal);
1898
+ process.on("SIGTERM", onSignal);
1899
+ let result: Awaited<ReturnType<typeof runCollect>>;
1900
+ try {
1901
+ emit.log(
1902
+ `collect: preset ${plan.spec.preset}, item ${plan.spec.item}, seed ${plan.seed}`,
1903
+ "info",
1904
+ );
1905
+ const driver = createPageDriver(page, plan.spec, {
1906
+ rng,
1907
+ log: (message) => emit.log(message, "warn"),
1908
+ });
1909
+ result = await runCollect(driver, {
1910
+ limits: plan.limits,
1911
+ pacing: plan.pacing,
1912
+ rng,
1913
+ shouldStop: () => interrupted,
1914
+ onStep: ({ step, items, added }) =>
1915
+ emit.log(`collect: step ${step}, ${items} items (+${added})`, "debug"),
1916
+ });
1917
+ } finally {
1918
+ process.removeListener("SIGINT", onSignal);
1919
+ process.removeListener("SIGTERM", onSignal);
1920
+ }
1921
+ const { items, stats } = result;
1922
+ emit.log(
1923
+ `collect: ${items.length} items in ${stats.steps} steps, stopped on ${stats.stopReason}` +
1924
+ (stats.stopDetail ? ` (${stats.stopDetail})` : ""),
1925
+ "info",
1926
+ );
1927
+ const envelope = {
1928
+ url: page.url(),
1929
+ preset: plan.spec.preset,
1930
+ itemSelector: plan.spec.item,
1931
+ seed: plan.seed,
1932
+ stopReason: stats.stopReason,
1933
+ stats,
1934
+ items,
1935
+ };
1936
+ if (plan.format === "jsonl") {
1937
+ const body = items.map((item) => JSON.stringify(item)).join("\n");
1938
+ if (plan.out) {
1939
+ mkdirSync(dirname(plan.out), { recursive: true });
1940
+ writeFileSync(plan.out, body ? `${body}\n` : "");
1941
+ emit.file(plan.out, { items: items.length, stopReason: stats.stopReason, stats });
1942
+ } else if (body) {
1943
+ process.stdout.write(`${body}\n`);
1944
+ }
1945
+ } else if (plan.out) {
1946
+ mkdirSync(dirname(plan.out), { recursive: true });
1947
+ writeFileSync(plan.out, `${JSON.stringify(envelope, null, 2)}\n`);
1948
+ emit.file(plan.out, { items: items.length, stopReason: stats.stopReason, stats });
1949
+ } else {
1950
+ emit.data(envelope);
1951
+ }
1952
+ if (interrupted) process.exitCode = 130;
1953
+ }
1954
+
1955
+ // ---------------------------------------------------------------------------
1956
+ // Batch mode runner. Each step is `<verb> <args>`; see splitBatchSteps for
1957
+ // the separator rule (eval steps keep semicolons inside JS quotes and
1958
+ // brackets; `\;` is always a literal semicolon).
1618
1959
  // ---------------------------------------------------------------------------
1619
1960
 
1620
1961
  interface BatchResult {
@@ -1694,27 +2035,6 @@ async function runBatch(
1694
2035
  return result;
1695
2036
  }
1696
2037
 
1697
- function splitBatchSteps(input: string): string[] {
1698
- const steps: string[] = [];
1699
- let buf = "";
1700
- for (let i = 0; i < input.length; i++) {
1701
- const ch = input[i];
1702
- if (ch === "\\" && input[i + 1] === ";") {
1703
- buf += ";";
1704
- i++;
1705
- continue;
1706
- }
1707
- if (ch === ";") {
1708
- steps.push(buf);
1709
- buf = "";
1710
- continue;
1711
- }
1712
- buf += ch;
1713
- }
1714
- if (buf.trim()) steps.push(buf);
1715
- return steps;
1716
- }
1717
-
1718
2038
  // ---------------------------------------------------------------------------
1719
2039
  // Print mode (--snapshot / --html / --json): stdout, no file writes
1720
2040
  // ---------------------------------------------------------------------------
@@ -2967,15 +3287,57 @@ function summarizeDiagnostics(diag: Diagnostics): Record<string, unknown> {
2967
3287
 
2968
3288
  /**
2969
3289
  * Merge Chromium launch flags from three sources, in order, de-duped:
2970
- * 1. WSLg headed default (`--disable-gpu`) — auto-applied for headed launches
2971
- * under WSL so the window actually paints. Opt out with
2972
- * HARNERY_BROWSER_NO_WSL_DEFAULTS=1.
3290
+ * 1. WSLg `--disable-gpu`, only when HARNERY_BROWSER_WSL_DISABLE_GPU=1. It
3291
+ * was the default for headed launches, but software WebGL (SwiftShader)
3292
+ * is a bot tell that sign-in flows screen for, and current WSLg exposes
3293
+ * the host GPU through D3D12, so the flag is now opt-in for machines
3294
+ * that still paint a blank headed window without it.
2973
3295
  * 2. HARNERY_BROWSER_ARGS env (whitespace-separated) — a machine-wide default.
2974
3296
  * 3. --browser-arg flags on this invocation (repeatable).
2975
3297
  */
3298
+ const BROWSER_CHANNELS = new Set(["chrome", "chrome-beta", "msedge", "chromium"]);
3299
+
3300
+ /**
3301
+ * Pick the browser channel: `--browser-channel`, then `HARNERY_BROWSER_CHANNEL`,
3302
+ * then (headed only) the installed Google Chrome when one exists. Headless runs
3303
+ * keep the bundled Chromium unless told otherwise, so QA renders stay stable.
3304
+ */
3305
+ function resolveBrowserChannel(
3306
+ opts: BrowseOpts,
3307
+ headed: boolean,
3308
+ ): "chrome" | "chrome-beta" | "msedge" | "chromium" | undefined {
3309
+ const requested = (opts.browserChannel ?? process.env.HARNERY_BROWSER_CHANNEL)?.trim();
3310
+ if (requested) {
3311
+ if (!BROWSER_CHANNELS.has(requested)) {
3312
+ throw new Error(
3313
+ `Unknown --browser-channel "${requested}". Use chrome, chrome-beta, msedge, or chromium.`,
3314
+ );
3315
+ }
3316
+ return requested as "chrome" | "chrome-beta" | "msedge" | "chromium";
3317
+ }
3318
+ return headed ? installedChromeChannel() : undefined;
3319
+ }
3320
+
3321
+ /**
3322
+ * Major version of the Chrome that will launch: the installed Chrome for the
3323
+ * `chrome` channel, else Playwright's bundled Chromium. Unknown when neither
3324
+ * answers `--version`; the user-agent store then keeps its last value.
3325
+ */
3326
+ function launchedChromeMajor(channel: string | undefined): number | undefined {
3327
+ if (channel === "chrome") {
3328
+ const exe = installedChromePath();
3329
+ return exe ? chromeMajorFromExecutable(exe) : undefined;
3330
+ }
3331
+ try {
3332
+ return chromeMajorFromExecutable(chromium.executablePath());
3333
+ } catch {
3334
+ return undefined;
3335
+ }
3336
+ }
3337
+
2976
3338
  function resolveLaunchArgs(opts: BrowseOpts, headed: boolean, proxyEnabled: boolean): string[] {
2977
3339
  const args: string[] = [];
2978
- if (headed && !process.env.HARNERY_BROWSER_NO_WSL_DEFAULTS) {
3340
+ if (headed && process.env.HARNERY_BROWSER_WSL_DISABLE_GPU === "1") {
2979
3341
  args.push(...wslHeadedLaunchArgs());
2980
3342
  }
2981
3343
  const envArgs = process.env.HARNERY_BROWSER_ARGS?.trim();
@@ -3005,6 +3367,166 @@ async function verifyBrowserProxyGate(
3005
3367
  emit.log(`browser proxy gate: ${observedIp} · expected IP confirmed`, "info");
3006
3368
  }
3007
3369
 
3370
+ /**
3371
+ * How a `browse` run handles sign-in. `--login` opens the installed Google
3372
+ * Chrome with nothing attached, because sites can detect an
3373
+ * automation-controlled browser and disable the account that signed in with
3374
+ * it. The attached window is reachable only through an explicit `--attached`.
3375
+ */
3376
+ export function resolveLoginMode(
3377
+ opts: Pick<BrowseOpts, "login" | "attached" | "controlFile" | "loginCloseFile">,
3378
+ ): "none" | "plain" | "attached" {
3379
+ if (opts.attached && !opts.login) throw new Error("--attached requires --login.");
3380
+ if (opts.loginCloseFile && !opts.login) throw new Error("--login-close-file requires --login.");
3381
+ if (opts.controlFile && !(opts.login && opts.attached)) {
3382
+ throw new Error(
3383
+ "--control-file requires --login --attached. A sign-in to a third-party account uses plain " +
3384
+ "--login, which nothing can drive.",
3385
+ );
3386
+ }
3387
+ if (!opts.login) return "none";
3388
+ return opts.attached ? "attached" : "plain";
3389
+ }
3390
+
3391
+ /**
3392
+ * `--login`: spawn the installed Google Chrome directly on the shared
3393
+ * persistent profile, with no DevTools protocol attached. Playwright enables
3394
+ * its runtime hooks in every frame it drives, and sign-in verification vendors
3395
+ * (X's is the reported case) detect that and fail the flow with no explanation.
3396
+ * The person signs in here; cookies land in the same profile that later
3397
+ * `browse` and `browse-session` runs open, so read-only captures inherit the
3398
+ * session without ever repeating the sign-in under automation.
3399
+ */
3400
+ async function runPlainLogin(
3401
+ url: string,
3402
+ profileDir: string,
3403
+ loginCloseFile: string | null,
3404
+ userAgent: string | undefined,
3405
+ emit: { log: (message: string, level: "info" | "warn" | "error" | "debug") => void },
3406
+ ): Promise<void> {
3407
+ const executable = installedChromePath();
3408
+ if (!executable) {
3409
+ throw new Error(
3410
+ "--login needs an installed Google Chrome at a standard path; none was found on this machine. " +
3411
+ "Install Google Chrome; do not fall back to --attached for a third-party sign-in.",
3412
+ );
3413
+ }
3414
+ mkdirSync(profileDir, { recursive: true });
3415
+ const child = spawn(
3416
+ executable,
3417
+ [
3418
+ `--user-data-dir=${profileDir}`,
3419
+ "--no-first-run",
3420
+ "--no-default-browser-check",
3421
+ ...(userAgent ? [`--user-agent=${userAgent}`] : []),
3422
+ url,
3423
+ ],
3424
+ { stdio: "ignore" },
3425
+ );
3426
+ const exited = new Promise<void>((resolveExit) => {
3427
+ child.once("exit", () => resolveExit());
3428
+ child.once("error", () => resolveExit());
3429
+ });
3430
+ const lifecycleAbort = new AbortController();
3431
+ const waits: Promise<void>[] = [exited];
3432
+ if (loginCloseFile) {
3433
+ emit.log(
3434
+ `[--login] Plain Google Chrome is open with nothing attached. Sign in, then close the window or create ${loginCloseFile}.`,
3435
+ "info",
3436
+ );
3437
+ waits.push(waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal));
3438
+ } else {
3439
+ emit.log(
3440
+ "[--login] Plain Google Chrome is open with nothing attached. Sign in, then close the window or press Enter here.",
3441
+ "info",
3442
+ );
3443
+ waits.push(waitForTerminalEnter(lifecycleAbort.signal));
3444
+ }
3445
+ await Promise.race(waits);
3446
+ lifecycleAbort.abort();
3447
+ if (child.exitCode === null && child.signalCode === null) {
3448
+ child.kill("SIGTERM");
3449
+ await Promise.race([exited, new Promise((r) => setTimeout(r, 5000))]);
3450
+ }
3451
+ emit.log(`[--login] Chrome closed; cookies persisted in ${profileDir}.`, "info");
3452
+ }
3453
+
3454
+ /**
3455
+ * Under WSL, `--login` opens the Windows host's Google Chrome when one
3456
+ * is installed. Some sign-in flows reject every browser running inside WSL,
3457
+ * even a plain Linux Chrome, while the Windows desktop Chrome signs in
3458
+ * normally. `HARNERY_BROWSER_WSL_PLAIN=linux` keeps the Linux Chrome.
3459
+ */
3460
+ function useWindowsPlainLogin(): boolean {
3461
+ if (!isWSL()) return false;
3462
+ if ((process.env.HARNERY_BROWSER_WSL_PLAIN ?? "").toLowerCase() === "linux") return false;
3463
+ return windowsChromePath() !== undefined;
3464
+ }
3465
+
3466
+ /**
3467
+ * The WSL form of `--login`: the person signs in through the Windows
3468
+ * Chrome on a dedicated Windows profile with nothing attached. When the window
3469
+ * closes, the profile's cookies for the sign-in site are read and merged into
3470
+ * the shared cookie store, which later `browse` runs attach.
3471
+ */
3472
+ async function runWindowsPlainLogin(
3473
+ url: string,
3474
+ storePath: string,
3475
+ loginCloseFile: string | null,
3476
+ emit: { log: (message: string, level: "info" | "warn" | "error" | "debug") => void },
3477
+ ): Promise<void> {
3478
+ const chromeLinux = windowsChromePath();
3479
+ const localAppData = windowsLocalAppData();
3480
+ if (!chromeLinux || !localAppData) {
3481
+ throw new Error(
3482
+ "--login under WSL needs the Windows Google Chrome and %LOCALAPPDATA%; neither was readable.",
3483
+ );
3484
+ }
3485
+ const chromeWindows = wslPathToWindows(chromeLinux);
3486
+ const profileWindows = windowsPlainProfile(localAppData);
3487
+ const child = spawn(
3488
+ chromeLinux,
3489
+ [`--user-data-dir=${profileWindows}`, "--no-first-run", "--no-default-browser-check", url],
3490
+ { stdio: "ignore" },
3491
+ );
3492
+ const exited = new Promise<void>((resolveExit) => {
3493
+ child.once("exit", () => resolveExit());
3494
+ child.once("error", () => resolveExit());
3495
+ });
3496
+ const lifecycleAbort = new AbortController();
3497
+ const waits: Promise<void>[] = [exited];
3498
+ const how = loginCloseFile ? `create ${loginCloseFile}` : "press Enter here";
3499
+ emit.log(
3500
+ `[--login] Windows Google Chrome is open with nothing attached (profile ${profileWindows}). Sign in, then close the window or ${how}.`,
3501
+ "info",
3502
+ );
3503
+ waits.push(
3504
+ loginCloseFile
3505
+ ? waitForLoginCloseFile(loginCloseFile, lifecycleAbort.signal)
3506
+ : waitForTerminalEnter(lifecycleAbort.signal),
3507
+ );
3508
+ await Promise.race(waits);
3509
+ lifecycleAbort.abort();
3510
+ stopWindowsChromeProfile(profileWindows);
3511
+ await Promise.race([exited, new Promise((r) => setTimeout(r, 5000))]);
3512
+ const domain = harvestDomain(url);
3513
+ const cookies = harvestWindowsChromeCookies({ chromeWindows, profileWindows, domain });
3514
+ if (cookies.length === 0) {
3515
+ emit.log(
3516
+ `[--login] No ${domain} cookies were found in the Windows profile; the sign-in may not have completed.`,
3517
+ "warn",
3518
+ );
3519
+ return;
3520
+ }
3521
+ const jar = new CookieJar({ path: storePath, source: "harn-browse" });
3522
+ jar.save(mergeCookies(jar.load(), cookies));
3523
+ const names = [...new Set(cookies.map((cookie) => cookie.name))].sort().join(", ");
3524
+ emit.log(
3525
+ `[--login] Copied ${cookies.length} ${domain} cookies into ${storePath}: ${names}.`,
3526
+ "info",
3527
+ );
3528
+ }
3529
+
3008
3530
  async function waitForLoginCloseFile(path: string, signal?: AbortSignal): Promise<void> {
3009
3531
  while (!existsSync(path) && !signal?.aborted) {
3010
3532
  await new Promise((resolve) => setTimeout(resolve, 250));