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.
- package/README.md +1 -1
- package/dist/commander.js +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +34 -20
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +113 -12
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +4 -0
- package/dist/commands/browse.d.ts +164 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +388 -45
- package/dist/commands/checkpoint.js +8 -12
- package/dist/commands/claude-desktop.d.ts.map +1 -1
- package/dist/commands/claude-desktop.js +52 -0
- package/dist/commands/deinit.d.ts +1 -1
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +8 -4
- package/dist/commands/disk.d.ts +6 -0
- package/dist/commands/disk.d.ts.map +1 -0
- package/dist/commands/disk.js +80 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +2 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +58 -3
- package/dist/commands/fetch.d.ts.map +1 -1
- package/dist/commands/fetch.js +10 -0
- package/dist/commands/files.d.ts +16 -0
- package/dist/commands/files.d.ts.map +1 -1
- package/dist/commands/files.js +54 -2
- package/dist/commands/init.d.ts +21 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +271 -109
- package/dist/commands/instructions.js +5 -4
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +2 -1
- package/dist/commands/rm.d.ts +4 -0
- package/dist/commands/rm.d.ts.map +1 -0
- package/dist/commands/rm.js +87 -0
- package/dist/commands/servers.d.ts +7 -0
- package/dist/commands/servers.d.ts.map +1 -0
- package/dist/commands/servers.js +394 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +82 -2
- package/dist/commands/webview.d.ts +4 -0
- package/dist/commands/webview.d.ts.map +1 -0
- package/dist/commands/webview.js +94 -0
- package/dist/core/adapter.d.ts +50 -1
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +59 -1
- package/dist/core/adapters/attestation.d.ts.map +1 -1
- package/dist/core/adapters/attestation.js +3 -2
- package/dist/core/adapters/profiles.d.ts +29 -3
- package/dist/core/adapters/profiles.d.ts.map +1 -1
- package/dist/core/adapters/profiles.js +53 -1
- package/dist/core/adapters/registry.d.ts.map +1 -1
- package/dist/core/adapters/registry.js +33 -0
- package/dist/core/adapters/types.d.ts +9 -0
- package/dist/core/adapters/types.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.d.ts +1 -1
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +2 -7
- package/dist/core/agents/cli.js +25 -16
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +7 -2
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +2 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +3 -9
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +2 -8
- package/dist/core/agents/rules/stop-hook.d.ts +2 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +1 -1
- package/dist/core/agents/session-events.d.ts +6 -2
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +105 -26
- package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalization-state-v3.js +2 -1
- package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
- package/dist/core/agents/session-finalizer-v3.js +5 -4
- package/dist/core/agents/session-name-display.d.ts +28 -0
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +43 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +21 -2
- package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +61 -0
- package/dist/core/agents/state/live-coordination-writer.js +3 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +2 -10
- package/dist/core/agents/turn-changes.d.ts +17 -0
- package/dist/core/agents/turn-changes.d.ts.map +1 -0
- package/dist/core/agents/turn-changes.js +76 -0
- package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
- package/dist/core/artifacts/delivery-card.js +11 -3
- package/dist/core/artifacts/index.d.ts +140 -4
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +526 -52
- package/dist/core/config.d.ts +73 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +142 -3
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +2 -1
- package/dist/core/conversations/archive.d.ts.map +1 -1
- package/dist/core/conversations/archive.js +4 -3
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +5 -4
- package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/canary.js +5 -4
- package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
- package/dist/core/events/legacy-storage/compression.js +2 -1
- package/dist/core/events/v3/adapter-id.d.ts +10 -2
- package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
- package/dist/core/events/v3/adapter-id.js +16 -0
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +2 -1
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +5 -4
- package/dist/core/events/v3/capabilities.d.ts.map +1 -1
- package/dist/core/events/v3/capabilities.js +17 -0
- package/dist/core/events/v3/contract.d.ts +5 -0
- package/dist/core/events/v3/contract.d.ts.map +1 -1
- package/dist/core/events/v3/contract.js +7 -1
- package/dist/core/events/v3/control-witness.d.ts.map +1 -1
- package/dist/core/events/v3/control-witness.js +3 -2
- package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
- package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
- package/dist/core/events/v3/fingerprint-keys.js +7 -6
- package/dist/core/events/v3/generated.d.ts +1 -1
- package/dist/core/events/v3/generated.js +1 -1
- package/dist/core/events/v3/live-feed.d.ts.map +1 -1
- package/dist/core/events/v3/live-feed.js +5 -4
- package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/command-recorder.js +8 -7
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
- package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
- package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
- package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
- package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook-base.js +10 -4
- package/dist/core/events/v3/producers/hook.d.ts +2 -0
- package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
- package/dist/core/events/v3/producers/hook.js +16 -1
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +16 -6
- package/dist/core/events/v3/producers/recorder.d.ts +2 -4
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +125 -101
- package/dist/core/events/v3/projection.js +9 -1
- package/dist/core/events/v3/recovery.d.ts.map +1 -1
- package/dist/core/events/v3/recovery.js +4 -3
- package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/inventory.js +2 -1
- package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/maintenance.js +4 -3
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
- package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
- package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
- package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
- package/dist/core/events/v3/tool-line-changes.js +135 -0
- package/dist/core/events/v3/writer.d.ts.map +1 -1
- package/dist/core/events/v3/writer.js +8 -7
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +6 -5
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +12 -11
- package/dist/core/governor/state.d.ts.map +1 -1
- package/dist/core/governor/state.js +7 -6
- package/dist/core/guard/evaluator.js +1 -0
- package/dist/core/guard/storage.d.ts.map +1 -1
- package/dist/core/guard/storage.js +3 -2
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
- package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
- package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/index.js +20 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
- package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
- package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
- package/dist/core/hooks/adapter/behaviors/types.js +1 -0
- package/dist/core/hooks/adapter/detect.d.ts +1 -1
- package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
- package/dist/core/hooks/adapter/detect.js +2 -3
- package/dist/core/hooks/adapter/events.d.ts +46 -1
- package/dist/core/hooks/adapter/events.d.ts.map +1 -1
- package/dist/core/hooks/adapter/events.js +36 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
- package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
- package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
- package/dist/core/hooks/adapter/output.d.ts +7 -0
- package/dist/core/hooks/adapter/output.d.ts.map +1 -1
- package/dist/core/hooks/adapter/output.js +8 -1
- package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
- package/dist/core/hooks/adapter/wiring.d.ts +52 -8
- package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
- package/dist/core/hooks/adapter/wiring.js +133 -12
- package/dist/core/hooks/cli.js +676 -531
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
- package/dist/core/hooks/effects/image-capture.js +2 -1
- package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
- package/dist/core/hooks/prompt-context/contract.js +1 -0
- package/dist/core/hooks/resolve/transcript.d.ts +1 -1
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +35 -4
- package/dist/core/hooks/run-markers.d.ts +37 -0
- package/dist/core/hooks/run-markers.d.ts.map +1 -0
- package/dist/core/hooks/run-markers.js +109 -0
- package/dist/core/hooks/session-name-presence.d.ts +3 -1
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +7 -2
- package/dist/core/hooks/shell-waiter.d.ts +2 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
- package/dist/core/hooks/shell-waiter.js +173 -0
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +5 -4
- package/dist/core/semantic/review.d.ts.map +1 -1
- package/dist/core/semantic/review.js +5 -4
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +15 -14
- package/dist/core/semantic/storage.d.ts.map +1 -1
- package/dist/core/semantic/storage.js +5 -4
- package/dist/core/servers/index.d.ts +211 -0
- package/dist/core/servers/index.d.ts.map +1 -0
- package/dist/core/servers/index.js +560 -0
- package/dist/core/servers/net.d.ts +58 -0
- package/dist/core/servers/net.d.ts.map +1 -0
- package/dist/core/servers/net.js +270 -0
- package/dist/core/servers/tunnels.d.ts +14 -0
- package/dist/core/servers/tunnels.d.ts.map +1 -0
- package/dist/core/servers/tunnels.js +33 -0
- package/dist/core/storage/atomic-json.d.ts.map +1 -1
- package/dist/core/storage/atomic-json.js +7 -3
- package/dist/core/storage/builtins.js +1 -0
- package/dist/core/storage/durable-history.d.ts.map +1 -1
- package/dist/core/storage/durable-history.js +6 -5
- package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
- package/dist/core/storage/maintenance-providers.js +2 -1
- package/dist/core/storage/maintenance.d.ts.map +1 -1
- package/dist/core/storage/maintenance.js +7 -6
- package/dist/core/storage/metrics.d.ts.map +1 -1
- package/dist/core/storage/metrics.js +5 -1
- package/dist/core/storage/modes.d.ts +17 -0
- package/dist/core/storage/modes.d.ts.map +1 -0
- package/dist/core/storage/modes.js +47 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +3 -2
- package/dist/core/storage/receipts.d.ts.map +1 -1
- package/dist/core/storage/receipts.js +4 -3
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +7 -6
- package/dist/core/supervisor/hook-health.d.ts +2 -1
- package/dist/core/supervisor/hook-health.d.ts.map +1 -1
- package/dist/core/supervisor/hook-health.js +4 -3
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +7 -6
- package/dist/core/work/state.d.ts.map +1 -1
- package/dist/core/work/state.js +8 -7
- package/dist/core/workflow/adapters.d.ts +7 -0
- package/dist/core/workflow/adapters.d.ts.map +1 -1
- package/dist/core/workflow/adapters.js +9 -0
- package/dist/core/workflow/approvals.d.ts.map +1 -1
- package/dist/core/workflow/approvals.js +6 -5
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +33 -4
- package/dist/core/workflow/durable-record.d.ts.map +1 -1
- package/dist/core/workflow/durable-record.js +4 -3
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +30 -1
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +2 -3
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -2
- package/dist/core/workflow/sandbox-projection.d.ts +13 -2
- package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
- package/dist/core/workflow/sandbox-projection.js +23 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +6 -1
- package/dist/core/workflow/spawn-codex.d.ts +2 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +20 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +6 -1
- package/dist/core/workflow/spawn-opencode.d.ts +36 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
- package/dist/core/workflow/spawn-opencode.js +135 -0
- package/dist/core/workflow/transcript.d.ts.map +1 -1
- package/dist/core/workflow/transcript.js +3 -2
- package/dist/core/workflow/types.d.ts +47 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/worker-access.d.ts +92 -0
- package/dist/core/workflow/worker-access.d.ts.map +1 -0
- package/dist/core/workflow/worker-access.js +450 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +5 -4
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +3 -2
- package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/paths.js +2 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +10 -9
- package/dist/lazy-commands.js +8 -1
- package/dist/lib/agent-browser/client.d.ts +9 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +13 -0
- package/dist/lib/browser/batch-steps.d.ts +18 -0
- package/dist/lib/browser/batch-steps.d.ts.map +1 -0
- package/dist/lib/browser/batch-steps.js +82 -0
- package/dist/lib/browser/client.d.ts +35 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +117 -8
- package/dist/lib/browser/collect/collector.d.ts +99 -0
- package/dist/lib/browser/collect/collector.d.ts.map +1 -0
- package/dist/lib/browser/collect/collector.js +202 -0
- package/dist/lib/browser/collect/fields.d.ts +31 -0
- package/dist/lib/browser/collect/fields.d.ts.map +1 -0
- package/dist/lib/browser/collect/fields.js +75 -0
- package/dist/lib/browser/collect/index.d.ts +6 -0
- package/dist/lib/browser/collect/index.d.ts.map +1 -0
- package/dist/lib/browser/collect/index.js +5 -0
- package/dist/lib/browser/collect/page-driver.d.ts +14 -0
- package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
- package/dist/lib/browser/collect/page-driver.js +306 -0
- package/dist/lib/browser/collect/presets.d.ts +52 -0
- package/dist/lib/browser/collect/presets.d.ts.map +1 -0
- package/dist/lib/browser/collect/presets.js +176 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
- package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
- package/dist/lib/browser/collect/scroll-plan.js +118 -0
- package/dist/lib/browser/identity.d.ts +25 -0
- package/dist/lib/browser/identity.d.ts.map +1 -0
- package/dist/lib/browser/identity.js +66 -0
- package/dist/lib/browser/index.d.ts +3 -2
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -1
- package/dist/lib/browser/launch-args.d.ts +26 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -1
- package/dist/lib/browser/launch-args.js +41 -1
- package/dist/lib/browser/page-review-judge.d.ts +2 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +13 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +1 -0
- package/dist/lib/browser/user-agent.d.ts +53 -0
- package/dist/lib/browser/user-agent.d.ts.map +1 -0
- package/dist/lib/browser/user-agent.js +79 -0
- package/dist/lib/browser/windows-chrome.d.ts +59 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
- package/dist/lib/browser/windows-chrome.js +179 -0
- package/dist/lib/claude-desktop-share.d.ts +136 -0
- package/dist/lib/claude-desktop-share.d.ts.map +1 -0
- package/dist/lib/claude-desktop-share.js +394 -0
- package/dist/lib/disk-usage.d.ts +50 -0
- package/dist/lib/disk-usage.d.ts.map +1 -0
- package/dist/lib/disk-usage.js +303 -0
- package/dist/lib/docs-lint.d.ts +6 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +8 -3
- package/dist/lib/docs-metadata-managed.d.ts +1 -0
- package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
- package/dist/lib/docs-metadata-managed.js +17 -1
- package/dist/lib/guarded-remove.d.ts +34 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -0
- package/dist/lib/guarded-remove.js +310 -0
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -9
- package/dist/lib/instructions/apply.d.ts.map +1 -1
- package/dist/lib/instructions/apply.js +2 -0
- package/dist/lib/instructions/templates.js +8 -8
- package/dist/lib/pace/index.d.ts +2 -0
- package/dist/lib/pace/index.d.ts.map +1 -0
- package/dist/lib/pace/index.js +1 -0
- package/dist/lib/pace/pace.d.ts +95 -0
- package/dist/lib/pace/pace.d.ts.map +1 -0
- package/dist/lib/pace/pace.js +313 -0
- package/dist/lib/tunnel/error-page.d.ts +1 -1
- package/dist/lib/tunnel/error-page.d.ts.map +1 -1
- package/dist/lib/tunnel/error-page.js +19 -8
- package/dist/lib/tunnel/forward-headers.d.ts +11 -0
- package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
- package/dist/lib/tunnel/forward-headers.js +17 -0
- package/dist/lib/tunnel/gate.js +34 -10
- package/dist/lib/tunnel/path-scope.d.ts +22 -0
- package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
- package/dist/lib/tunnel/path-scope.js +62 -0
- package/dist/lib/tunnel/state.d.ts +5 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +17 -3
- package/dist/lib/webview/index.d.ts +2 -0
- package/dist/lib/webview/index.d.ts.map +1 -0
- package/dist/lib/webview/index.js +1 -0
- package/dist/lib/webview/webview.d.ts +116 -0
- package/dist/lib/webview/webview.d.ts.map +1 -0
- package/dist/lib/webview/webview.js +289 -0
- package/opencode-plugin/package.json +19 -0
- package/opencode-plugin/src/index.ts +791 -0
- package/package.json +16 -4
- package/schemas/config.schema.json +104 -0
- package/schemas/event-v3.schema.json +164 -0
- package/src/commander.ts +26 -0
- package/src/commands/agents.ts +47 -18
- package/src/commands/artifacts.ts +171 -19
- package/src/commands/browse-ai.ts +8 -0
- package/src/commands/browse.ts +570 -48
- package/src/commands/checkpoint.ts +7 -9
- package/src/commands/claude-desktop.ts +61 -0
- package/src/commands/deinit.ts +9 -4
- package/src/commands/disk.ts +114 -0
- package/src/commands/docs.ts +2 -0
- package/src/commands/doctor.ts +63 -3
- package/src/commands/fetch.ts +14 -0
- package/src/commands/files.ts +93 -2
- package/src/commands/init.ts +334 -122
- package/src/commands/instructions.ts +8 -4
- package/src/commands/ledger-v3.ts +2 -1
- package/src/commands/rm.ts +115 -0
- package/src/commands/servers.ts +506 -0
- package/src/commands/tunnel.ts +102 -2
- package/src/commands/webview.ts +159 -0
- package/src/core/adapter.ts +102 -1
- package/src/core/adapters/attestation.ts +3 -2
- package/src/core/adapters/profiles.ts +63 -1
- package/src/core/adapters/registry.ts +37 -0
- package/src/core/adapters/types.ts +10 -0
- package/src/core/agents/canonical-emit.ts +2 -5
- package/src/core/agents/cli.ts +26 -17
- package/src/core/agents/coord-client.ts +9 -3
- package/src/core/agents/render/session-context.ts +1 -0
- package/src/core/agents/rules/claim-conflict.ts +3 -8
- package/src/core/agents/rules/commit-conflict.ts +2 -7
- package/src/core/agents/rules/stop-hook.ts +3 -1
- package/src/core/agents/session-events.ts +120 -26
- package/src/core/agents/session-finalization-state-v3.ts +2 -1
- package/src/core/agents/session-finalizer-v3.ts +5 -4
- package/src/core/agents/session-name-display.ts +48 -0
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +24 -2
- package/src/core/agents/state/live-coordination-view.ts +74 -0
- package/src/core/agents/state/live-coordination-writer.ts +3 -0
- package/src/core/agents/state/stale-sweep.ts +2 -9
- package/src/core/agents/turn-changes.ts +97 -0
- package/src/core/artifacts/delivery-card.ts +12 -3
- package/src/core/artifacts/index.ts +701 -47
- package/src/core/config.ts +206 -4
- package/src/core/context/index.ts +2 -1
- package/src/core/conversations/archive.ts +4 -3
- package/src/core/diagnostics/bundle.ts +5 -3
- package/src/core/events/legacy-storage/canary.ts +5 -4
- package/src/core/events/legacy-storage/compression.ts +2 -1
- package/src/core/events/v3/adapter-id.ts +29 -1
- package/src/core/events/v3/authority-outbox.ts +2 -1
- package/src/core/events/v3/bootstrap.ts +5 -4
- package/src/core/events/v3/capabilities.ts +17 -0
- package/src/core/events/v3/contract.ts +9 -1
- package/src/core/events/v3/control-witness.ts +3 -2
- package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
- package/src/core/events/v3/fingerprint-keys.ts +7 -6
- package/src/core/events/v3/generated.ts +1 -1
- package/src/core/events/v3/live-feed.ts +5 -4
- package/src/core/events/v3/producers/command-recorder.ts +8 -6
- package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
- package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
- package/src/core/events/v3/producers/hook-base.ts +11 -4
- package/src/core/events/v3/producers/hook.ts +19 -1
- package/src/core/events/v3/producers/intake.ts +18 -6
- package/src/core/events/v3/producers/recorder.ts +139 -114
- package/src/core/events/v3/projection.ts +11 -1
- package/src/core/events/v3/recovery.ts +4 -3
- package/src/core/events/v3/support-storage/inventory.ts +2 -1
- package/src/core/events/v3/support-storage/maintenance.ts +8 -3
- package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
- package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
- package/src/core/events/v3/tool-line-changes.ts +139 -0
- package/src/core/events/v3/writer.ts +8 -7
- package/src/core/governor/planning.ts +6 -5
- package/src/core/governor/service.ts +12 -11
- package/src/core/governor/state.ts +7 -6
- package/src/core/guard/evaluator.ts +1 -0
- package/src/core/guard/storage.ts +3 -2
- package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
- package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
- package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
- package/src/core/hooks/adapter/behaviors/index.ts +31 -0
- package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
- package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
- package/src/core/hooks/adapter/behaviors/types.ts +110 -0
- package/src/core/hooks/adapter/detect.ts +2 -3
- package/src/core/hooks/adapter/events.ts +72 -1
- package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
- package/src/core/hooks/adapter/output.ts +8 -1
- package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
- package/src/core/hooks/adapter/wiring.ts +198 -16
- package/src/core/hooks/cli.ts +776 -539
- package/src/core/hooks/effects/image-capture.ts +2 -1
- package/src/core/hooks/prompt-context/contract.ts +1 -0
- package/src/core/hooks/resolve/transcript.ts +42 -5
- package/src/core/hooks/run-markers.ts +132 -0
- package/src/core/hooks/session-name-presence.ts +10 -3
- package/src/core/hooks/shell-waiter.ts +166 -0
- package/src/core/resources/service.ts +5 -4
- package/src/core/semantic/review.ts +5 -4
- package/src/core/semantic/service.ts +15 -14
- package/src/core/semantic/storage.ts +5 -4
- package/src/core/servers/index.ts +712 -0
- package/src/core/servers/net.ts +283 -0
- package/src/core/servers/tunnels.ts +39 -0
- package/src/core/storage/atomic-json.ts +7 -3
- package/src/core/storage/builtins.ts +1 -0
- package/src/core/storage/durable-history.ts +6 -5
- package/src/core/storage/maintenance-providers.ts +2 -1
- package/src/core/storage/maintenance.ts +7 -6
- package/src/core/storage/metrics.ts +5 -1
- package/src/core/storage/modes.ts +53 -0
- package/src/core/storage/process-log.ts +3 -2
- package/src/core/storage/receipts.ts +4 -3
- package/src/core/storage/segments.ts +7 -6
- package/src/core/supervisor/hook-health.ts +4 -4
- package/src/core/supervisor/service.ts +7 -6
- package/src/core/work/state.ts +8 -7
- package/src/core/workflow/adapters.ts +15 -0
- package/src/core/workflow/approvals.ts +6 -5
- package/src/core/workflow/billing.ts +34 -4
- package/src/core/workflow/durable-record.ts +4 -3
- package/src/core/workflow/engine.ts +38 -1
- package/src/core/workflow/live-session-v3.ts +2 -3
- package/src/core/workflow/run-state.ts +3 -2
- package/src/core/workflow/sandbox-projection.ts +38 -2
- package/src/core/workflow/spawn-claude.ts +5 -1
- package/src/core/workflow/spawn-codex.ts +18 -1
- package/src/core/workflow/spawn-cursor.ts +5 -1
- package/src/core/workflow/spawn-opencode.ts +153 -0
- package/src/core/workflow/transcript.ts +3 -2
- package/src/core/workflow/types.ts +62 -2
- package/src/core/workflow/worker-access.ts +525 -0
- package/src/core/workflow/workspaces/leases.ts +5 -4
- package/src/core/workflow/workspaces/local-git.ts +3 -2
- package/src/core/workflow/workspaces/paths.ts +2 -1
- package/src/core/workflow/workspaces/state.ts +10 -9
- package/src/lazy-commands.ts +11 -1
- package/src/lib/agent-browser/client.ts +18 -0
- package/src/lib/browser/batch-steps.ts +79 -0
- package/src/lib/browser/client.ts +147 -9
- package/src/lib/browser/collect/collector.ts +306 -0
- package/src/lib/browser/collect/fields.ts +87 -0
- package/src/lib/browser/collect/index.ts +47 -0
- package/src/lib/browser/collect/page-driver.ts +327 -0
- package/src/lib/browser/collect/presets.ts +237 -0
- package/src/lib/browser/collect/scroll-plan.ts +179 -0
- package/src/lib/browser/identity.ts +64 -0
- package/src/lib/browser/index.ts +16 -1
- package/src/lib/browser/launch-args.ts +53 -1
- package/src/lib/browser/page-review-judge.ts +16 -0
- package/src/lib/browser/qa-run-contracts.ts +11 -0
- package/src/lib/browser/qa-run.ts +1 -0
- package/src/lib/browser/user-agent.ts +130 -0
- package/src/lib/browser/windows-chrome.ts +227 -0
- package/src/lib/claude-desktop-share.ts +511 -0
- package/src/lib/disk-usage.ts +347 -0
- package/src/lib/docs-lint.ts +8 -3
- package/src/lib/docs-metadata-managed.ts +21 -0
- package/src/lib/guarded-remove.ts +376 -0
- package/src/lib/identities/assume.ts +7 -7
- package/src/lib/instructions/apply.ts +2 -0
- package/src/lib/instructions/templates.ts +8 -8
- package/src/lib/pace/index.ts +14 -0
- package/src/lib/pace/pace.ts +370 -0
- package/src/lib/tunnel/error-page.ts +25 -9
- package/src/lib/tunnel/forward-headers.ts +16 -0
- package/src/lib/tunnel/gate.ts +39 -10
- package/src/lib/tunnel/path-scope.ts +73 -0
- package/src/lib/tunnel/state.ts +20 -3
- package/src/lib/webview/index.ts +13 -0
- package/src/lib/webview/webview.ts +457 -0
package/src/commands/browse.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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,
|
|
366
|
-
"
|
|
367
|
-
"
|
|
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
|
|
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]
|
|
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]
|
|
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
|
-
//
|
|
1616
|
-
//
|
|
1617
|
-
//
|
|
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
|
|
2971
|
-
*
|
|
2972
|
-
*
|
|
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 &&
|
|
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));
|