harnery 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +84 -2
- package/bin/agent-coord +42 -0
- package/bin/agent-hook +44 -0
- package/bin/harn +40 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/commander.d.ts +128 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +126 -0
- package/dist/commands/agents.d.ts +18 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +3946 -0
- package/dist/commands/backup.d.ts +22 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse-ai.d.ts +4 -0
- package/dist/commands/browse-ai.d.ts.map +1 -0
- package/dist/commands/browse-ai.js +156 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +590 -0
- package/dist/commands/callers.d.ts +4 -0
- package/dist/commands/callers.d.ts.map +1 -0
- package/dist/commands/callers.js +276 -0
- package/dist/commands/completion.d.ts +17 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +158 -0
- package/dist/commands/config-get.d.ts +4 -0
- package/dist/commands/config-get.d.ts.map +1 -0
- package/dist/commands/config-get.js +131 -0
- package/dist/commands/context.d.ts +11 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +185 -0
- package/dist/commands/cookies.d.ts +4 -0
- package/dist/commands/cookies.d.ts.map +1 -0
- package/dist/commands/cookies.js +140 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +137 -0
- package/dist/commands/doctor.d.ts +25 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/edit-batch.d.ts +18 -0
- package/dist/commands/edit-batch.d.ts.map +1 -0
- package/dist/commands/edit-batch.js +172 -0
- package/dist/commands/eml.d.ts +4 -0
- package/dist/commands/eml.d.ts.map +1 -0
- package/dist/commands/eml.js +428 -0
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +201 -0
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +99 -0
- package/dist/commands/file-history.d.ts +4 -0
- package/dist/commands/file-history.d.ts.map +1 -0
- package/dist/commands/file-history.js +152 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +317 -0
- package/dist/commands/init.d.ts +82 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +288 -0
- package/dist/commands/outline.d.ts +4 -0
- package/dist/commands/outline.d.ts.map +1 -0
- package/dist/commands/outline.js +494 -0
- package/dist/commands/presence.d.ts +12 -0
- package/dist/commands/presence.d.ts.map +1 -0
- package/dist/commands/presence.js +123 -0
- package/dist/commands/read.d.ts +7 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +46 -0
- package/dist/commands/scratch.d.ts +4 -0
- package/dist/commands/scratch.d.ts.map +1 -0
- package/dist/commands/scratch.js +426 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +162 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +275 -0
- package/dist/commands/toc.d.ts +5 -0
- package/dist/commands/toc.d.ts.map +1 -0
- package/dist/commands/toc.js +153 -0
- package/dist/commands/tokens.d.ts +4 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +48 -0
- package/dist/commands/tunnel.d.ts +4 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +513 -0
- package/dist/commands/uninstall.d.ts +22 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +165 -0
- package/dist/core/agents/canonical-emit.d.ts +27 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -0
- package/dist/core/agents/canonical-emit.js +72 -0
- package/dist/core/agents/cli-emit.d.ts +27 -0
- package/dist/core/agents/cli-emit.d.ts.map +1 -0
- package/dist/core/agents/cli-emit.js +57 -0
- package/dist/core/agents/cli.d.ts +10 -0
- package/dist/core/agents/cli.d.ts.map +1 -0
- package/dist/core/agents/cli.js +757 -0
- package/dist/core/agents/codex-replay.d.ts +29 -0
- package/dist/core/agents/codex-replay.d.ts.map +1 -0
- package/dist/core/agents/codex-replay.js +138 -0
- package/dist/core/agents/coord-client.d.ts +98 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -0
- package/dist/core/agents/coord-client.js +212 -0
- package/dist/core/agents/events/consume.d.ts +59 -0
- package/dist/core/agents/events/consume.d.ts.map +1 -0
- package/dist/core/agents/events/consume.js +147 -0
- package/dist/core/agents/events/emit.d.ts +42 -0
- package/dist/core/agents/events/emit.d.ts.map +1 -0
- package/dist/core/agents/events/emit.js +70 -0
- package/dist/core/agents/events/ulid.d.ts +11 -0
- package/dist/core/agents/events/ulid.d.ts.map +1 -0
- package/dist/core/agents/events/ulid.js +47 -0
- package/dist/core/agents/index.d.ts +14 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +13 -0
- package/dist/core/agents/paths.d.ts +6 -0
- package/dist/core/agents/paths.d.ts.map +1 -0
- package/dist/core/agents/paths.js +17 -0
- package/dist/core/agents/render/prompt-context.d.ts +43 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
- package/dist/core/agents/render/prompt-context.js +335 -0
- package/dist/core/agents/render/session-context.d.ts +39 -0
- package/dist/core/agents/render/session-context.d.ts.map +1 -0
- package/dist/core/agents/render/session-context.js +283 -0
- package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/claim-conflict.js +244 -0
- package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/commit-conflict.js +244 -0
- package/dist/core/agents/rules/stop-hook.d.ts +44 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
- package/dist/core/agents/rules/stop-hook.js +161 -0
- package/dist/core/agents/session-events.d.ts +41 -0
- package/dist/core/agents/session-events.d.ts.map +1 -0
- package/dist/core/agents/session-events.js +205 -0
- package/dist/core/agents/state/activity-log.d.ts +18 -0
- package/dist/core/agents/state/activity-log.d.ts.map +1 -0
- package/dist/core/agents/state/activity-log.js +34 -0
- package/dist/core/agents/state/council.d.ts +39 -0
- package/dist/core/agents/state/council.d.ts.map +1 -0
- package/dist/core/agents/state/council.js +216 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-projector.js +436 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-writer.js +271 -0
- package/dist/core/agents/state/names.d.ts +35 -0
- package/dist/core/agents/state/names.d.ts.map +1 -0
- package/dist/core/agents/state/names.js +376 -0
- package/dist/core/agents/state/pidmap.d.ts +11 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -0
- package/dist/core/agents/state/pidmap.js +32 -0
- package/dist/core/agents/state/scratch.d.ts +27 -0
- package/dist/core/agents/state/scratch.d.ts.map +1 -0
- package/dist/core/agents/state/scratch.js +90 -0
- package/dist/core/agents/state/shell-mutation.d.ts +17 -0
- package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
- package/dist/core/agents/state/shell-mutation.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts +16 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
- package/dist/core/agents/state/stale-sweep.js +166 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +108 -0
- package/dist/core/hooks/cli.d.ts +21 -0
- package/dist/core/hooks/cli.d.ts.map +1 -0
- package/dist/core/hooks/cli.js +1123 -0
- package/dist/core/hooks/effects/image-capture.d.ts +43 -0
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
- package/dist/core/hooks/effects/image-capture.js +288 -0
- package/dist/core/hooks/effects/index.d.ts +64 -0
- package/dist/core/hooks/effects/index.d.ts.map +1 -0
- package/dist/core/hooks/effects/index.js +197 -0
- package/dist/core/hooks/events/emit.d.ts +31 -0
- package/dist/core/hooks/events/emit.d.ts.map +1 -0
- package/dist/core/hooks/events/emit.js +89 -0
- package/dist/core/hooks/events/schema.d.ts +235 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -0
- package/dist/core/hooks/events/schema.js +12 -0
- package/dist/core/hooks/events/ulid.d.ts +10 -0
- package/dist/core/hooks/events/ulid.d.ts.map +1 -0
- package/dist/core/hooks/events/ulid.js +47 -0
- package/dist/core/hooks/harness/detect.d.ts +9 -0
- package/dist/core/hooks/harness/detect.d.ts.map +1 -0
- package/dist/core/hooks/harness/detect.js +29 -0
- package/dist/core/hooks/harness/events.d.ts +45 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -0
- package/dist/core/hooks/harness/events.js +71 -0
- package/dist/core/hooks/harness/output.d.ts +46 -0
- package/dist/core/hooks/harness/output.d.ts.map +1 -0
- package/dist/core/hooks/harness/output.js +87 -0
- package/dist/core/hooks/harness/parse.d.ts +67 -0
- package/dist/core/hooks/harness/parse.d.ts.map +1 -0
- package/dist/core/hooks/harness/parse.js +132 -0
- package/dist/core/hooks/index.d.ts +8 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +7 -0
- package/dist/core/hooks/resolve/anchor.d.ts +37 -0
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
- package/dist/core/hooks/resolve/anchor.js +48 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
- package/dist/core/hooks/resolve/coord-root.js +27 -0
- package/dist/core/hooks/resolve/intent.d.ts +33 -0
- package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
- package/dist/core/hooks/resolve/intent.js +79 -0
- package/dist/core/hooks/resolve/owner.d.ts +42 -0
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
- package/dist/core/hooks/resolve/owner.js +140 -0
- package/dist/core/hooks/resolve/transcript.d.ts +26 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
- package/dist/core/hooks/resolve/transcript.js +73 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/agent-browser/client.d.ts +99 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -0
- package/dist/lib/agent-browser/client.js +177 -0
- package/dist/lib/agent-browser/index.d.ts +2 -0
- package/dist/lib/agent-browser/index.d.ts.map +1 -0
- package/dist/lib/agent-browser/index.js +1 -0
- package/dist/lib/browser/client.d.ts +193 -0
- package/dist/lib/browser/client.d.ts.map +1 -0
- package/dist/lib/browser/client.js +325 -0
- package/dist/lib/browser/dev-overlay.d.ts +23 -0
- package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
- package/dist/lib/browser/dev-overlay.js +153 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.d.ts.map +1 -0
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/layout.d.ts +79 -0
- package/dist/lib/browser/layout.d.ts.map +1 -0
- package/dist/lib/browser/layout.js +220 -0
- package/dist/lib/browser/visibility.d.ts +86 -0
- package/dist/lib/browser/visibility.d.ts.map +1 -0
- package/dist/lib/browser/visibility.js +333 -0
- package/dist/lib/browser/visual-diff.d.ts +38 -0
- package/dist/lib/browser/visual-diff.d.ts.map +1 -0
- package/dist/lib/browser/visual-diff.js +107 -0
- package/dist/lib/completion/bash.d.ts +25 -0
- package/dist/lib/completion/bash.d.ts.map +1 -0
- package/dist/lib/completion/bash.js +284 -0
- package/dist/lib/completion/fish.d.ts +16 -0
- package/dist/lib/completion/fish.d.ts.map +1 -0
- package/dist/lib/completion/fish.js +118 -0
- package/dist/lib/completion/index.d.ts +5 -0
- package/dist/lib/completion/index.d.ts.map +1 -0
- package/dist/lib/completion/index.js +4 -0
- package/dist/lib/completion/walk.d.ts +68 -0
- package/dist/lib/completion/walk.d.ts.map +1 -0
- package/dist/lib/completion/walk.js +102 -0
- package/dist/lib/completion/zsh.d.ts +13 -0
- package/dist/lib/completion/zsh.d.ts.map +1 -0
- package/dist/lib/completion/zsh.js +249 -0
- package/dist/lib/context/index.d.ts +107 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +275 -0
- package/dist/lib/cookies/client.d.ts +131 -0
- package/dist/lib/cookies/client.d.ts.map +1 -0
- package/dist/lib/cookies/client.js +239 -0
- package/dist/lib/cookies/index.d.ts +2 -0
- package/dist/lib/cookies/index.d.ts.map +1 -0
- package/dist/lib/cookies/index.js +1 -0
- package/dist/lib/council/index.d.ts +266 -0
- package/dist/lib/council/index.d.ts.map +1 -0
- package/dist/lib/council/index.js +674 -0
- package/dist/lib/docs-index.d.ts +28 -0
- package/dist/lib/docs-index.d.ts.map +1 -0
- package/dist/lib/docs-index.js +169 -0
- package/dist/lib/docs-lint.d.ts +26 -0
- package/dist/lib/docs-lint.d.ts.map +1 -0
- package/dist/lib/docs-lint.js +378 -0
- package/dist/lib/docs-sweep.d.ts +34 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -0
- package/dist/lib/docs-sweep.js +304 -0
- package/dist/lib/docs.d.ts +27 -0
- package/dist/lib/docs.d.ts.map +1 -0
- package/dist/lib/docs.js +142 -0
- package/dist/lib/env.d.ts +11 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +12 -0
- package/dist/lib/exec.d.ts +32 -0
- package/dist/lib/exec.d.ts.map +1 -0
- package/dist/lib/exec.js +54 -0
- package/dist/lib/format.d.ts +29 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +139 -0
- package/dist/lib/http/client.d.ts +56 -0
- package/dist/lib/http/client.d.ts.map +1 -0
- package/dist/lib/http/client.js +160 -0
- package/dist/lib/http/index.d.ts +2 -0
- package/dist/lib/http/index.d.ts.map +1 -0
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/identities/index.d.ts +77 -0
- package/dist/lib/identities/index.d.ts.map +1 -0
- package/dist/lib/identities/index.js +190 -0
- package/dist/lib/machine.d.ts +19 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/machine.js +61 -0
- package/dist/lib/presence.d.ts +48 -0
- package/dist/lib/presence.d.ts.map +1 -0
- package/dist/lib/presence.js +123 -0
- package/dist/lib/readability/client.d.ts +32 -0
- package/dist/lib/readability/client.d.ts.map +1 -0
- package/dist/lib/readability/client.js +119 -0
- package/dist/lib/readability/index.d.ts +2 -0
- package/dist/lib/readability/index.d.ts.map +1 -0
- package/dist/lib/readability/index.js +1 -0
- package/dist/lib/scratch/index.d.ts +74 -0
- package/dist/lib/scratch/index.d.ts.map +1 -0
- package/dist/lib/scratch/index.js +393 -0
- package/dist/lib/tunnel/gate.d.ts +12 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -0
- package/dist/lib/tunnel/gate.js +101 -0
- package/dist/lib/tunnel/state.d.ts +34 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -0
- package/dist/lib/tunnel/state.js +132 -0
- package/package.json +160 -8
- package/schemas/.gitkeep +0 -0
- package/schemas/config.schema.json +109 -0
- package/src/cli.ts +22 -0
- package/src/commander.ts +242 -0
- package/src/commands/.gitkeep +0 -0
- package/src/commands/agents.ts +4567 -0
- package/src/commands/backup.ts +305 -0
- package/src/commands/browse-ai.ts +198 -0
- package/src/commands/browse.ts +849 -0
- package/src/commands/callers.ts +363 -0
- package/src/commands/completion.ts +193 -0
- package/src/commands/config-get.ts +161 -0
- package/src/commands/context.ts +209 -0
- package/src/commands/cookies.ts +198 -0
- package/src/commands/docs.ts +174 -0
- package/src/commands/doctor.ts +231 -0
- package/src/commands/edit-batch.ts +233 -0
- package/src/commands/eml.ts +519 -0
- package/src/commands/env.ts +254 -0
- package/src/commands/fetch.ts +136 -0
- package/src/commands/file-history.ts +202 -0
- package/src/commands/grep.ts +371 -0
- package/src/commands/init.ts +335 -0
- package/src/commands/outline.ts +564 -0
- package/src/commands/presence.ts +152 -0
- package/src/commands/read.ts +64 -0
- package/src/commands/scratch.ts +445 -0
- package/src/commands/session.ts +187 -0
- package/src/commands/sync.ts +306 -0
- package/src/commands/toc.ts +218 -0
- package/src/commands/tokens.ts +79 -0
- package/src/commands/tunnel.ts +633 -0
- package/src/commands/uninstall.ts +144 -0
- package/src/commands/web.ts +193 -0
- package/src/core/agents/canonical-emit.ts +77 -0
- package/src/core/agents/cli-emit.ts +64 -0
- package/src/core/agents/cli.ts +838 -0
- package/src/core/agents/codex-replay.ts +163 -0
- package/src/core/agents/coord-client.ts +249 -0
- package/src/core/agents/events/consume.ts +196 -0
- package/src/core/agents/events/emit.ts +108 -0
- package/src/core/agents/events/ulid.ts +51 -0
- package/src/core/agents/index.ts +14 -0
- package/src/core/agents/paths.ts +16 -0
- package/src/core/agents/render/prompt-context.ts +401 -0
- package/src/core/agents/render/session-context.ts +341 -0
- package/src/core/agents/rules/claim-conflict.ts +282 -0
- package/src/core/agents/rules/commit-conflict.ts +303 -0
- package/src/core/agents/rules/stop-hook.ts +229 -0
- package/src/core/agents/session-events.ts +228 -0
- package/src/core/agents/state/activity-log.ts +33 -0
- package/src/core/agents/state/council.ts +265 -0
- package/src/core/agents/state/heartbeat-projector.ts +488 -0
- package/src/core/agents/state/heartbeat-writer.ts +333 -0
- package/src/core/agents/state/names.ts +399 -0
- package/src/core/agents/state/pidmap.ts +38 -0
- package/src/core/agents/state/scratch.ts +121 -0
- package/src/core/agents/state/shell-mutation.ts +44 -0
- package/src/core/agents/state/stale-sweep.ts +190 -0
- package/src/core/config.ts +111 -0
- package/src/core/hooks/cli.ts +1247 -0
- package/src/core/hooks/effects/image-capture.ts +330 -0
- package/src/core/hooks/effects/index.ts +210 -0
- package/src/core/hooks/events/emit.ts +120 -0
- package/src/core/hooks/events/schema.ts +430 -0
- package/src/core/hooks/events/ulid.ts +51 -0
- package/src/core/hooks/harness/detect.ts +30 -0
- package/src/core/hooks/harness/events.ts +102 -0
- package/src/core/hooks/harness/output.ts +100 -0
- package/src/core/hooks/harness/parse.ts +180 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/resolve/anchor.ts +51 -0
- package/src/core/hooks/resolve/coord-root.ts +25 -0
- package/src/core/hooks/resolve/intent.ts +89 -0
- package/src/core/hooks/resolve/owner.ts +140 -0
- package/src/core/hooks/resolve/transcript.ts +72 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/index.ts +15 -0
- package/src/lib/agent-browser/client.ts +239 -0
- package/src/lib/agent-browser/index.ts +1 -0
- package/src/lib/browser/client.ts +449 -0
- package/src/lib/browser/dev-overlay.ts +207 -0
- package/src/lib/browser/index.ts +24 -0
- package/src/lib/browser/layout.ts +288 -0
- package/src/lib/browser/visibility.ts +419 -0
- package/src/lib/browser/visual-diff.ts +150 -0
- package/src/lib/completion/bash.ts +291 -0
- package/src/lib/completion/fish.ts +134 -0
- package/src/lib/completion/index.ts +10 -0
- package/src/lib/completion/walk.ts +184 -0
- package/src/lib/completion/zsh.ts +262 -0
- package/src/lib/context/index.ts +386 -0
- package/src/lib/cookies/client.ts +301 -0
- package/src/lib/cookies/index.ts +13 -0
- package/src/lib/council/index.ts +803 -0
- package/src/lib/docs-index.ts +216 -0
- package/src/lib/docs-lint.ts +413 -0
- package/src/lib/docs-sweep.ts +348 -0
- package/src/lib/docs.ts +199 -0
- package/src/lib/env.ts +12 -0
- package/src/lib/exec.ts +74 -0
- package/src/lib/format.ts +147 -0
- package/src/lib/http/client.ts +211 -0
- package/src/lib/http/index.ts +1 -0
- package/src/lib/identities/index.ts +210 -0
- package/src/lib/machine.ts +61 -0
- package/src/lib/presence.ts +154 -0
- package/src/lib/readability/client.ts +156 -0
- package/src/lib/readability/index.ts +5 -0
- package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
- package/src/lib/scratch/index.ts +470 -0
- package/src/lib/tunnel/gate.ts +113 -0
- package/src/lib/tunnel/state.ts +167 -0
- package/src/web/.gitkeep +0 -0
- package/index.js +0 -1
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import { mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { resolveBinName } from "../core/config.js";
|
|
5
|
+
import { Browser, captureDevOverlay, } from "../lib/browser/index.js";
|
|
6
|
+
import { diffAgainstBaseline, saveBaseline, } from "../lib/browser/visual-diff.js";
|
|
7
|
+
import { CookieJar } from "../lib/cookies/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* `harn browse <url>`: Playwright-backed page navigation with shared
|
|
10
|
+
* cookie jar + persistent profile + diagnostics capture for LLM
|
|
11
|
+
* iteration loops.
|
|
12
|
+
*
|
|
13
|
+
* **Default behavior is "trio of files":** running `harn browse <url>` writes:
|
|
14
|
+
*
|
|
15
|
+
* <prefix>.png Full-page screenshot (omit with --no-screenshot)
|
|
16
|
+
* <prefix>.html Post-JS-render serialized DOM
|
|
17
|
+
* <prefix>.json Diagnostics: title, url, status, viewport, console
|
|
18
|
+
* events, console errors, page errors, failed requests
|
|
19
|
+
*
|
|
20
|
+
* `<prefix>` defaults to `~/.cache/harnery/browse/last`. Override with
|
|
21
|
+
* `--out <prefix>`.
|
|
22
|
+
*
|
|
23
|
+
* Print modes (`--snapshot`, `--html`, `--json`) skip file writes and
|
|
24
|
+
* print to stdout instead, handy for shell pipelines like
|
|
25
|
+
* `harn browse <url> --html | harn read -`.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_PROFILE = resolve(homedir(), ".cache", "harnery", "browser-profile");
|
|
28
|
+
const DEFAULT_STORE = resolve(homedir(), ".cache", "harnery", "cookies.json");
|
|
29
|
+
const FALLBACK_OUT_PREFIX = resolve(homedir(), ".cache", "harnery", "browse", "last");
|
|
30
|
+
const VIEWPORT_PRESETS = {
|
|
31
|
+
mobile: { width: 390, height: 844 },
|
|
32
|
+
tablet: { width: 820, height: 1180 },
|
|
33
|
+
desktop: { width: 1280, height: 800 },
|
|
34
|
+
hd: { width: 1920, height: 1080 },
|
|
35
|
+
};
|
|
36
|
+
// Module-scoped emit assigned by registerBrowseCommand. Same pattern as
|
|
37
|
+
// cookies/read: the many helper functions in this large file close over
|
|
38
|
+
// `emit` so action callbacks stay concise.
|
|
39
|
+
let emit;
|
|
40
|
+
export function registerBrowseCommand(program, emitParam, context) {
|
|
41
|
+
emit = emitParam;
|
|
42
|
+
program
|
|
43
|
+
.command("browse <url>")
|
|
44
|
+
.description("Headless Chromium with persistent profile + cookie jar. Default writes a trio of files (last.png, last.html, last.json) for the LLM iteration loop; --snapshot/--html/--json switch to stdout-print mode.")
|
|
45
|
+
.option("--out <prefix>", "Output prefix for the trio (writes <prefix>.png, .html, .json). Defaults to ~/.cache/harnery/browse/last.")
|
|
46
|
+
.option("--no-screenshot", "Skip the .png in the trio (DOM + JSON only)")
|
|
47
|
+
.option("--dom-only", "Alias for --no-screenshot")
|
|
48
|
+
.option("--no-full-page", "Capture only the viewport, not the full scrollable page")
|
|
49
|
+
.option("--snapshot", "Print body innerText to stdout (skips file writes)")
|
|
50
|
+
.option("--html", `Print raw outer HTML to stdout (skips file writes; pair with \`${resolveBinName()} read -\`)`)
|
|
51
|
+
.option("--json", "Print full JSON envelope to stdout (skips file writes)")
|
|
52
|
+
.option("--selector <css>", "Scope --html / --snapshot to one element")
|
|
53
|
+
.option("--click <selector>", "Click this selector after navigation")
|
|
54
|
+
.option("--fill <selector=>value>", "Fill an input: 'input[name=q]=>hello' (separator is `=>`)")
|
|
55
|
+
.option("--press <key>", "Press a key after navigation/fill (e.g., Enter)")
|
|
56
|
+
.option("--wait-for <selector>", "Wait for this selector before capturing output")
|
|
57
|
+
.option("--evaluate <js>", "Run JS in the page context after navigation; result printed to stdout")
|
|
58
|
+
.option("--batch <steps>", "Run multiple steps in one session, semicolon-separated. Each step is one of: " +
|
|
59
|
+
"`click <selector>`, `fill <selector=>value>`, `press <key>`, `wait <selector|ms>`, `eval <js>`, `reload`. " +
|
|
60
|
+
'Example: `--batch "click button; wait 1500; reload; wait 3000"`. `reload` preserves sessionStorage + cookies, which is how to repro sessionStorage-restored UI state.')
|
|
61
|
+
.option("--network-har <path>", "Record network traffic to a HAR file (finalized on close)")
|
|
62
|
+
.option("--viewport <preset|WxH>", "Viewport: mobile (390x844), tablet (820x1180), desktop (1280x800), hd (1920x1080), or explicit '1920x1080'", "desktop")
|
|
63
|
+
.option("--login", "Headed mode for one-time auth flow (cookies persist in profile)")
|
|
64
|
+
.option("--headed", "Headed mode for one-off (no auth-flow framing)")
|
|
65
|
+
.option("--no-cookies", "Skip cookie-jar attach and persist")
|
|
66
|
+
.option("--store <path>", `Cookie store path (default ${DEFAULT_STORE})`)
|
|
67
|
+
.option("--profile <dir>", `Persistent Chromium profile dir (default ${DEFAULT_PROFILE})`)
|
|
68
|
+
.option("--wait-until <strategy>", "Navigation wait strategy: load | domcontentloaded | networkidle | commit", "load")
|
|
69
|
+
.option("--timeout <ms>", "Navigation timeout in milliseconds", "30000")
|
|
70
|
+
.option("--check-visible <selector>", "Run an occlusion check on this selector after navigation + batch. " +
|
|
71
|
+
"Samples a 3×3 grid inside the element's bounding rect, reports " +
|
|
72
|
+
"`visibleRatio` + the dominant occluder in the JSON envelope, and " +
|
|
73
|
+
"(by default) overlays green/red/amber boxes on the screenshot. " +
|
|
74
|
+
"Repeat the flag for multiple targets.", (value, prev = []) => [...prev, value], [])
|
|
75
|
+
.option("--check-visible-threshold <num>", "visibleRatio threshold below which a target is considered occluded " +
|
|
76
|
+
"(0–1, default 0.9). Used by the screenshot annotation color + by " +
|
|
77
|
+
"--check-visible-fail.", "0.9")
|
|
78
|
+
.option("--check-visible-fail", "Exit non-zero if any --check-visible target falls below threshold. " +
|
|
79
|
+
"Use in deploy scripts to break the build on UI regressions.")
|
|
80
|
+
.option("--check-visible-sample-grid <n>", "Grid size for occlusion sampling (n×n points). Default 3 (9 samples).", "3")
|
|
81
|
+
.option("--no-check-visible-annotate", "Skip drawing target + occluder boxes on the screenshot (JSON still emitted).")
|
|
82
|
+
.option("--check-width <selector>", "Assert this selector's bounding rect width is at least --check-width-threshold " +
|
|
83
|
+
"of the viewport. Catches the class of mobile-layout bug where a table or card " +
|
|
84
|
+
"sits at e.g. 85% viewport fill because of stacked padding. Repeat the flag for " +
|
|
85
|
+
"multiple targets. Reports viewportFill + parentFill in the JSON envelope.", (value, prev = []) => [...prev, value], [])
|
|
86
|
+
.option("--check-width-threshold <ratio>", "viewportFill threshold below which a target is considered too-narrow (0–1, default 0.9). " +
|
|
87
|
+
"Used by the screenshot annotation color + by --check-width-fail.", "0.9")
|
|
88
|
+
.option("--check-width-fail", "Exit non-zero if any --check-width target falls below threshold.")
|
|
89
|
+
.option("--no-check-width-annotate", "Skip drawing width-check boxes on the screenshot (JSON still emitted).")
|
|
90
|
+
.option("--check-overflow", "Assert the document has no horizontal overflow (document.scrollWidth <= window.innerWidth). " +
|
|
91
|
+
"Surfaces protruding elements in the JSON envelope and annotates them on the screenshot. " +
|
|
92
|
+
"Catches the class of mobile-layout bug where a nav/table overflows the viewport edge.")
|
|
93
|
+
.option("--check-overflow-fail", "Exit non-zero if --check-overflow detects horizontal overflow.")
|
|
94
|
+
.option("--no-check-overflow-annotate", "Skip drawing overflow annotations on the screenshot (JSON still emitted).")
|
|
95
|
+
.option("--baseline <name>", "Save the captured screenshot as a named baseline at " +
|
|
96
|
+
"~/.cache/harnery/visual-baselines/<name>.png. Use --diff <name> later to " +
|
|
97
|
+
"compare future captures against it (visual-regression check).")
|
|
98
|
+
.option("--diff <name>", "Pixel-diff the captured screenshot against the named baseline. Writes " +
|
|
99
|
+
"the diff PNG next to the screenshot, reports mismatchedPixels + " +
|
|
100
|
+
"similarity in the JSON envelope.")
|
|
101
|
+
.option("--diff-threshold <ratio>", "mismatchRatio (mismatchedPixels / totalPixels) below which the diff is " +
|
|
102
|
+
"considered a match (0–1, default 0.01 = 1%).", "0.01")
|
|
103
|
+
.option("--diff-fail", "Exit non-zero if --diff mismatchRatio exceeds --diff-threshold.")
|
|
104
|
+
.option("--no-dev-overlay", "Skip auto-capture of Next.js dev-overlay issues. Default: capture every queued error (kind/code/message/stack) when a <nextjs-portal> shadow root is present. Necessary because Next.js 16 + React 19 route hydration errors + most React warnings through onCaughtError → next-devtools' errorQueue, NOT through console.error, so Playwright's standard listener doesn't see them. Surfaces them in the JSON envelope under `devOverlay`.")
|
|
105
|
+
.action(async (url, opts) => {
|
|
106
|
+
try {
|
|
107
|
+
await runBrowse(url, opts, context);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
111
|
+
emit.error({ code: "browse_error", message: msg });
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async function runBrowse(url, opts, context) {
|
|
117
|
+
// Commander: `--no-cookies` turns `opts.cookies` into `false`. Default is `true`.
|
|
118
|
+
const jar = opts.cookies === false
|
|
119
|
+
? null
|
|
120
|
+
: new CookieJar({ path: opts.store ?? DEFAULT_STORE, source: "bp-browse" });
|
|
121
|
+
const headed = opts.login || opts.headed;
|
|
122
|
+
const viewport = parseViewport(opts.viewport ?? "desktop");
|
|
123
|
+
const browser = new Browser({
|
|
124
|
+
profileDir: opts.profile ?? DEFAULT_PROFILE,
|
|
125
|
+
headed,
|
|
126
|
+
jar,
|
|
127
|
+
viewport,
|
|
128
|
+
navigationTimeout: Number.parseInt(opts.timeout, 10),
|
|
129
|
+
waitUntil: opts.waitUntil,
|
|
130
|
+
recordHarPath: opts.networkHar ? resolve(opts.networkHar) : undefined,
|
|
131
|
+
extraHeaders: context?.extraHeaders,
|
|
132
|
+
});
|
|
133
|
+
// Print mode: --snapshot / --html / --json all suppress file writes.
|
|
134
|
+
const printMode = opts.snapshot || opts.html || opts.json;
|
|
135
|
+
try {
|
|
136
|
+
await browser.open();
|
|
137
|
+
const navResult = await browser.navigate(url);
|
|
138
|
+
if (opts.fill) {
|
|
139
|
+
const sep = opts.fill.indexOf("=>");
|
|
140
|
+
if (sep < 0) {
|
|
141
|
+
throw new Error(`--fill expects 'selector=>value' (got: ${opts.fill}). The separator is '=>' (not '='), so CSS attribute selectors like input[name=q] don't collide.`);
|
|
142
|
+
}
|
|
143
|
+
await browser.fill(opts.fill.slice(0, sep), opts.fill.slice(sep + 2));
|
|
144
|
+
}
|
|
145
|
+
if (opts.click)
|
|
146
|
+
await browser.click(opts.click);
|
|
147
|
+
if (opts.press)
|
|
148
|
+
await browser.press(opts.press);
|
|
149
|
+
if (opts.waitFor)
|
|
150
|
+
await browser.waitForSelector(opts.waitFor, Number.parseInt(opts.timeout, 10));
|
|
151
|
+
let batchResult;
|
|
152
|
+
if (opts.batch) {
|
|
153
|
+
batchResult = await runBatch(browser, opts.batch, Number.parseInt(opts.timeout, 10));
|
|
154
|
+
}
|
|
155
|
+
let evalResult;
|
|
156
|
+
if (opts.evaluate) {
|
|
157
|
+
evalResult = await browser.evaluate(opts.evaluate);
|
|
158
|
+
}
|
|
159
|
+
// Run visibility checks AFTER any --batch interactions but BEFORE the
|
|
160
|
+
// screenshot. Annotation injection happens between sampling and capture
|
|
161
|
+
// so the boxes show on the saved PNG; they're cleared post-screenshot
|
|
162
|
+
// so the live profile state isn't polluted.
|
|
163
|
+
let visibility;
|
|
164
|
+
if (opts.checkVisible && opts.checkVisible.length > 0) {
|
|
165
|
+
visibility = await browser.checkVisibility(opts.checkVisible, {
|
|
166
|
+
sampleGrid: Number.parseInt(opts.checkVisibleSampleGrid ?? "3", 10),
|
|
167
|
+
});
|
|
168
|
+
if (opts.checkVisibleNoAnnotate !== true) {
|
|
169
|
+
await browser.annotateVisibility(visibility);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
let widths;
|
|
173
|
+
if (opts.checkWidth && opts.checkWidth.length > 0) {
|
|
174
|
+
widths = await browser.checkWidth(opts.checkWidth);
|
|
175
|
+
}
|
|
176
|
+
let overflow;
|
|
177
|
+
if (opts.checkOverflow) {
|
|
178
|
+
overflow = await browser.checkOverflow();
|
|
179
|
+
}
|
|
180
|
+
const widthThreshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
|
|
181
|
+
const annotateWidth = widths && opts.checkWidthNoAnnotate !== true;
|
|
182
|
+
const annotateOverflow = overflow && opts.checkOverflowNoAnnotate !== true;
|
|
183
|
+
if (annotateWidth || annotateOverflow) {
|
|
184
|
+
await browser.annotateLayout({
|
|
185
|
+
widths: annotateWidth ? widths : [],
|
|
186
|
+
overflow: annotateOverflow ? overflow : null,
|
|
187
|
+
widthThreshold,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (opts.login) {
|
|
191
|
+
await new Promise((res) => {
|
|
192
|
+
emit.log("[--login] Headed Chromium is open. Walk through your auth flow now. Press Enter here to close + persist cookies into the profile.", "info");
|
|
193
|
+
process.stdin.once("data", () => res());
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Auto-capture Next.js dev-overlay issues unless --no-dev-overlay was passed.
|
|
197
|
+
// Cheap no-op when no <nextjs-portal> shadow host is present (non-Next.js page).
|
|
198
|
+
let devOverlay;
|
|
199
|
+
if (opts.devOverlay !== false) {
|
|
200
|
+
devOverlay = await captureDevOverlay(browser.currentPage);
|
|
201
|
+
}
|
|
202
|
+
if (printMode) {
|
|
203
|
+
if (opts.baseline || opts.diff) {
|
|
204
|
+
throw new Error("--baseline / --diff require trio mode (screenshot file). Remove --snapshot/--html/--json or capture the screenshot first.");
|
|
205
|
+
}
|
|
206
|
+
await runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
await runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult);
|
|
210
|
+
}
|
|
211
|
+
if (visibility && opts.checkVisibleNoAnnotate !== true) {
|
|
212
|
+
await browser.clearVisibilityAnnotations();
|
|
213
|
+
}
|
|
214
|
+
if (annotateWidth || annotateOverflow) {
|
|
215
|
+
await browser.clearLayoutAnnotations();
|
|
216
|
+
}
|
|
217
|
+
if (opts.checkVisibleFail && visibility) {
|
|
218
|
+
const threshold = Number.parseFloat(opts.checkVisibleThreshold ?? "0.9");
|
|
219
|
+
const failed = visibility.filter((r) => !r.found || !r.cssVisible || r.visibleRatio < threshold);
|
|
220
|
+
if (failed.length > 0) {
|
|
221
|
+
for (const f of failed) {
|
|
222
|
+
let reason;
|
|
223
|
+
if (!f.found) {
|
|
224
|
+
reason = "element not found";
|
|
225
|
+
}
|
|
226
|
+
else if (!f.cssVisible) {
|
|
227
|
+
const hb = f.hiddenBy;
|
|
228
|
+
reason = hb
|
|
229
|
+
? `CSS-hidden via ${hb.reason} on ${hb.ancestorTag}${hb.ancestorId ? `#${hb.ancestorId}` : hb.ancestorClass ? `.${hb.ancestorClass.split(" ").slice(0, 2).join(".")}` : ""} (${hb.propertyValue})`
|
|
230
|
+
: "CSS-hidden (display/visibility/opacity/content-visibility)";
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
reason = `visibleRatio ${(f.visibleRatio * 100).toFixed(0)}% < ${(threshold * 100).toFixed(0)}%${f.occludedBy ? ` (occluded by ${f.occludedBy.tagName}${f.occludedBy.id ? `#${f.occludedBy.id}` : f.occludedBy.className ? `.${f.occludedBy.className.split(" ").slice(0, 2).join(".")}` : ""})` : ""}`;
|
|
234
|
+
}
|
|
235
|
+
emit.log(`check-visible FAIL ${f.selector}: ${reason}`, "warn");
|
|
236
|
+
}
|
|
237
|
+
process.exitCode = 2;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (opts.checkWidthFail && widths) {
|
|
241
|
+
const failed = widths.filter((w) => !w.found || w.viewportFill < widthThreshold);
|
|
242
|
+
if (failed.length > 0) {
|
|
243
|
+
for (const f of failed) {
|
|
244
|
+
const reason = !f.found
|
|
245
|
+
? "element not found"
|
|
246
|
+
: `viewportFill ${(f.viewportFill * 100).toFixed(0)}% < ${(widthThreshold * 100).toFixed(0)}% (rect=${f.rect.width}px, viewport=${f.viewportWidth}px)`;
|
|
247
|
+
emit.log(`check-width FAIL ${f.selector}: ${reason}`, "warn");
|
|
248
|
+
}
|
|
249
|
+
process.exitCode = 2;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (opts.checkOverflowFail && overflow?.hasHorizontalOverflow) {
|
|
253
|
+
const culprit = overflow.widerThanViewport[0] ?? overflow.rightOverflow[0] ?? null;
|
|
254
|
+
const detail = culprit
|
|
255
|
+
? `, top culprit: ${culprit.tagName}${culprit.id ? `#${culprit.id}` : culprit.className ? `.${culprit.className.split(" ").slice(0, 2).join(".")}` : ""} (${culprit.widthOverflowPx > 0 ? `+${culprit.widthOverflowPx}px wider` : `+${culprit.rightOverflowPx}px past right`})`
|
|
256
|
+
: "";
|
|
257
|
+
emit.log(`check-overflow FAIL: documentScrollWidth ${overflow.documentScrollWidth}px > viewport ${overflow.viewport.width}px (+${overflow.overflowPx}px)${detail}`, "warn");
|
|
258
|
+
process.exitCode = 2;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await browser.close();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function runBatch(browser, batch, defaultTimeoutMs) {
|
|
266
|
+
const result = { clipboardReads: [] };
|
|
267
|
+
const steps = splitBatchSteps(batch);
|
|
268
|
+
for (const step of steps) {
|
|
269
|
+
const trimmed = step.trim();
|
|
270
|
+
if (!trimmed)
|
|
271
|
+
continue;
|
|
272
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
273
|
+
const verb = (spaceIdx < 0 ? trimmed : trimmed.slice(0, spaceIdx)).toLowerCase();
|
|
274
|
+
const rest = spaceIdx < 0 ? "" : trimmed.slice(spaceIdx + 1).trim();
|
|
275
|
+
switch (verb) {
|
|
276
|
+
case "click":
|
|
277
|
+
if (!rest)
|
|
278
|
+
throw new Error(`batch: 'click' needs a selector (got: '${trimmed}')`);
|
|
279
|
+
await browser.click(rest);
|
|
280
|
+
break;
|
|
281
|
+
case "fill": {
|
|
282
|
+
const sep = rest.indexOf("=>");
|
|
283
|
+
if (sep < 0) {
|
|
284
|
+
throw new Error(`batch: 'fill' expects '<selector>=><value>' (got: '${trimmed}')`);
|
|
285
|
+
}
|
|
286
|
+
await browser.fill(rest.slice(0, sep), rest.slice(sep + 2));
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case "press":
|
|
290
|
+
if (!rest)
|
|
291
|
+
throw new Error(`batch: 'press' needs a key (got: '${trimmed}')`);
|
|
292
|
+
await browser.press(rest);
|
|
293
|
+
break;
|
|
294
|
+
case "wait": {
|
|
295
|
+
if (!rest)
|
|
296
|
+
throw new Error(`batch: 'wait' needs a selector or ms (got: '${trimmed}')`);
|
|
297
|
+
const asNum = Number(rest);
|
|
298
|
+
if (Number.isFinite(asNum)) {
|
|
299
|
+
await new Promise((res) => setTimeout(res, asNum));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
await browser.waitForSelector(rest, defaultTimeoutMs);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case "eval":
|
|
307
|
+
if (!rest)
|
|
308
|
+
throw new Error(`batch: 'eval' needs a JS expression (got: '${trimmed}')`);
|
|
309
|
+
await browser.evaluate(rest);
|
|
310
|
+
break;
|
|
311
|
+
case "reload":
|
|
312
|
+
// Reload preserves cookies + sessionStorage on the same origin: the
|
|
313
|
+
// only programmatic path that reproduces "drawer auto-restored from
|
|
314
|
+
// sessionStorage" focus + tooltip behavior. Pair with a `wait` step
|
|
315
|
+
// after to let hydration + Dialog auto-focus settle.
|
|
316
|
+
await browser.reload();
|
|
317
|
+
break;
|
|
318
|
+
case "clipboard": {
|
|
319
|
+
// Read the system clipboard and record the result so regression
|
|
320
|
+
// tests can assert against it. `rest` is an optional label for the
|
|
321
|
+
// stored value; defaults to "clipboard" so a bare `clipboard` verb
|
|
322
|
+
// works in one-shot use. Reads land in the JSON envelope under
|
|
323
|
+
// `batchClipboardReads` (see runTrioMode + runPrintMode).
|
|
324
|
+
const label = rest || "clipboard";
|
|
325
|
+
const value = await browser.readClipboard();
|
|
326
|
+
result.clipboardReads.push({ label, value });
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
default:
|
|
330
|
+
throw new Error(`batch: unknown verb '${verb}'. Supported: click, fill, press, wait, eval, reload, clipboard.`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
function splitBatchSteps(input) {
|
|
336
|
+
const steps = [];
|
|
337
|
+
let buf = "";
|
|
338
|
+
for (let i = 0; i < input.length; i++) {
|
|
339
|
+
const ch = input[i];
|
|
340
|
+
if (ch === "\\" && input[i + 1] === ";") {
|
|
341
|
+
buf += ";";
|
|
342
|
+
i++;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (ch === ";") {
|
|
346
|
+
steps.push(buf);
|
|
347
|
+
buf = "";
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
buf += ch;
|
|
351
|
+
}
|
|
352
|
+
if (buf.trim())
|
|
353
|
+
steps.push(buf);
|
|
354
|
+
return steps;
|
|
355
|
+
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// Print mode (--snapshot / --html / --json): stdout, no file writes
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
async function runPrintMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
|
|
360
|
+
let body = null;
|
|
361
|
+
if (opts.html) {
|
|
362
|
+
body = await browser.htmlContent(opts.selector);
|
|
363
|
+
}
|
|
364
|
+
else if (opts.snapshot) {
|
|
365
|
+
body = opts.selector
|
|
366
|
+
? await browser.evaluate(`(() => { const el = document.querySelector(${JSON.stringify(opts.selector)}); return el ? el.innerText : ''; })()`)
|
|
367
|
+
: await browser.textSnapshot();
|
|
368
|
+
}
|
|
369
|
+
if (opts.json) {
|
|
370
|
+
const result = {
|
|
371
|
+
url: navResult.url,
|
|
372
|
+
title: navResult.title,
|
|
373
|
+
status: navResult.status,
|
|
374
|
+
...summarizeDiagnostics(browser.diagnostics()),
|
|
375
|
+
};
|
|
376
|
+
if (body !== null)
|
|
377
|
+
result.body = body;
|
|
378
|
+
if (opts.evaluate)
|
|
379
|
+
result.eval = evalResult;
|
|
380
|
+
if (opts.networkHar)
|
|
381
|
+
result.har = resolve(opts.networkHar);
|
|
382
|
+
if (visibility)
|
|
383
|
+
result.visibility = visibility;
|
|
384
|
+
if (widths)
|
|
385
|
+
result.width = widths;
|
|
386
|
+
if (overflow)
|
|
387
|
+
result.overflow = overflow;
|
|
388
|
+
if (devOverlay)
|
|
389
|
+
result.devOverlay = devOverlay;
|
|
390
|
+
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
391
|
+
result.batchClipboardReads = batchResult.clipboardReads;
|
|
392
|
+
}
|
|
393
|
+
emit.data(result);
|
|
394
|
+
}
|
|
395
|
+
else if (body !== null) {
|
|
396
|
+
emit.text(body);
|
|
397
|
+
}
|
|
398
|
+
else if (opts.evaluate) {
|
|
399
|
+
// --evaluate alone (no other print flag): emit the result
|
|
400
|
+
if (typeof evalResult === "string") {
|
|
401
|
+
emit.text(evalResult);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
emit.data(evalResult);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// Trio mode (default): write <prefix>.{png,html,json}
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
async function runTrioMode(browser, navResult, opts, evalResult, visibility, widths, overflow, devOverlay, batchResult) {
|
|
412
|
+
const prefix = resolveOutPrefix(opts.out);
|
|
413
|
+
mkdirSync(dirname(prefix), { recursive: true });
|
|
414
|
+
const written = [];
|
|
415
|
+
const skipScreenshot = opts.screenshot === false || opts.domOnly === true;
|
|
416
|
+
let pngPath;
|
|
417
|
+
let pngBytes;
|
|
418
|
+
if (!skipScreenshot) {
|
|
419
|
+
pngPath = `${prefix}.png`;
|
|
420
|
+
pngBytes = await browser.screenshot(pngPath, { fullPage: opts.fullPage !== false });
|
|
421
|
+
written.push(pngPath);
|
|
422
|
+
}
|
|
423
|
+
const htmlPath = `${prefix}.html`;
|
|
424
|
+
const html = await browser.htmlContent(opts.selector);
|
|
425
|
+
writeFileSync(htmlPath, html);
|
|
426
|
+
written.push(htmlPath);
|
|
427
|
+
const diag = browser.diagnostics();
|
|
428
|
+
const jsonPath = `${prefix}.json`;
|
|
429
|
+
const envelope = {
|
|
430
|
+
url: navResult.url,
|
|
431
|
+
title: navResult.title,
|
|
432
|
+
status: navResult.status,
|
|
433
|
+
files: {
|
|
434
|
+
png: pngPath ?? null,
|
|
435
|
+
html: htmlPath,
|
|
436
|
+
json: jsonPath,
|
|
437
|
+
},
|
|
438
|
+
...summarizeDiagnostics(diag),
|
|
439
|
+
};
|
|
440
|
+
if (pngBytes !== undefined)
|
|
441
|
+
envelope.screenshotBytes = pngBytes;
|
|
442
|
+
if (opts.evaluate)
|
|
443
|
+
envelope.eval = evalResult;
|
|
444
|
+
if (opts.networkHar)
|
|
445
|
+
envelope.har = resolve(opts.networkHar);
|
|
446
|
+
if (visibility)
|
|
447
|
+
envelope.visibility = visibility;
|
|
448
|
+
if (widths)
|
|
449
|
+
envelope.width = widths;
|
|
450
|
+
if (overflow)
|
|
451
|
+
envelope.overflow = overflow;
|
|
452
|
+
if (devOverlay)
|
|
453
|
+
envelope.devOverlay = devOverlay;
|
|
454
|
+
if (batchResult && batchResult.clipboardReads.length > 0) {
|
|
455
|
+
envelope.batchClipboardReads = batchResult.clipboardReads;
|
|
456
|
+
}
|
|
457
|
+
// Visual-regression: save baseline and/or diff against an existing one.
|
|
458
|
+
// Both depend on the PNG being captured, so guard against --no-screenshot.
|
|
459
|
+
let savedBaseline;
|
|
460
|
+
let diff;
|
|
461
|
+
if ((opts.baseline || opts.diff) && !pngPath) {
|
|
462
|
+
throw new Error("--baseline / --diff require a screenshot; --no-screenshot / --dom-only disables it.");
|
|
463
|
+
}
|
|
464
|
+
if (opts.baseline && pngPath) {
|
|
465
|
+
savedBaseline = saveBaseline(pngPath, opts.baseline);
|
|
466
|
+
envelope.baselineSaved = savedBaseline;
|
|
467
|
+
written.push(savedBaseline.path);
|
|
468
|
+
}
|
|
469
|
+
if (opts.diff && pngPath) {
|
|
470
|
+
diff = diffAgainstBaseline(pngPath, opts.diff);
|
|
471
|
+
envelope.diff = diff;
|
|
472
|
+
written.push(diff.diffPath);
|
|
473
|
+
}
|
|
474
|
+
writeFileSync(jsonPath, `${JSON.stringify(envelope)}\n`);
|
|
475
|
+
written.push(jsonPath);
|
|
476
|
+
// Echo --evaluate result to stdout so 'harn browse <url> --evaluate ...' is
|
|
477
|
+
// shell-composable without forcing the user into --json print mode.
|
|
478
|
+
if (opts.evaluate) {
|
|
479
|
+
if (typeof evalResult === "string") {
|
|
480
|
+
emit.text(evalResult);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
emit.data(evalResult);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
emit.log(`${navResult.status ?? "?"} ${navResult.title || "(no title)"} ${navResult.url}; wrote ${written.length} file${written.length === 1 ? "" : "s"}: ${written.join(", ")}`, "info");
|
|
487
|
+
const errSummary = [
|
|
488
|
+
diag.consoleErrors.length ? `${diag.consoleErrors.length} console errors` : "",
|
|
489
|
+
diag.pageErrors.length ? `${diag.pageErrors.length} page errors` : "",
|
|
490
|
+
diag.failedRequests.length ? `${diag.failedRequests.length} failed requests` : "",
|
|
491
|
+
]
|
|
492
|
+
.filter(Boolean)
|
|
493
|
+
.join(", ");
|
|
494
|
+
if (errSummary) {
|
|
495
|
+
emit.log(errSummary, "warn");
|
|
496
|
+
}
|
|
497
|
+
if (visibility && visibility.length > 0) {
|
|
498
|
+
const threshold = Number.parseFloat(opts.checkVisibleThreshold ?? "0.9");
|
|
499
|
+
const lines = visibility.map((r) => {
|
|
500
|
+
if (!r.found)
|
|
501
|
+
return ` [FAIL] ${r.selector}: not found`;
|
|
502
|
+
if (!r.cssVisible) {
|
|
503
|
+
const hb = r.hiddenBy;
|
|
504
|
+
const detail = hb
|
|
505
|
+
? `CSS-hidden [${hb.reason}] via ${hb.ancestorTag}${hb.ancestorId ? `#${hb.ancestorId}` : hb.ancestorClass ? `.${hb.ancestorClass.split(" ").slice(0, 2).join(".")}` : ""}`
|
|
506
|
+
: "CSS-hidden";
|
|
507
|
+
return ` [HIDDEN] ${r.selector}: ${detail}`;
|
|
508
|
+
}
|
|
509
|
+
const pct = (r.visibleRatio * 100).toFixed(0);
|
|
510
|
+
const ok = r.visibleRatio >= threshold;
|
|
511
|
+
const why = !ok && r.occludedBy
|
|
512
|
+
? ` (occluded by ${r.occludedBy.tagName}${r.occludedBy.id ? `#${r.occludedBy.id}` : ""}${r.occludedBy.className ? `.${r.occludedBy.className.split(" ").slice(0, 2).join(".")}` : ""})`
|
|
513
|
+
: "";
|
|
514
|
+
return ` [${ok ? "OK" : "FAIL"}] ${r.selector}: ${pct}% visible${why}`;
|
|
515
|
+
});
|
|
516
|
+
emit.log(`check-visible (threshold ${(threshold * 100).toFixed(0)}%):\n${lines.join("\n")}`, "info");
|
|
517
|
+
}
|
|
518
|
+
if (widths && widths.length > 0) {
|
|
519
|
+
const threshold = Number.parseFloat(opts.checkWidthThreshold ?? "0.9");
|
|
520
|
+
const lines = widths.map((w) => {
|
|
521
|
+
if (!w.found)
|
|
522
|
+
return ` [FAIL] ${w.selector}: not found`;
|
|
523
|
+
const pct = (w.viewportFill * 100).toFixed(0);
|
|
524
|
+
const ok = w.viewportFill >= threshold;
|
|
525
|
+
return ` [${ok ? "OK" : "FAIL"}] ${w.selector}: ${pct}% viewport-fill (${w.rect.width}px of ${w.viewportWidth}px)`;
|
|
526
|
+
});
|
|
527
|
+
emit.log(`check-width (threshold ${(threshold * 100).toFixed(0)}%):\n${lines.join("\n")}`, "info");
|
|
528
|
+
}
|
|
529
|
+
if (overflow) {
|
|
530
|
+
const ok = !overflow.hasHorizontalOverflow;
|
|
531
|
+
const culprit = overflow.widerThanViewport[0] ?? overflow.rightOverflow[0] ?? null;
|
|
532
|
+
const detail = ok
|
|
533
|
+
? "no horizontal overflow"
|
|
534
|
+
: `documentScrollWidth ${overflow.documentScrollWidth}px > viewport ${overflow.viewport.width}px (+${overflow.overflowPx}px)${culprit
|
|
535
|
+
? `; top culprit: ${culprit.tagName}${culprit.id ? `#${culprit.id}` : culprit.className ? `.${culprit.className.split(" ").slice(0, 2).join(".")}` : ""}`
|
|
536
|
+
: ""}`;
|
|
537
|
+
emit.log(`check-overflow: [${ok ? "OK" : "FAIL"}] ${detail}`, ok ? "info" : "warn");
|
|
538
|
+
}
|
|
539
|
+
if (savedBaseline) {
|
|
540
|
+
emit.log(`baseline saved: ${savedBaseline.name} → ${savedBaseline.path} (${savedBaseline.bytes} bytes)`, "info");
|
|
541
|
+
}
|
|
542
|
+
if (diff) {
|
|
543
|
+
const pct = (diff.mismatchRatio * 100).toFixed(2);
|
|
544
|
+
const detail = diff.sizeMismatch
|
|
545
|
+
? `size mismatch (baseline ${diff.baselineDims.width}×${diff.baselineDims.height}, current ${diff.currentDims.width}×${diff.currentDims.height})`
|
|
546
|
+
: `${diff.mismatchedPixels.toLocaleString()} / ${diff.totalPixels.toLocaleString()} pixels differ (${pct}%)`;
|
|
547
|
+
emit.log(`diff vs '${diff.name}': ${detail} → ${diff.diffPath}`, diff.matched ? "info" : "warn");
|
|
548
|
+
if (opts.diffFail) {
|
|
549
|
+
const threshold = Number.parseFloat(opts.diffThreshold ?? "0.01");
|
|
550
|
+
if (diff.sizeMismatch || diff.mismatchRatio > threshold) {
|
|
551
|
+
emit.log(`diff FAIL: ${pct}% mismatch > ${(threshold * 100).toFixed(2)}% threshold`, "warn");
|
|
552
|
+
process.exitCode = 2;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
// Helpers
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
function summarizeDiagnostics(diag) {
|
|
561
|
+
return {
|
|
562
|
+
viewport: diag.viewport,
|
|
563
|
+
consoleErrors: diag.consoleErrors,
|
|
564
|
+
consoleEvents: diag.consoleEvents,
|
|
565
|
+
pageErrors: diag.pageErrors,
|
|
566
|
+
failedRequests: diag.failedRequests,
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function parseViewport(spec) {
|
|
570
|
+
const preset = VIEWPORT_PRESETS[spec.toLowerCase()];
|
|
571
|
+
if (preset)
|
|
572
|
+
return preset;
|
|
573
|
+
const match = /^(\d+)x(\d+)$/.exec(spec.trim());
|
|
574
|
+
if (!match) {
|
|
575
|
+
throw new Error(`--viewport must be a preset (mobile|tablet|desktop|hd) or 'WxH' (got: ${spec})`);
|
|
576
|
+
}
|
|
577
|
+
return { width: Number.parseInt(match[1], 10), height: Number.parseInt(match[2], 10) };
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Pick the trio prefix. Precedence:
|
|
581
|
+
* 1. Explicit --out <prefix>.
|
|
582
|
+
* 2. ~/.cache/harnery/browse/last fallback.
|
|
583
|
+
*/
|
|
584
|
+
function resolveOutPrefix(explicit) {
|
|
585
|
+
if (explicit)
|
|
586
|
+
return isAbsolute(explicit) ? explicit : resolve(explicit);
|
|
587
|
+
return FALLBACK_OUT_PREFIX;
|
|
588
|
+
}
|
|
589
|
+
// Suppress unused-import warning in build modes that strip dead imports.
|
|
590
|
+
void statSync;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext, HarneryProgramContext } from "../commander.js";
|
|
3
|
+
export declare function registerCallersCommand(program: Command, emit: EmitContext, context?: HarneryProgramContext): void;
|
|
4
|
+
//# sourceMappingURL=callers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callers.d.ts","sourceRoot":"","sources":["../../src/commands/callers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAuE1E,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CA6BN"}
|