# OpenDevBrowser CLI

Command-line interface for installing and managing the OpenDevBrowser plugin, plus automation commands for agents.
Status: active  
Last updated: 2026-06-30

OpenDevBrowser exposes 70 `opendevbrowser_*` tools; see `README.md` and `docs/SURFACE_REFERENCE.md` for the full inventories.
Generated help is the primary first-contact inventory and onboarding surface. Agent runs should start with `opendevbrowser_prompting_guide` or `opendevbrowser_skill_load opendevbrowser-best-practices "quick start"` before low-level browser commands, then load `opendevbrowser_skill_load opendevbrowser-best-practices "validated capability lanes"` when they need the current transcript, evidence-gated research, shopping, browser replay, desktop observation, and browser-scoped computer-use workflows named in `src/cli/onboarding-metadata.json`. Load `opendevbrowser_skill_load opendevbrowser-design-agent "canvas-contract"` immediately after that baseline for frontend, screenshot-to-code, or `/canvas` design work. Load `opendevbrowser_skill_load opendevbrowser-motion-design "quick start"` after design-agent for motion-heavy UI work, animation systems, scroll motion, gesture motion, reduced-motion audits, or motion release evidence. Use continuity guidance only for long-running handoff or compaction.
That generated help surface now leads with a `Find It Fast` block that uses the exact lookup terms `screencast / browser replay`, `desktop observation`, and `computer use / browser-scoped computer use`. It maps replay to `screencast-start` / `screencast-stop`, desktop observation to the public read-only `desktop-*` family, and browser-scoped computer use to `--challenge-automation-mode` on `research run`, `shopping run`, `product-video run`, `inspiredesign run`, and `macro-resolve --execute`, with `research run --topic ... --challenge-automation-mode browser` as the first entry command.
Tool-only commands `opendevbrowser_prompting_guide`, `opendevbrowser_skill_list`, and `opendevbrowser_skill_load` run locally via the skill loader. They are onboarding helpers, not browser-runtime commands, and they do not require relay or daemon bootstrap.
CLI-only power command `rpc` intentionally has no tool equivalent; it is an internal daemon escape hatch behind an explicit safety flag and should be used with extreme caution.
Public-surface metadata now flows from `src/public-surface/source.ts` through `scripts/generate-public-surface-manifest.mjs` into `src/public-surface/generated-manifest.ts` and `src/public-surface/generated-manifest.json`, which are consumed by `src/cli/help.ts`, `src/cli/args.ts`, inventory scripts, mirrored website inputs, and re-export paths in `src/tools/index.ts`. Onboarding literals still live in `src/cli/onboarding-metadata.json`, and runtime execution authority remains `src/cli/args.ts` plus `src/tools/index.ts`.
The generated first-contact inventory mirrored into downstream website and release flows is tracked in `docs/ASSET_INVENTORY.md`.

Dependency inventory: `docs/DEPENDENCIES.md`
First-run local-artifact onboarding: `docs/FIRST_RUN_ONBOARDING.md`

Parity and skill-pack gates:

```bash
npm run test -- tests/cli-help-parity.test.ts
npm run test -- tests/parity-matrix.test.ts
./skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh
./skills/opendevbrowser-design-agent/scripts/validate-skill-assets.sh
./skills/opendevbrowser-motion-design/scripts/validate-skill-assets.sh
./skills/opendevbrowser-design-agent/scripts/design-workflow.sh research-harvest
./skills/opendevbrowser-design-agent/scripts/design-workflow.sh release-gate
```

Treat those checks as contract and documentation guards. Live release proof comes from the direct-run harnesses documented later in this guide and in `docs/RELEASE_RUNBOOK.md`.

## Installation

Requires Node.js `>=18`.

```bash
# Interactive installation (prompts for location)
npx opendevbrowser

# Non-interactive with flags
npx opendevbrowser --global   # Install to ~/.config/opencode/opencode.json
npx opendevbrowser --local    # Install to ./opencode.json

# Optional: persistent global CLI
npm install -g opendevbrowser
opendevbrowser --version
```

### Local package onboarding from a source tarball

```bash
cd <public-repo-root>
npm pack

WORKDIR=$(mktemp -d /tmp/opendevbrowser-first-run-XXXXXX)
ISOLATED_ROOT=$(mktemp -d /tmp/opendevbrowser-first-run-isolated-XXXXXX)
export HOME="$ISOLATED_ROOT/home"
export OPENCODE_CONFIG_DIR="$ISOLATED_ROOT/opencode-config"
export OPENCODE_CACHE_DIR="$ISOLATED_ROOT/opencode-cache"
export CODEX_HOME="$ISOLATED_ROOT/codex-home"
export CLAUDECODE_HOME="$ISOLATED_ROOT/claudecode-home"
export AMP_CLI_HOME="$ISOLATED_ROOT/ampcli-home"
cd "$WORKDIR"
npm init -y
npm install <public-repo-root>/opendevbrowser-0.0.40.tgz
npx --no-install opendevbrowser --help
npx --no-install opendevbrowser help
```

Load extension unpacked from:
- `$WORKDIR/node_modules/opendevbrowser/extension`

Set `OPDEVBROWSER_SKIP_POSTINSTALL_SKILL_SYNC=1` before `npm install` only if you need a packaging smoke test that exits the legacy package lifecycle shim before built postinstall code imports. Set `OPDEVBROWSER_SKIP_INSTALL_AUTOSTART_RECONCILIATION=1` when you only want to skip install-time daemon auto-start reconciliation.

By default (`--skills-global`), the CLI installs bundled skills to global OpenCode/Codex/ClaudeCode/AmpCLI/Agents locations. Codex-managed OpenDevBrowser packs use the shared Agents root so Codex does not show duplicate `opendevbrowser-*` entries from both `$CODEX_HOME/skills` and `~/.agents/skills`. Use `--skills-local` for project-local locations or `--no-skills` to skip CLI-managed skill installation. Package installation (`npm install -g`, local tarball install, or equivalent) also best-effort syncs the canonical bundled packs into the managed global skill targets during package `postinstall`. Raw npm global package postinstall best-effort reconciles daemon auto-start only when npm lifecycle context clearly indicates a global install; local, ambiguous, conflicting, or non-npm package-manager contexts skip auto-start without failing package installation. Use `--full` to always create `opendevbrowser.jsonc` and pre-extract extension assets.

Installer inventory:
- `--skills-global` and `--skills-local` sync the 10 canonical `opendevbrowser-*` packs under `skills/` into managed global or project-local agent directories.
- Codex managed installs write OpenDevBrowser packs through `~/.agents/skills` or `./.agents/skills`; managed legacy `.codex/skills` duplicates are removed during sync, while markerless user-owned `.codex` directories are preserved.
- Managed installs write a target-level ownership marker, so default updates and uninstall only act on CLI-managed targets or older config installs that already contain canonical packs.
- Reinstall and update refresh drifted managed copies, adopt matching markerless canonical copies when a managed OpenDevBrowser target is being repaired or promoted to all canonical packs, and preserve drifted markerless directories outside managed repair for user review.
- Uninstall removes marker- or sentinel-managed canonical packs and leaves unrelated directories untouched.

`OPENCODE_CONFIG_DIR` changes config lookup, but the extracted unpacked-extension copy created by `--full` still lives at `~/.config/opencode/opendevbrowser/extension`.

Published npm consumer proof is a separate release gate:

```bash
node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json
```

### Skill discovery order

The skill loader discovers skills in this order (first match wins):

1. Project-local: `./.opencode/skill`
2. Global: `~/.config/opencode/skill` (or `$OPENCODE_CONFIG_DIR/skill`)
3. Compatibility (project): `./.codex/skills`
4. Compatibility (global): `$CODEX_HOME/skills` (fallback `~/.codex/skills`)
5. Compatibility (project): `./.claude/skills`
6. Compatibility (global): `$CLAUDECODE_HOME/skills` (fallback `~/.claude/skills`)
7. Compatibility (project): `./.amp/skills`
8. Compatibility (global): `$AMP_CLI_HOME/skills` (fallback `~/.amp/skills`)
9. Compatibility (project): `./.agents/skills`
10. Compatibility (global): `~/.agents/skills`
11. Extra paths from `skillPaths` (advanced)
12. Bundled package fallback: packaged `skills/` directory after `skillPaths` when no installed copy matches

---

## Output formats

Use `--output-format` to switch output formats for automation.

- `text` (default): human-friendly output
- `json`: single JSON object
- `stream-json`: emit one JSON object per line for arrays; otherwise emits a single JSON object

Use `--quiet` to suppress output. Use `--no-interactive` (alias of `--no-prompt`) to disable prompts.

---

## Exit codes

- `0`: success
- `1`: usage error (invalid args, missing flags)
- `2`: execution error (runtime/daemon failures)
- `10`: disconnected (daemon not running or unreachable)

Fatal errors are emitted even when `--quiet` is set. For JSON output, fatal errors are emitted as:

```json
{ "success": false, "error": "message", "exitCode": 2 }
```

---

## Argument validation

The CLI validates common flags early and returns a usage error (`exitCode: 1`) when inputs are invalid.

- Conflicting flags are rejected (examples: `--global` + `--local`, `--skills-global` + `--skills-local`).
- Positive integer flags must use plain decimal digits:
  - `--port`, `--cdp-port`
  - `--wait-timeout-ms`, `--timeout-ms`, `--max`, `--max-chars`
- Non-negative cursor flags allow `0` and use plain decimal digits: `--since-seq`, `--since-console-seq`, `--since-network-seq`, `--since-exception-seq`.
- Signed movement flags use plain decimal integers and may be negative: `--dy`.

---

## Surface inventory (source-accurate)

Canonical inventory document: `docs/SURFACE_REFERENCE.md`.

### CLI command surface

- Total commands: `78`.
- Categories: install/runtime management, session/connection plus capability discovery, navigation plus desktop-assisted browser review, interaction plus low-level pointer control, targets/pages, DOM inspection, browser capture and replay, desktop observation, design canvas, export plus session-centric diagnostics and browser-scoped inspection, macro/annotation, and internal power (`rpc`).

### Tool surface

- Total tools: `70` (`opendevbrowser_*`).
- CLI-tool pairs: `67`.
- Tool-only surface (no CLI equivalent): `opendevbrowser_prompting_guide`, `opendevbrowser_skill_list`, `opendevbrowser_skill_load`.
- CLI-only surface (no tool equivalent): `install`, `update`, `uninstall`, `help`, `version`, `serve`, `daemon`, `native`, `artifacts`, `cdp-profile`, `rpc`.

### Relay channel surface

- `/ops` (default extension channel): high-level command protocol; see `docs/SURFACE_REFERENCE.md` for all `59` command names.
- `/canvas` (design-canvas channel): typed design-canvas protocol; see `docs/SURFACE_REFERENCE.md` for all `41` command names and envelope contracts.
- `/cdp` (legacy): low-level `forwardCDPCommand` relay path with explicit opt-in (`--extension-legacy`).

## Challenge orchestration contract

- Managed and `/ops`-backed manager responses preserve the shipped blocker fields `meta.blocker`, `meta.blockerState`, and `meta.blockerResolution`, and may append additive `meta.challenge` plus `meta.challengeOrchestration`.
- Browser-assisted provider fallback reports explicit transport `disposition`: `completed`, `challenge_preserved`, `deferred`, or `failed`. When bounded challenge orchestration runs during fallback, decision evidence is recorded in `details.challengeOrchestration`.
- Workflow and daemon callers can set `challengeAutomationMode` to `off`, `browser`, or `browser_with_helper`. Effective precedence is `run > session > config`.
- Shipped config defaults now resolve to `providers.challengeOrchestration.mode = browser_with_helper` and `providers.challengeOrchestration.optionalComputerUseBridge.enabled = true`.
- `meta.challengeOrchestration` and fallback `details.challengeOrchestration` can expose `mode`, `source`, `standDownReason`, and `helperEligibility` so stand-down decisions stay explicit.
- `ProviderRegistry` is the only durable anti-bot pressure authority used by policy, runtime routing, and workflow summaries. Provider modules only contribute extraction logic and optional `recoveryHints()`.
- Direct browser, `/ops`, and provider fallback flows share one bounded challenge plane. It can try auth navigation, legitimate session or cookie reuse, non-secret field fill, and bounded browser-native interaction experimentation before yielding.
- The optional helper bridge is browser-scoped, not a desktop agent. `browser` keeps it disabled and `browser_with_helper` only evaluates it after the existing hard gates pass.
- Separate `desktop.*` config controls the shipped public read-only desktop observation plane. It is enabled by default, is never enabled by `challengeAutomationMode`, and does not widen the browser challenge helper into a desktop agent or desktop `/ops` family.
- Provider and workflow auto-resume still happen only after manager-owned verification clears the blocker.
- In scope: preserved sessions, visual observation loops, low-level pointer controls, bounded interaction experimentation, reclaimable human yield packets, and owned-environment fixtures that use vendor test keys only.
- Out of scope: hidden bypass paths, CAPTCHA-solving services, challenge token harvesting, or autonomous unsandboxed solving of third-party anti-bot systems.

---

## Commands

### Install (default)

```bash
# Interactive - prompts for global or local
npx opendevbrowser

# Global installation (user-wide)
npx opendevbrowser --global
npx opendevbrowser -g

# Local installation (current project)
npx opendevbrowser --local
npx opendevbrowser -l

# Skip prompts, use default (global)
npx opendevbrowser --no-prompt
npx opendevbrowser --no-interactive

# Full install (config + extension assets)
npx opendevbrowser --full

# Also create opendevbrowser.jsonc config file
npx opendevbrowser --global --with-config

# Full install (plugin + config + bundled skills)
npx opendevbrowser --global --with-config --skills-global

# Install skills locally (project scope)
npx opendevbrowser --skills-local

# Skip installing skills
npx opendevbrowser --no-skills
```

On successful installs, the CLI/plugin installer reconciles daemon auto-start on supported platforms (macOS/Windows) so the relay is available on
login. Existing installs are rechecked and repaired when the per-user auto-start entry is missing or stale on macOS and Windows,
and when the macOS LaunchAgent is malformed, missing its stable non-root working directory, or missing required `EnvironmentVariables.PATH` entries for common binary directories. Raw npm global package postinstall also runs the same reconciliation best effort when npm lifecycle context clearly indicates a global install. Package postinstall targets `dist/cli/index.js`, not `scripts/postinstall-sync-skills.mjs`, and all package warnings are non-fatal.

If the current CLI entrypoint lives under a transient temp-root path (for example a first-run `/tmp` or `/private/tmp` `npx` workspace), OpenDevBrowser refuses to persist that path as auto-start. Plugin install still succeeds, and package install still exits successfully, but auto-start repair warns or skips. Rerun `opendevbrowser daemon install` from a stable install location, or `npx --no-install opendevbrowser daemon install` from a persistent local package install. You can inspect with `opendevbrowser daemon status` and remove auto-start later with `opendevbrowser daemon uninstall`.

### Update

Repair OpenCode package caches and stale plugin pins, then refresh managed skill packs unless `--no-skills` is used.

```bash
npx opendevbrowser --update
npx opendevbrowser -u
```

This removes cached files from `~/.cache/opencode/node_modules/opendevbrowser/` and
`~/.cache/opencode/packages/opendevbrowser@latest/`, removes stale `opendevbrowser`
dependency pins from `~/.cache/opencode/package.json`, and deletes `~/.cache/opencode/package-lock.json` when present. OpenCode
will download the latest version on next run.

If a global npm install or update appears stale, compare `command -v opendevbrowser`, `which -a opendevbrowser`, and `npm prefix -g`. The active command may live under a different prefix from npm's default global prefix; install with the prefix that owns the active binary, or run through `npx --yes opendevbrowser@latest` for a one-shot current package.

### Uninstall

Remove the plugin from configuration and clean managed skill packs for the selected install target.

```bash
# Remove from global config
npx opendevbrowser --uninstall --global

# Remove from local config
npx opendevbrowser --uninstall --local

# Interactive - prompts which to remove if both exist
npx opendevbrowser --uninstall
```

### Help / Version

```bash
npx opendevbrowser --help
npx opendevbrowser -h

npx opendevbrowser help

npx opendevbrowser --version
npx opendevbrowser -v
```

`--help` and `help` print the same generated first-contact inventory:
- A `Find It Fast` block that uses the exact lookup terms `screencast / browser replay`, `desktop observation`, and `computer use / browser-scoped computer use`.
- That block maps replay to `screencast-start` / `screencast-stop`, desktop observation to the public `desktop-*` family, and browser-scoped computer use to `--challenge-automation-mode` on `research run`, `shopping run`, `product-video run`, `inspiredesign run`, and `macro-resolve --execute`, with a concrete `research run --topic ... --challenge-automation-mode browser` entry command.
- An `Agent Quick Start` block that tells agents to start with `opendevbrowser_prompting_guide` or `opendevbrowser_skill_load opendevbrowser-best-practices "quick start"` before low-level browser commands.
- A follow-up `validated_lanes` entry that points agents to `opendevbrowser_skill_load opendevbrowser-best-practices "validated capability lanes"` for the current transcript, evidence-gated research, and shopping runbook.
- A direct pointer to `opendevbrowser_skill_list` when an agent needs a different local skill lane.
- A browser-scoped computer-use description that makes the optional helper boundary explicit and does not imply a desktop agent.
- The complete generated CLI command, flag, and `opendevbrowser_*` tool inventories.
- Canonical pointers to `docs/FIRST_RUN_ONBOARDING.md`, `skills/opendevbrowser-best-practices/SKILL.md`, and `docs/SURFACE_REFERENCE.md`.

Quick lookup terms from generated help:
- `screencast / browser replay`: `screencast-start`, `screencast-stop`
- `desktop observation`: `desktop-status`, `desktop-windows`, `desktop-active-window`, `desktop-capture-desktop`, `desktop-capture-window`, `desktop-accessibility-snapshot`
- `computer use / browser-scoped computer use`: `--challenge-automation-mode off|browser|browser_with_helper` on `research run`, `shopping run`, `product-video run`, `inspiredesign run`, and `macro-resolve --execute`; entry command `npx opendevbrowser research run --topic "account recovery flow" --sources web,community --challenge-automation-mode browser --mode json --output-format json`

These first-contact assets are also mirrored as release and website inputs through `src/cli/onboarding-metadata.json`, `src/public-surface/generated-manifest.ts`, and `src/public-surface/generated-manifest.json`.

Operational help parity check:

```bash
npx opendevbrowser --help
npx opendevbrowser help
```

First-run proof lane:

```bash
node scripts/cli-onboarding-smoke.mjs
```

---

## Automation commands (daemon)

### Serve (daemon)

Start a local daemon to keep sessions alive between commands.

```bash
npx opendevbrowser serve
npx opendevbrowser serve --port 8788 --token my-token

# Stop the daemon
npx opendevbrowser serve --stop
```

The daemon listens on `127.0.0.1` and starts the relay the extension connects to. Metadata lives in
`~/.cache/opendevbrowser/daemon.json` (cache only);
`/status` is the source of truth. The daemon port/token are persisted in `opendevbrowser.jsonc` as `daemonPort`/`daemonToken`.
`serve` now performs a stale-daemon preflight and terminates orphan `opendevbrowser serve` processes before starting (or before
returning "already running"), while preserving the active daemon on the requested port.

If you run onboarding tests alongside an existing daemon, isolate config/cache via `OPENCODE_CONFIG_DIR` and `OPENCODE_CACHE_DIR`
to avoid token/port collisions between sessions. Before daemon-backed workflows, run
`npx opendevbrowser status --daemon --output-format json` and require `data.fingerprintCurrent === true`.

If `nativeExtensionId` is set in `opendevbrowser.jsonc`, `serve` will auto-install the native messaging host when it is missing.
If it is not set, `serve` attempts to auto-detect the extension ID from Chrome, Brave, or Chromium profiles; if detection fails it continues startup.

Relay HTTP endpoints (`/config`, `/status`, `/pair`) accept extension origins and loopback requests with no `Origin` (including
`Origin: null`) to support MV3 + PNA. Non-extension origins are rejected; preflights include
`Access-Control-Allow-Private-Network: true` when requested.

#### Manual recovery (if the daemon is down or stuck)

```bash
# Start the daemon manually
npx opendevbrowser serve

# Stop/kill an existing daemon before restarting
npx opendevbrowser serve --stop

# Verify the daemon matches the current CLI build
npx opendevbrowser status --daemon --output-format json
```

Proceed with daemon-backed automation only when the JSON response has `data.fingerprintCurrent === true`.

#### Protected fingerprint mismatch

A protected daemon fingerprint mismatch means the running daemon was started by a different OpenDevBrowser build than the CLI now issuing daemon-backed commands. It is separate from native messaging host drift. JSON surfaces use `daemon_fingerprint_mismatch` as the stable reason when available.

Recovery sequence:
1. Run `npx opendevbrowser status --daemon --output-format json` and inspect `data.fingerprintCurrent`.
2. If it is `false`, use the binary that started the running daemon, or restart the daemon from the current install.
3. In shared first-run or CI environments, isolate with `OPENCODE_CONFIG_DIR`, `OPENCODE_CACHE_DIR`, and unique daemon or relay ports before retrying.
4. Continue only after `data.fingerprintCurrent === true`.

### Daemon auto-start

Install or remove OS-level auto-start for the daemon. This persists an absolute `node + cli + serve` entrypoint (no PATH
reliance) only when the current CLI path is stable. On macOS it also persists a `WorkingDirectory` under
`~/.cache/opendevbrowser` so launchd never starts the daemon from `/`, plus an `EnvironmentVariables.PATH` containing common Homebrew, MacPorts, Nix, and system binary directories. Temporary `npx` caches and temp onboarding workdirs are rejected instead of
being written to LaunchAgent or Task Scheduler state, and all commands return machine-readable output with `--output-format json`.

```bash
opendevbrowser daemon install
opendevbrowser daemon uninstall
opendevbrowser daemon status

# Persistent local package install alternative
npx --no-install opendevbrowser daemon install
```

Behavior:
- macOS: LaunchAgent at `~/Library/LaunchAgents/com.opendevbrowser.daemon.plist` targeting an absolute `node + cli + serve` entrypoint plus `WorkingDirectory=~/.cache/opendevbrowser` and `EnvironmentVariables.PATH` with Homebrew, MacPorts, Nix, and system paths.
- Windows: per-user Task Scheduler logon task targeting an absolute `node + cli + serve` entrypoint; status inspects the persisted scheduled-task action from Task Scheduler XML instead of task presence alone.
- `daemon status --output-format json` keeps top-level `installed` and `running`, adds nested `autostart` on supported platforms, and includes `status` only when the daemon is running.
- `autostart` is the canonical detail object and includes `health`, `needsRepair`, `reason`, `command`, `expectedCommand`, and macOS working-directory fields when applicable.
- `autostart.reason` can be `transient_cli_path` when an existing LaunchAgent or Task Scheduler action points at a temp-root CLI path that should be repaired; macOS can also report `working_directory_mismatch` when an older LaunchAgent would start from `/` or another unsafe directory, or `missing_environment_path` when a LaunchAgent lacks the required `EnvironmentVariables.PATH` entries.
- when the current invocation is transient, a stable persisted auto-start entry can still report `health="healthy"`; `expectedCommand` is omitted instead of advertising the transient current path as the repair target.
- `daemon status` returns exit code `0` when the daemon is reachable even if auto-start is missing, stale, or malformed, and `10` when the daemon is not running.
- Successful plugin installs surface auto-start reconciliation through `autostartAction`; `autostartError` is included only when repair fails.
- Raw npm global package postinstall best-effort reconciles auto-start only when npm lifecycle context clearly indicates a global install. Local, ambiguous, conflicting, or non-npm package-manager contexts skip auto-start without failing package installation.
- Package postinstall auto-start uses `dist/cli/index.js` as the packaged CLI entrypoint and never persists `scripts/postinstall-sync-skills.mjs`.
- `OPDEVBROWSER_SKIP_INSTALL_AUTOSTART_RECONCILIATION=1` skips install-time auto-start reconciliation only. `OPDEVBROWSER_SKIP_POSTINSTALL_SKILL_SYNC=1` remains the legacy full package lifecycle shim skip because the shim exits before importing built code.
- If install-time reconciliation is running from a transient temp-root CLI path, it refuses to write auto-start and reports `autostartAction="repair_failed"` for plugin installs or a non-fatal package postinstall warning, with guidance to rerun `opendevbrowser daemon install` from a stable install location.

Exit codes align with the CLI:
- `0`: success
- `1`: usage error
- `2`: execution error (permissions, missing binary, OS service failure, unsupported `daemon install|uninstall`, or unexpected status evaluation failure)
- `10`: disconnected/not running (status only)

#### Auto-start install + manual fallback

```bash
# Install auto-start (recommended)
opendevbrowser daemon install

# Or, from a persistent local package install
npx --no-install opendevbrowser daemon install

# If auto-start fails, start manually
npx opendevbrowser serve

# Stop/kill before restarting
npx opendevbrowser serve --stop
```

If you are running from a first-run temp workspace or transient `npx` cache, rerun `opendevbrowser daemon install` from a stable
install location before expecting login auto-start to persist. On macOS, `daemon status --output-format json` should show matching
`autostart.workingDirectory` and `autostart.expectedWorkingDirectory`.

### Native messaging host

Install the native messaging host for the extension to use as a fallback transport when the relay WebSocket is unavailable.

```bash
# Install native host (requires extension ID)
npx opendevbrowser native install <extension-id>

# Check native host status
npx opendevbrowser native status

# Remove native host
npx opendevbrowser native uninstall
```

Notes:
- Supported on macOS, Linux, and Windows.
- Windows install writes a registry entry under `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.opendevbrowser.native`.
- `native install` requires the extension ID (chrome://extensions → copy ID).
- Set `nativeExtensionId` in `opendevbrowser.jsonc` to allow `serve` to auto-install the native host when missing (preferred).
- `serve` also attempts auto-detection from Chrome, Brave, or Chromium profiles, but explicit config is more reliable.
- `native status` reports installed state + extension ID from the manifest.
- Use `--transport native` with `status` to check native host status without requiring the daemon.

### Workflow wrappers

The workflow wrappers expose the finalized research, shopping, product-video, and inspiredesign surfaces from
`docs/RESEARCH_SHOPPING_PRODUCT_VIDEO_FINAL_SPEC.md`, central next-step advisory builders in `src/providers/workflow-handoff.ts`,
and the shared inspiredesign artifact source at `src/inspiredesign/handoff.ts`.

#### Research (`research run`)

`research run` is a provider-constrained evidence collection primitive with a deterministic evidence briefing renderer. Load `opendevbrowser-research` before research tasks so source planning, artifact review, confidence, limitations, and final conclusions stay evidence-gated.

```bash
npx opendevbrowser research run --topic "browser automation" --days 30 --mode compact
npx opendevbrowser research run --topic "Chrome extension debugging workflows" --days 30 --sources web,community --browser-mode managed --mode json
npx opendevbrowser research run --topic "creator tools" --sources web,shopping --include-engagement --limit-per-source 5 --mode context
```

Flags:
- `--topic` (required)
- `--days`
- `--from`
- `--to`
- `--source-selection` (`auto|web|community|social|shopping|all`)
- `--sources` (comma-separated concrete sources)
- `--browser-mode` (`auto|extension|managed`)
- `--profile` (named managed profile for provider browser fallback; explicit CDP profiles use `cdp-profile start` plus `connect --profile` for browser/session primitives)
- `--mode` (`compact|json|md|context|path`)
- `--include-engagement`
- `--limit-per-source`
- `--timeout-ms`
- `--output-dir`
- `--ttl-hours`
- `--use-cookies` (`true|false`; bare flag means `true`)
- `--challenge-automation-mode` (`off|browser|browser_with_helper`)
- `--cookie-policy-override` (`off|auto|required`)
- `--cookie-policy` (alias of `--cookie-policy-override`)

Notes:
- Prefer explicit public source-family examples such as `--sources web,community`; use `--source-selection` only to explain selector semantics.
- Use `--browser-mode extension` when X, Threads, Facebook, Reddit, or another signed-in social provider needs an existing relay-backed browser session; use `managed` for reproducible no-auth reruns.
- In the current contract, `auto` and `all` both stay inside the public topical families (`web`, `community`, `social`), but neither value guarantees reliability.
- Add shopping only with `--source-selection shopping` or explicit `--sources ...shopping...` when the task is deliberately commercial.
- Successful research artifact bundles include `report.md`, `summary.md`, `records.json`, `context.json`, `meta.json`, and `bundle-manifest.json`.
- `report.md` is the primary deterministic evidence briefing. It renders evidence gate status, final answer, claim map, theme synthesis, source agreement or disagreement, confidence by claim, limitations, recommendations, and an evidence appendix from accepted records and metadata.
- `report.md` does not call a paid API, hidden LLM, or external interpretation step. Audit claims against `records.json`, `context.json`, and `meta.json` before publishing final claims.
- Research runs fail instead of emitting a successful empty report when providers return only shell records, stale records, or no source evidence; successful runs persist diagnostics in `meta.json`.

#### Shopping (`shopping run`)

```bash
npx opendevbrowser shopping run --query "usb microphone" --mode compact
npx opendevbrowser shopping run --query "wireless ergonomic mouse" --providers shopping/bestbuy,shopping/ebay --budget 150 --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --mode json --output-format json
npx opendevbrowser shopping run --query "27 inch 4k monitor" --providers shopping/bestbuy,shopping/ebay --budget 350 --sort lowest_price --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --mode json --output-format json
npx opendevbrowser shopping run --query "wireless earbuds" --providers shopping/amazon --region us --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --mode json --output-format json
```

Flags:
- `--query` (required)
- `--providers` (comma-separated; defaults to all v1 adapters)
- `--budget`
- `--region`
- `--browser-mode` (`auto|extension|managed`)
- `--profile` (named managed profile for provider browser fallback; explicit CDP profiles use `cdp-profile start` plus `connect --profile` for browser/session primitives)
- `--sort` (`best_deal|lowest_price|highest_rating|fastest_shipping`)
- `--mode` (`compact|json|md|context|path`)
- `--timeout-ms`
- `--output-dir`
- `--ttl-hours`
- `--use-cookies` (`true|false`; bare flag means `true`)
- `--challenge-automation-mode` (`off|browser|browser_with_helper`)
- `--cookie-policy-override` (`off|auto|required`)
- `--cookie-policy` (alias of `--cookie-policy-override`)

Notes:
- Use explicit providers plus `--browser-mode managed` for the most reproducible live reruns.
- `deals.md` is the primary deterministic buying brief. It renders `# Shopping Buying Brief`, a Buying Readiness Gate, Recommendation, Best Candidate Offers, Market Baseline, Warnings and Constraints, Excluded or Constrained Offers, and an Evidence Appendix.
- Buying readiness is `pass`, `partial`, or `fail`. `pass` allows bounded buying guidance, `partial` means use the output as a constrained shortlist, and `fail` means no confident purchase recommendation is allowed from the current evidence.
- Raw audit surfaces stay separate: `offers.json` preserves structured offers, `comparison.csv` preserves the tabular comparison with appended currency and total-status audit fields, `meta.json` preserves workflow diagnostics, and `deals-context.json` preserves `query`, report-derived `highlights`, raw `offers`, and `meta` for agent handoff.
- `compact` and `context` modes summarize the same report guidance as `deals.md`; `json` mode still returns raw offers and meta.
- Expect warnings for stale, inferred, or missing price freshness; unknown or out-of-stock availability; weak query relevance; suspicious titles; duplicate same-title or same-product pressure; workflow alerts; and advisory region handling.
- Treat `--region` as advisory unless `meta.selection.region_authoritative=true`. If `meta.alerts` includes `reasonCode=region_unenforced`, do not present the output as a trustworthy regional comparison. The alert is emitted only for selected provider diagnostics that did not enforce the requested region; enforced-region diagnostics do not create that warning.
- The Market Baseline section is computed only from deterministic same-currency evidence. If sample size or currency coverage is insufficient, the report says `market baseline unavailable` instead of inventing savings.
- Seller trust, return policy, warranty, condition, shipping certainty, and price history are not guaranteed fields. Treat them as unavailable unless the raw offer attributes and report text explicitly include them.
- When a run returns no final offers, inspect `meta.primaryConstraintSummary` first.
- If `meta.primaryConstraint.guidance` is present, follow `meta.primaryConstraint.guidance.reason` and `meta.primaryConstraint.guidance.recommendedNextCommands[]` before classifying the provider path as broken.
- If `meta.primaryConstraint.guidance` is absent, inspect `meta.offerFilterDiagnostics`; summary-only outcomes are usually offer-filter constraints such as budget or region heuristics, not provider outage proof.

#### Product presentation asset (`product-video run`)

```bash
npx opendevbrowser product-video run --product-url "https://example.com/p/1" --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-screenshots
npx opendevbrowser product-video run --product-name "Sample Product" --provider-hint shopping/amazon --browser-mode extension --use-cookies --challenge-automation-mode browser_with_helper
```

Flags:
- `--product-url` (required unless `--product-name` is provided)
- `--product-name` (required unless `--product-url` is provided)
- `--provider-hint`
- `--include-screenshots` (`true|false`; bare flag means `true`)
- `--include-all-images` (`true|false`; bare flag means `true`)
- `--include-copy` (`true|false`; bare flag means `true`)
- `--timeout-ms`
- `--browser-mode` (`auto|extension|managed`)
- `--profile` (named managed profile for provider browser fallback; explicit CDP profiles use `cdp-profile start` plus `connect --profile` for browser/session primitives)
- `--output-dir`
- `--ttl-hours`
- `--use-cookies` (`true|false`; bare flag means `true`)
- `--challenge-automation-mode` (`off|browser|browser_with_helper`)
- `--cookie-policy-override` (`off|auto|required`)
- `--cookie-policy` (alias of `--cookie-policy-override`)

Notes:
- Successful product-video runs write `manifest.json`, `product.json`, `pricing.json`, `copy.md`, `features.md`, `presentation-readiness.json`, `raw/source-record.json`, and `bundle-manifest.json`.
- `--output-dir` is a workflow root, not the final bundle directory. Omit it for routine runs and inspect the returned `artifact_path` first; when omitted, persisted artifacts land under `.opendevbrowser/product-video/<runId>`. If a wrapper must pass an explicit root, prefer `--output-dir .opendevbrowser`.
- `presentation-readiness.json` is the audit surface for `presentationReadiness`, `productVideoReadiness`, selected and original record ids, bounded candidate summaries, promoted claims, rejected candidate summaries, evidence references, and compact counts.
- `manifest.readiness.presentation` and `manifest.readiness.productVideo` are the manifest production gates. `product.json.presentationReadiness`, `product.json.productVideoReadiness`, returned `product.presentationReadiness`, and returned `product.productVideoReadiness` mirror the product-facing gates, while JSON workflow output exposes `meta.presentationReadiness` and `meta.productVideoReadiness`.
- Readiness status is `pass`, `partial`, or `fail`. `pass` can feed a normal production brief after human evidence review. `partial` is a constrained draft and must carry warnings plus reason codes. `fail` blocks production use.
- Raw evidence stays preserved under `raw/source-record.json` for audit and debugging. Do not treat raw marketplace, site navigation, catalog, seller, shipping, condition, or returns text as verified copy unless it is promoted through `presentation-readiness.json.promotedClaims[]`.
- `copy.md` and `features.md` are not automatically verified production input. They are production-safe only when readiness permits and the claims evidence map ties each claim to captured or structured evidence.
- `render-video-brief.sh <pack>/manifest.json [output-dir]` reads `manifest.readiness`: `pass` writes normal production briefs, `partial` writes gated briefs with warnings and reason codes, and `fail` exits nonzero after warning-only diagnostics.

#### Inspiredesign (`inspiredesign run`) and `inspiredesign harvest`

```bash
npx opendevbrowser inspiredesign run --brief "Synthesize a premium docs landing page from calm editorial references" --url https://stripe.com --url https://vercel.com
npx opendevbrowser inspiredesign run --brief "Extract a reusable dashboard design contract from live references" --url https://linear.app --browser-mode managed --use-cookies --challenge-automation-mode browser_with_helper --include-prototype-guidance
npx opendevbrowser inspiredesign harvest --brief "Synthesize a premium docs workspace" --query "best docs product landing pages" --provider web/default --max-references 5 --visual-evidence required --browser-mode managed --mode json
npx opendevbrowser inspiredesign harvest --brief "Premium digital photography studio landing page" --query "Pinterest premium digital photography studio landing page cinematic parallax portfolio" --provider social/pinterest --max-references 5 --visual-evidence required --browser-mode managed --profile pinterest-design --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper --mode json --output-format json
npx opendevbrowser inspiredesign harvest --brief "Fashion design studio landing page with atelier motion references" --provider social/pinterest --url "https://www.pinterest.com/pin/27654985208435505/" --max-references 5 --visual-evidence required --browser-mode managed --profile pinterest-design --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper --mode json --output-format json
```

Flags:
- `--brief` (required)
- `--url` (repeatable inspiration URL input; explicit URLs are kept before discovered URLs)
- `--query` (`harvest` only; discovers bounded public references through provider search when available)
- `--provider` (repeatable `harvest` provider id used with `--query` or compatible browser-native URL recovery; `social/pinterest` routes through the Pinterest site recipe, not a default full social provider)
- `--max-references` (`1` to `10`; `harvest` defaults to `5`)
- `--visual-evidence` (`off|auto|required`; `run` defaults to `off`, `harvest` defaults to `required`)
- `--capture-mode` (`off|deep`; `run` forces deep capture for any explicit `--url`; `harvest` forces deep capture for non-Pinterest explicit `--url`; Pinterest-only harvest discovery and compatible Pinterest URL recovery force `captureMode=off` even when `--capture-mode deep` is requested)
- `--include-prototype-guidance` (`true|false`; bare flag means `true`)
- `--mode` (`compact|json|md|context|path`; `harvest` defaults to `path`)
- `--timeout-ms`
- `--output-dir`
- `--ttl-hours`
- `--browser-mode` (`auto|extension|managed`)
- `--profile` (named managed profile for provider browser fallback; explicit CDP profiles use `cdp-profile start` plus `connect --profile` for browser/session primitives)
- `--use-cookies` (`true|false`; bare flag means `true`)
- `--challenge-automation-mode` (`off|browser|browser_with_helper`)
- `--cookie-policy-override` (`off|auto|required`)
- `--cookie-policy` (alias of `--cookie-policy-override`)

Notes:
- `inspiredesign harvest` is an explicit entrypoint on the existing `inspiredesign.run` daemon method. It does not create a new daemon method.
- `inspiredesign harvest` requires either `--query` or at least one `--url`; use `--query` for provider discovery and `--url` for explicit visual references.
- `run` keeps compact text output and visual evidence off unless the caller opts in. `harvest` defaults to path output, visual evidence required, and `maxReferences=5`.
- `--output-dir` is a workflow root, not the final bundle directory. Omit it for routine runs and inspect the returned `artifact_path` first; when omitted, persisted artifacts land under `.opendevbrowser/inspiredesign/<runId>`. If a wrapper must pass an explicit root, prefer `--output-dir .opendevbrowser`.
- Proven Pinterest image, GIF, and video pins require manifest-backed pin-media evidence for product-ready canonical pin-media harvests. Screenshot evidence and screencast evidence remain useful capture or motion lanes, but they are not substitutes for `evidenceAuthority=pin_media_ready`. Video posters remain still-image fallback cues, and DOM/clone/deep capture is disabled for Pinterest harvest. Remote DOM media URLs are not product-readiness signals unless persisted first-party bytes appear in `pin-media-index.json`.
- `inspiredesign run` forces `captureMode=deep` for any explicit `--url`, including Pinterest URLs, so the workflow can collect DOM/layout diagnostics. `inspiredesign harvest` forces `captureMode=deep` for non-Pinterest explicit `--url` references, even when `--capture-mode off` is requested. Pinterest-only harvest discovery and compatible Pinterest URL recovery force `captureMode=off` even when `--capture-mode deep` is requested.
- Repeat `--url` for multiple inspiration sources. There is no `--urls` alias.
- `harvest` merges explicit URLs before discovered URLs, trims and de-duplicates references, and stores rejected reference diagnostics in generated metadata.
- Query-based Pinterest harvests become product-ready only through query-discovered canonical `/pin/{id}/` references plus manifest-backed first-party pin-media bytes. `discovery-diagnostics.json` records accepted and rejected URL counts, blocker diagnostics, and recovery actions; login/challenge and search-shell diagnostics are recovery paths, not product-ready evidence.
- `social/pinterest` is a browser-native site recipe. For non-Google logged-in Pinterest search, prefer a dedicated managed headed profile with `--profile <name>`, `--use-cookies`, and `--cookie-policy required`; use extension `/ops` when you need live active-tab reuse. Registry-backed explicit CDP profiles are available for browser/session primitives through `cdp-profile start` plus `connect --profile`, but provider workflows do not yet expose an explicit-CDP transport selector. Compatible Pinterest URL recovery can run as `--provider social/pinterest --url <pinterest-url>` without `--query`; generic provider plus URL recovery without a query remains rejected. Use one canonical `/pin/{id}/` URL per harvest when validating design-ready pin media. The workflow must recover session evidence first when Pinterest returns login, challenge, empty-grid, or search-shell states.
- Canonical Pinterest pin-media harvest opens the exact canonical pin in the active managed or extension workflow session before extracting persisted first-party bytes. Extension `/ops` remains the best live-tab reuse path, but it is no longer the only intended logged-in Pinterest lane.
- Browser-native site recipes do not silently widen scope to unrelated providers. If fallback to broad web sources is desired, ask the user or rerun with an explicit `--provider web/default`.
- `--include-prototype-guidance` appends prototype structure guidance to the generated design contract output.
- Successful runs emit `advanced-brief.md`, `design-agent-handoff.json`, `visual-evidence.json`, `screenshot-index.json`, `motion-evidence.json`, `pin-media-evidence.json`, `pin-media-index.json`, `media-analysis.json`, `ranked-references.json`, `discovery-diagnostics.json` for query harvests, and `meta-prompt.md` alongside the existing design contract and implementation artifacts. `canvas-plan.request.json` is emitted only when `productSuccess=true` and `artifactAuthority=product_ready`. Harvest PNG files are written under `visual-evidence/<referenceId>/viewport.png`; motion replays, HTML previews, and preview PNGs are written under `motion-evidence/<referenceId>/` when video evidence is captured; persisted Pinterest pin media files are written under `pin-media-evidence/<referenceId>/main.*`, `pin-media-evidence/<referenceId>/video.mp4`, or `pin-media-evidence/<referenceId>/poster.*` when canonical pin media proof is captured.
- `design-contract.json.colorSystem.tokens` and `implementation-plan.json.tokenStrategy.colors` use explicit `{ light, dark }` semantic token maps. `design-agent-handoff.json.implementationContext.tokenStrategy` carries the same dual-mode token strategy for implementation agents.
- `media-analysis.json` is a deterministic design-fact surface emitted from trusted saved pin media after finalization. It can describe dimensions, tone, quantized palette, layout posture, OCR-free typography structure, text-region geometry, and sampled GIF/video motion facts including `motionSignature`, but it never grants product readiness or replaces `pin-media-index.json` authority. media-analysis.json never satisfies product readiness. `motionSignature` is saved-media analysis only, not browser replay evidence or interaction choreography.
- FFmpeg and FFprobe are recommended optional host tools for richer `media-analysis.json` metadata and sampled GIF or video frame facts. OpenDevBrowser does not bundle static FFmpeg binaries and they are not downloaded by default.
- Media-analysis binary resolution is environment, then config, then `PATH`, then common absolute install directories for implicit `PATH`-source ENOENT misses: `OPENDEVBROWSER_FFMPEG_PATH` and `OPENDEVBROWSER_FFPROBE_PATH`, then `inspiredesign.mediaAnalysis.ffmpegPath` and `inspiredesign.mediaAnalysis.ffprobePath`, then `ffmpeg` and `ffprobe` from `PATH`, and then common absolute directories such as Homebrew, MacPorts, Nix, and system binary locations only when the bare `PATH` lookup is missing. Invalid env or config paths stay diagnostic and do not fall back.
- Missing or invalid FFmpeg or FFprobe binaries degrade `media-analysis.json` only. They can reduce metadata, palette, tone, scene-score, or sampled motion detail, but they do not fail pin-media readiness and do not let `media-analysis.json` replace `pin-media-index.json` or `motion-evidence.json` authority. Empty `motion-evidence.json` means no authoritative browser replay screencast was captured; saved GIF/video motion can still appear in `media-analysis.json` as sampled saved-media facts.
- `pin-media-index.json` remains the only pin-media readiness and provenance authority for persisted first-party pin media bytes. `media-analysis.json` can inform `ranked-references.json`, `design.md`, `generation-plan.json`, `design-contract.json`, `meta-prompt.md`, and `design-agent-handoff.json`, but it cannot make a reference `pin_media_ready`.
- V1 media analysis does not perform readable text extraction, OCR, model vision, Tesseract, OpenCV, Sharp, browser canvas analysis, or new dependency-backed image decoding. Exact headlines, nav labels, CTA copy, and font families remain unavailable unless supplied by the brief, metadata, or a later explicitly approved readable-text layer.
- `canvas-plan.request.json` does not contain raw `mediaAnalysis`. Canvas receives only concise media-derived summaries through existing generation-plan and design-vector fields when readiness already permits Canvas continuation.
- `ranked-references.json.rejectedReferences` serializes captured-but-rejected diagnostics, including untrusted `interface_chrome_shell`, without promoting those captures into design-facing references.
- Visual JSON is metadata-only: it contains artifact-relative paths, hashes, byte counts, viewport metadata when available, reference id and URL, and warnings. It must not contain base64 screenshots, temp paths, full DOM, or full snapshot text.
- Policy boundaries are preserved: visual capture must not bypass `policy_blocked`, unresolved `auth_required`, `challenge_detected`, or `rate_limited` provider outcomes.
- Workflow outputs can include typed `nextStepGuidance` with `readiness`, `reasonCode`, `primaryAction`, command examples, `paramsExamples`, `validationChecks`, `fallbackPolicy`, and `doNotProceedIf` blockers.
- Harvest outputs separate operational completion, guidance readiness, and product readiness. `success:true` or `ok:true` means the command completed and wrote artifacts; top-level `ready` is product-ready only when `productSuccess=true`, `artifactAuthority=product_ready`, and `evidenceAuthority` is non-diagnostic. `readiness` and `guidanceReadiness` expose the next-step guidance state.
- Treat product readiness as the gate between artifact completion and design readiness. Continue to Canvas only when top-level `ready=true`, `productSuccess=true`, `artifactAuthority=product_ready`, ranked references are non-empty, manifest-backed authority evidence exists, and no `doNotProceedIf` condition applies. For canonical Pinterest pin-media harvests, Canvas continuation requires `evidenceAuthority=pin_media_ready` and manifest-backed `pin-media-index.json`; `snapshot_ready` and `motion_ready` are not substitutes for pin-media readiness.
- Pinterest product readiness is pin-media-first: canonical pin URLs become product-ready only when their first-party pin-media artifact is captured, persisted, present in its manifest-backed index, and free of blocking warnings. Screenshot and screencast artifacts can inform diagnostics or motion design, but they do not satisfy required Pinterest pin-media readiness. The exact `login_or_challenge_state` and strict byte-backed `interface_chrome_shell` diagnostics are non-blocking only for trusted first-party manifest-backed pin-media bytes; broader login, challenge, captcha, search-shell, promoted, ad, blank, tiny, or chrome-only blockers still demote readiness unless query-discovered canonical pins also produce complete pin-media authority. When pin-media authority is complete, unavailable supplemental viewport screenshot evidence is recorded as skipped and satisfied by pin media, not as product-readiness failure. `pin-media-index.json` remains Pinterest authority, `media-analysis.json` remains advisory, and `motion-evidence.json` remains browser replay authority. Multimodal agents reason over those artifacts directly.
- CLI completion text includes `guidanceReadiness=<value> productSuccess=false artifactAuthority=<value> evidenceAuthority=<value>` when guidance readiness exists but product readiness is false. Product-ready runs can still emit `readiness=<value>`.
- For `needs_recovery`, `blocked`, or `diagnostic_only`, follow the primary recovery action first. Common blockers are zero references, empty ranked references, failed required screenshot, screencast, or pin-media evidence, provider unavailability, login or challenge screens, and diagnostic-only captures.
- The ready follow-through path is explicit: read `advanced-brief.md`, `meta-prompt.md`, `evidence.json`, `ranked-references.json`, `bundle-manifest.json`, `media-analysis.json` when present, and screenshot, screencast, or pin-media evidence first; load `opendevbrowser_skill_load opendevbrowser-best-practices "quick start"`, `opendevbrowser_skill_load opendevbrowser-design-agent "canvas-contract"`, and `opendevbrowser_skill_load opendevbrowser-motion-design "quick start"`; continue only if `canvas-plan.request.json` exists; open a Canvas session; fill the session ids in `canvas-plan.request.json`; run `opendevbrowser canvas --command canvas.plan.set --params-file ./canvas-plan.request.json`; confirm `planStatus=accepted`; then patch only the governance blocks called out by `design-agent-handoff.json`.
- `--browser-mode` applies to provider-backed reference retrieval. Screenshot, screencast, and deep diagnostic capture still use browser manager capture lanes.

Wrapper behavior:
- Timebox semantics are strict (`--days` is mutually exclusive with `--from/--to`).
- Render modes for `research`, `shopping`, and `inspiredesign` are shared: `compact|json|md|context|path`.
- Successful research, shopping, inspiredesign, and product-video artifact-bearing outputs include `artifact_path`.
- `inspiredesign run` returns a reusable design contract plus a typed handoff bundle. Canvas is primary only when product readiness is true, not merely when guidance readiness is `ready`.
- `inspiredesign harvest` extends the same workflow with query discovery, ranked references, screenshot, screencast, and pin-media evidence metadata, persisted evidence artifacts, typed recovery guidance, and Canvas follow-through only when product readiness is true.
- Prefer omitting `--output-dir` for routine research, shopping, inspiredesign, and product-video workflow runs, then inspect the returned `artifact_path` first. When `--output-dir` is omitted, path-bearing workflow outputs persist to `<invocation-or-workspace-root>/.opendevbrowser/<namespace>/<runId>` and include TTL metadata plus `bundle-manifest.json`. If a wrapper must pass an explicit workflow root, prefer `--output-dir .opendevbrowser`; the runtime appends the namespace and run id, so Inspiredesign lands at `.opendevbrowser/inspiredesign/<runId>`. Use external roots only intentionally, such as temp cleanup roots, release proof outputs, debug or audit evidence, screenshots, and screencast replay directories. The CLI rejects blank `--output-dir` values and resolves relative paths from the invocation directory before daemon dispatch. CLI runs use the process invocation directory. Direct daemon RPC uses `core.workspaceRoot/.opendevbrowser`, and direct OpenCode tools use the injected `deps.workspaceRoot/.opendevbrowser`; `src/index.ts` wires that value from `core.workspaceRoot`. Direct provider workflow calls without an injected workspace root keep the provider-level `process.cwd()/.opendevbrowser` fallback. Namespaces are `research`, `shopping`, `inspiredesign`, and `product-video`. Direct tool or daemon callers can still pass an explicit output directory for caller-specific placement.
- Workflow cookie policy defaults to `providers.cookiePolicy=auto` and source defaults to `providers.cookieSource` (`file`, `env`, or `inline`).
- Effective policy precedence is `--cookie-policy-override`/`--cookie-policy` > `--use-cookies` > config defaults.
- `auto` attempts injection when cookies are available and continues when cookies are missing/unusable.
- `required` fails fast with `reasonCode=auth_required` when cookie loading/injection/verification cannot establish a session.
- Workflow challenge automation defaults to `providers.challengeOrchestration.mode`.
- Effective challenge precedence is `challengeAutomationMode` with `run > session > config`.
- `off` keeps detection and reporting active but stands down challenge actions.
- `browser` enables only browser-native lanes and forces the helper bridge to stand down.
- `browser_with_helper` preserves browser-first lane ordering and only evaluates the browser-scoped helper bridge when hard gates pass.
- The helper bridge is browser-scoped and is not a desktop agent.
- Cookie diagnostics are exposed in workflow metrics under `meta.metrics.cookie_diagnostics` and `meta.metrics.cookieDiagnostics`.
- Shopping providers that return zero usable offer records now emit `meta.failures[*].error.reasonCode=env_limited` instead of silently counting as success.

### Artifact lifecycle cleanup

Use the artifact cleanup command to remove expired bundles generated by workflow runs:

```bash
npx opendevbrowser artifacts cleanup --expired-only
npx opendevbrowser artifacts cleanup --expired-only --output-dir /tmp/opendevbrowser
```

Script helper:

```bash
./scripts/artifacts-cleanup.sh
```

Notes:
- `--expired-only` is required.
- When `--output-dir` is omitted, cleanup targets the current working directory's `.opendevbrowser` root, matching omitted CLI workflow output roots.
- Use `--output-dir /tmp/opendevbrowser` only when intentionally cleaning an explicit temp artifact root.
- The script helper changes to the repository root before invoking the CLI, so omitted script cleanup targets the repository root `.opendevbrowser`.
- Output includes `removed` and `skipped` run paths.
- Cleanup removes expired workflow bundles with `bundle-manifest.json`; Canvas, browser screenshot, screencast, annotation, desktop audit, and release proof outputs are intentional non-bundle lanes and are not targets for bundle manifest cleanup.

### Run (single-shot script)

Run a JSON script without the daemon.

```bash
npx opendevbrowser run --script ./script.json --output-format json

# Or pipe JSON
cat ./script.json | npx opendevbrowser run --output-format stream-json

# Optional launch flags for run
npx opendevbrowser run --script ./script.json --headless --profile default --start-url https://example.com
npx opendevbrowser run --script ./script.json --chrome-path "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --flag "--disable-gpu"
```

Supported `run` launch flags:
- `--headless`
- `--profile`
- `--persist-profile`
- `--chrome-path`
- `--start-url`
- `--flag` (repeatable)

`--chrome-path` accepts Google Chrome, Chromium, or a Playwright-installed Chrome for Testing binary when you need a deterministic automation browser.

Script format:

```json
[
  { "action": "goto", "args": { "url": "https://example.com" } },
  { "action": "snapshot" },
  { "action": "click", "args": { "ref": "r1" } }
]
```

---

## Session commands (daemon required)

### Launch

```bash
npx opendevbrowser launch --profile default --start-url https://example.com

# Extension relay controls
npx opendevbrowser launch --extension-only
npx opendevbrowser launch --no-extension
npx opendevbrowser launch --extension-legacy
npx opendevbrowser launch --wait-for-extension --wait-timeout-ms 30000
npx opendevbrowser launch --google-auth-intent user-owned --extension-only --wait-for-extension
npx opendevbrowser launch --no-extension --disable-system-cookie-bootstrap
npx opendevbrowser launch --no-extension --allow-google-cookie-bootstrap
npx opendevbrowser launch --no-extension --profile pinterest-design --start-url https://www.pinterest.com
```

Flags:
- `--headless`
- `--profile`
- `--start-url`
- `--chrome-path`
- `--persist-profile`
- `--flag` (repeatable)
- `--no-extension`
- `--extension-only`
- `--extension-legacy`
- `--wait-for-extension`
- `--wait-timeout-ms`
- `--google-auth-intent user-owned`
- `--disable-system-cookie-bootstrap`
- `--allow-google-cookie-bootstrap`

Default behavior:
- Extension relay (`extension` mode) is the default when available, using the `/ops` WebSocket.
- If the extension is not connected, launch fails with guidance and exact commands for the explicit alternatives.
- Headless is never the default.
- Extension headless is unsupported. `launch --headless` must be paired with `--no-extension`; extension-intent headless requests fail with `unsupported_mode`.
- For non-Google login-required workflows, use a dedicated managed headed profile first: `launch --no-extension --profile <name> --start-url <site>`. This creates OpenDevBrowser-owned profile continuity without requiring the extension or automating the daily Chrome profile.
- `--google-auth-intent user-owned` is for runs that must reuse a user-owned Google OAuth session. It requires extension `/ops` against the live Chrome profile and fails closed for `--no-extension`, `--headless`, `--extension-legacy`, and direct CDP.
- When hub mode is enabled, there is no local relay fallback. If the hub is unavailable, commands fail with guidance.
- Extension relay requires Chrome 125+ (flat CDP sessions).
- Managed and `cdpConnect` sessions make a best-effort attempt to bootstrap readable cookies from the discovered system Chrome-family profile before first navigation unless `--disable-system-cookie-bootstrap` is set. Extension mode reuses the already logged-in tab or profile instead of importing cookies.
- Google-sensitive cookies are skipped by default during managed and direct `cdpConnect` bootstrap; `--allow-google-cookie-bootstrap` is a diagnostic override for runs that explicitly accept that risk.
- Managed/CDP cookie bootstrap is best-effort and copies readable cookies only. Copied cookies are not Google auth proof and are not a substitute for extension `/ops` when the task depends on user-owned Google OAuth continuity.
- Launch and connect results expose sanitized `diagnostics.authProvenance`. Do not use or record private cookies, tokens, account identifiers, full profile paths, or account screenshots.
- If a Google OAuth flow appears to cause perceived logout or auth invalidation, use the extension `/ops` path above and keep managed/CDP cookie bootstrap disabled while investigating.
- After Google sign-in or account chooser actions, recover the OAuth popup by running `targets-list --include-urls`, then `target-use --target-id <target-id>` for the chosen target.
- For isolated automation harnesses that rely on browser startup flags such as `--disable-extensions-except`, prefer Chromium or Chrome for Testing. Google Chrome stable may ignore those flags.

Interactive vs non-interactive:
- Interactive CLI (TTY): you will be prompted to connect the extension, then explicitly choose Managed or CDPConnect if you want to proceed.
- Non-interactive (agents/CI): the command fails fast and prints the exact commands to run for Managed or CDPConnect.
- If `status --daemon` shows `ext=on` but `handshake=off`, reopen the extension popup and click Connect again to re-establish a clean daemon-extension handshake before retrying extension-mode work.

### Connection flags & status semantics

| Flag / Status | Meaning | Notes |
|---|---|---|
| `--no-extension` | Force managed mode | Bypasses relay and extension entirely. |
| `--extension-only` | Require extension mode | Fails if extension is not connected/handshaken. |
| `--extension-legacy` | Use legacy extension relay | Routes through relay `/cdp` instead of `/ops`. |
| `--wait-for-extension` | Wait for extension handshake | Only applies to extension mode; waits up to `--wait-timeout-ms`. |
| `--wait-timeout-ms` | Max wait for extension | Defaults to 30s. |
| `extensionConnected` | Extension websocket connected | `false` means popup isn’t connected to relay. |
| `extensionHandshakeComplete` | Extension handshake done | `false` means reconnect/repair from popup. |
| `annotationConnected` | Active `/annotation` client attached | Expected `false` unless annotate relay transport is active. |
| `opsConnected` | Active `/ops` client attached | Presence-only signal; it does not prove the current extension target is owned by `/ops`. |
| `canvasConnected` | Active `/canvas` client attached | Expected `false` unless a design-canvas session is using relay preview/overlay features. |
| `cdpConnected` | Active `/cdp` client attached | Expected `false` until a legacy `/cdp` session connects. |
| `pairingRequired` | Relay token required | When `true`, `/ops`, `/canvas`, `/annotation`, and `/cdp` require a token (auto-fetched). |

### Connect

```bash
npx opendevbrowser connect --ws-endpoint ws://127.0.0.1:9222/devtools/browser/...
npx opendevbrowser connect --host 127.0.0.1 --cdp-port 9222
npx opendevbrowser connect --profile pinterest-design
npx opendevbrowser connect --google-auth-intent user-owned --ws-endpoint ws://127.0.0.1:8787/ops
npx opendevbrowser connect --host 127.0.0.1 --cdp-port 9222 --disable-system-cookie-bootstrap
npx opendevbrowser connect --host 127.0.0.1 --cdp-port 9222 --allow-google-cookie-bootstrap
```

This command starts a `cdpConnect` session (attach to an existing Chrome with remote debugging enabled).
Use `connect --profile <name>` only for a registry-backed OpenDevBrowser-owned CDP profile started with `cdp-profile start`; raw `--ws-endpoint` and host/port attach remain browser-control-only with unknown profile scope.
If the `--ws-endpoint` points at the local relay (for example `ws://127.0.0.1:8787` or `ws://127.0.0.1:8787/ops`),
the CLI will normalize to `/ops` and route through the extension relay (`extension` mode).
Use `--extension-legacy` if you need the legacy `/cdp` relay path.
When routing through the relay, the CLI automatically fetches relay config and the pairing token (if required) and authenticates
the `/ops` or `/cdp` connection. Direct relay websocket connections without a token are rejected when pairing is enabled.
Direct `cdpConnect` sessions use the same best-effort Chrome-family cookie bootstrap path as managed launches.
`--google-auth-intent user-owned` requires extension `/ops` and fails closed for direct CDP and `--extension-legacy`.
Use `--disable-system-cookie-bootstrap` when a managed or direct `cdpConnect` run should not copy readable system Chrome-family cookies. Use `--allow-google-cookie-bootstrap` only when a diagnostic run explicitly accepts importing Google-sensitive cookies. Copied cookies are not Google auth proof.

### CDP profile

```bash
npx opendevbrowser cdp-profile start --profile pinterest-design --start-url https://www.pinterest.com
npx opendevbrowser connect --profile pinterest-design
npx opendevbrowser cdp-profile status --profile pinterest-design
npx opendevbrowser cdp-profile stop --profile pinterest-design
```

`cdp-profile` starts Chrome with an OpenDevBrowser-owned non-default user data directory and local remote-debugging endpoint. It refuses the `default` profile name, stores only sanitized profile provenance, and strips raw websocket endpoints from command output. Use it when a workflow needs direct CDP without the extension and without touching the user's daily Chrome profile. Stop only closes OpenDevBrowser-owned launches with matching live ownership.

### Relay binding queue

Hub binding is exclusive only for binding/legacy paths (for example `/cdp` and commands that require a binding). Additional clients are queued FIFO and wait up to 30s by default.
If you see `RELAY_WAIT_TIMEOUT`, retry after the current binding expires or stop the other bound client.

Relay behavior note: extension uses a single extension websocket, while the relay can serve multiple `/ops` clients. Disconnecting the extension or restarting the relay drops active sessions, including annotation flows. Reconnect via the popup (or restart the daemon) before retrying.

### Concurrency semantics

- Transport-level concurrency: `/ops` supports multi-client access and multiple sessions.
- Runtime scheduling uses `ExecutionKey = (sessionId, targetId)` with target-scoped queues.
- Same target stays FIFO; different targets in one session run in parallel up to `effectiveParallelCap`.
- `session-per-worker` remains the safest baseline for simple operational isolation, but in-session multi-target parallelism is supported on `/ops`, managed, and `cdpConnect`.
- Use explicit `--target-id` routing for concurrent flows; treat `target-use` as ergonomic fallback only.
- Use `/ops` as the default concurrent relay channel. Use `--extension-legacy` (`/cdp`) only for compatibility-specific paths.
- Legacy `/cdp` does not coexist with an active `/ops` lease. If launch fails with `cdp_attach_blocked`, disconnect the `/ops` session and retry `--extension-legacy`.
- Legacy `/cdp` stays sequential (`effectiveParallelCap=1`) by design.
- For managed parallel launches with persisted profiles, use unique profile directories per session (or disable persistence) to avoid ProcessSingleton/SingletonLock collisions.
- Parity divergences are registry-bound in `docs/PARITY_DECLARED_DIVERGENCES.md`.

### Disconnect

```bash
npx opendevbrowser disconnect --session-id <session-id>
npx opendevbrowser disconnect --session-id <session-id> --close-browser
```

### Status

```bash
npx opendevbrowser status               # daemon status (default)
npx opendevbrowser status --daemon      # daemon status (explicit)
npx opendevbrowser status --session-id <session-id>
npx opendevbrowser status --transport native
npx opendevbrowser status --daemon --output-format json
```

Daemon JSON semantics:
- `data.fingerprintCurrent === true`: the running daemon fingerprint matches the current CLI build and daemon-backed workflows may proceed.
- `data.fingerprintCurrent === false`: the daemon is reachable but protected operations may reject reuse or stop; recovery should follow the protected fingerprint mismatch runbook.
- `data.reason === "daemon_fingerprint_mismatch"`: a stable automation reason for a protected mismatch when surfaced by the command.
- Missing `data.fingerprintCurrent` should be treated as not current by release and validation harnesses until a fresh daemon status proves otherwise.

### Status capabilities

```bash
npx opendevbrowser status-capabilities
npx opendevbrowser status-capabilities --session-id <session-id> --target-id <target-id> --challenge-automation-mode browser_with_helper --timeout-ms 30000
```

Notes:
- `status-capabilities` can inspect host capability discovery without a session, or add `--session-id` to include session-scoped browser and policy state.
- JSON output includes `host.mediaAnalysis` for FFmpeg and FFprobe availability, source, version, and limitation details before Inspiredesign media-analysis runs.
- `--challenge-automation-mode` lets you preview the effective browser-scoped computer-use mode and source before running a workflow.

### Cookie import

```bash
npx opendevbrowser cookie-import \
  --session-id <session-id> \
  --cookies-file ./cookies.json \
  --strict=false \
  --request-id req-cookie-001

npx opendevbrowser cookie-import \
  --session-id <session-id> \
  --cookies '[{"name":"synthetic-test-cookie","value":"fixture","url":"https://example.test"}]'
```

Notes:
- Prefer `--cookies-file` for real cookies. Inline `--cookies` can leak through shell history and process arguments, so reserve it for non-sensitive synthetic tests.
- Provide exactly one cookies source: `--cookies-file` or `--cookies`.
- `--strict` defaults to `true`.
- Managed and `cdpConnect` sessions already attempt Chrome-family cookie bootstrap on session creation; use `cookie-import` when you need to add or override cookies explicitly.
- Supported across `managed`, default extension `/ops`, extension legacy `/cdp`, and direct `cdpConnect` sessions.

### Cookie list

```bash
npx opendevbrowser cookie-list \
  --session-id <session-id>

npx opendevbrowser cookie-list \
  --session-id <session-id> \
  --url https://example.com \
  --url https://shop.example.com \
  --request-id req-cookie-list-001
```

Notes:
- `--url` is optional and repeatable; each value is normalized and deduplicated.
- Supports `managed`, default extension `/ops`, extension legacy `/cdp`, and direct `cdpConnect` sessions.

### Macro resolve

```bash
npx opendevbrowser macro-resolve --expression '@web.search("openai")'
npx opendevbrowser macro-resolve --expression '@social.post("x", "ship it")' --default-provider social/x --include-catalog
npx opendevbrowser macro-resolve --expression '@web.search("opendevbrowser")' --execute --output-format json
npx opendevbrowser macro-resolve --expression '@media.search("youtube transcript parity", "youtube", 5)' --execute --timeout-ms 120000 --output-format json
npx opendevbrowser macro-resolve --expression '@community.search("browser automation failures", 4)' --execute --browser-mode extension --use-cookies --cookie-policy required --challenge-automation-mode browser_with_helper --output-format json
```

Notes:
- Default mode is resolve-only (returns the resolved action/provenance payload).
- `--execute` runs the resolved provider action and returns additive execution metadata (`meta.tier.selected`, `meta.tier.reasonCode`, `meta.provenance.provider`, `meta.provenance.retrievalPath`, `meta.provenance.retrievedAt`).
- Resolve-only and execute responses now both emit `followthroughSummary`, `suggestedNextAction`, and `suggestedSteps` so the next rerun command stays explicit even when execution blocks.
- `--timeout-ms` sets client-side daemon transport timeout for slow `--execute` runs.
- `--browser-mode` is accepted for `--execute` runs and maps provider recovery to the same `auto|extension|managed` modes as workflow commands.
- `--challenge-automation-mode` is accepted for `--execute` runs and maps to `challengeAutomationMode` with the same `run > session > config` precedence as workflow commands.
- `opendevbrowser --help` includes this timeout flag in the global flag inventory.

### Blocker contract (v2)

Compatibility rule (v2):
- Blocker fields are additive-only and live under `meta.blocker` (or `execution.meta.blocker` for `macro-resolve --execute`).
- Existing success/error fields and codes remain unchanged.
- Consumers should treat missing blocker fields as backward-compatible `no blocker metadata`.

Canonical placement:
- `goto`: `data.meta.blockerState` + optional `data.meta.blocker`.
- `wait`: `data.meta.blockerState` + optional `data.meta.blocker`.
- `debug-trace-snapshot`: `data.meta.blockerState` + optional `data.meta.blocker` + optional `data.meta.blockerArtifacts`.
- `macro-resolve --execute`: `data.execution.meta.ok` + optional `data.execution.meta.blocker`.
- `status`: `data.meta.blockerState` + optional `data.meta.blockerResolution` (`resolved | unresolved | deferred`).

Canonical examples:

```json
{
  "command": "goto",
  "success": true,
  "data": {
    "finalUrl": "https://example.com",
    "status": 200,
    "timingMs": 412,
    "meta": {
      "blockerState": "clear"
    }
  }
}
```

```json
{
  "command": "goto",
  "success": true,
  "data": {
    "finalUrl": "https://x.com/i/flow/login",
    "status": 200,
    "timingMs": 588,
    "meta": {
      "blockerState": "active",
      "blocker": {
        "schemaVersion": "1.0",
        "type": "auth_required",
        "source": "navigation",
        "confidence": 0.97,
        "retryable": false
      }
    }
  }
}
```

```json
{
  "command": "wait",
  "success": true,
  "data": {
    "timingMs": 221,
    "meta": {
      "blockerState": "clear"
    }
  }
}
```

```json
{
  "command": "status",
  "success": true,
  "data": {
    "mode": "managed",
    "activeTargetId": "target-1",
    "url": "https://x.com/i/flow/login",
    "title": "Log in to X / X",
    "meta": {
      "blockerState": "active",
      "blockerResolution": {
        "status": "unresolved",
        "reason": "verification_timeout",
        "updatedAt": "2026-02-15T14:18:28.000Z"
      }
    }
  }
}
```

```json
{
  "command": "wait",
  "success": true,
  "data": {
    "timingMs": 1470,
    "meta": {
      "blockerState": "active",
      "blocker": {
        "schemaVersion": "1.0",
        "type": "anti_bot_challenge",
        "source": "navigation",
        "confidence": 0.96,
        "retryable": false
      }
    }
  }
}
```

```json
{
  "command": "debug-trace-snapshot",
  "success": true,
  "data": {
    "requestId": "req-debug-001",
    "meta": {
      "blockerState": "clear"
    }
  }
}
```

```json
{
  "command": "debug-trace-snapshot",
  "success": true,
  "data": {
    "requestId": "req-debug-002",
    "meta": {
      "blockerState": "active",
      "blocker": {
        "schemaVersion": "1.0",
        "type": "anti_bot_challenge",
        "source": "network",
        "confidence": 0.96,
        "retryable": false
      },
      "blockerArtifacts": {
        "schemaVersion": "1.0",
        "hosts": ["www.recaptcha.net", "challenges.cloudflare.com"]
      }
    }
  }
}
```

```json
{
  "command": "macro-resolve --execute",
  "success": true,
  "data": {
    "runtime": "macros",
    "execution": {
      "meta": {
        "ok": true,
        "partial": false
      }
    }
  }
}
```

```json
{
  "command": "macro-resolve --execute",
  "success": true,
  "data": {
    "runtime": "macros",
    "execution": {
      "meta": {
        "ok": false,
        "partial": true,
        "blocker": {
          "schemaVersion": "1.0",
          "type": "env_limited",
          "source": "macro_execution",
          "confidence": 0.9,
          "retryable": true
        }
      }
    }
  }
}
```

### Design Canvas

Use `canvas` to call the typed `canvas.*` surface through the daemon. The normal sequence is:
`canvas.session.open` -> inspect the handshake (`planStatus`, `preflightState`, `generationPlanRequirements.allowedValues`, `generationPlanIssues`, `guidance.recommendedNextCommands`, and `guidance.nextStepGuidance` when present) -> `canvas.plan.set` -> if accepted, follow returned guidance into `canvas.document.patch` (including `governance.update` blocks) -> `canvas.preview.render` -> `canvas.feedback.poll` -> `canvas.document.save` or `canvas.document.export`.
If `canvas.plan.set` fails with `generation_plan_invalid`, inspect the returned `details.missingFields`, `details.issues`, `guidance.paramsExamples`, `guidance.fieldExamples`, `guidance.validationChecks`, and `guidance.doNotProceedIf`, then repair and resubmit `canvas.plan.set`. `canvas.plan.get` and `canvas.capabilities.get` are diagnostic on that failure path, not required success-path checkpoints.
Additional same-session clients use `canvas.session.attach` with `attachMode=observer` or `attachMode=lease_reclaim`.
Workspace orchestration is additive above child sessions: create or attach normal child sessions first, then use `canvas.workspace.open`, `canvas.workspace.status`, `canvas.workspace.child.add`, `canvas.workspace.child.execute`, `canvas.workspace.child.close`, and `canvas.workspace.close` to coordinate child refs without duplicating document contents. Child commands still execute through the existing child `CanvasManager` session, lease, document store, preview, feedback, and code-sync semantics.
Unless `params.repoRoot` is provided explicitly, the CLI injects the caller cwd as the canvas session repo root. Relative `canvas.document.save`, `canvas.document.export`, `canvas.code.*`, and workspace manifests resolve against that session root even when the daemon is launchd-owned or started from another working directory.

```bash
# Open a canvas session bound to an existing browser session
npx opendevbrowser canvas --command canvas.session.open \
  --params '{"browserSessionId":"<session-id>","documentId":"landing-page","mode":"dual-track"}' \
  --output-format json

# Submit a generation plan (required before patching)
npx opendevbrowser canvas --command canvas.plan.set --params-file ./canvas-plan.json --output-format json

# Repair an invalid or incomplete generation plan by following returned paramsExamples
npx opendevbrowser canvas --command canvas.plan.set --params-file ./canvas-plan.repaired.json --output-format json

# Apply a patch batch against a specific revision, including governance blocks required before save
npx opendevbrowser canvas --command canvas.document.patch \
  --params '{"canvasSessionId":"<canvas-session-id>","leaseId":"<lease-id>","baseRevision":1,"patches":[{"op":"governance.update","block":"intent","changes":{"summary":"Marketing landing page refresh"}},{"op":"governance.update","block":"designLanguage","changes":{"profile":"clean-room"}},{"op":"page.create","page":{"id":"page_home","rootNodeId":null,"name":"Home","path":"/","description":"Marketing landing page"}}]}' \
  --output-format json

# Save the canonical design document back into the repo
npx opendevbrowser canvas --command canvas.document.save \
  --params '{"canvasSessionId":"<canvas-session-id>","leaseId":"<lease-id>"}' \
  --output-format json

# Open a workspace over existing child sessions without copying child documents
npx opendevbrowser canvas --command canvas.workspace.open \
  --params '{"workspaceId":"agent-review","children":[{"childId":"hero","canvasSessionId":"<hero-session-id>","role":"primary"},{"childId":"pricing","canvasSessionId":"<pricing-session-id>","role":"worker"}]}' \
  --output-format json

# Add another child session and inspect workspace guardrails
npx opendevbrowser canvas --command canvas.workspace.child.add \
  --params '{"workspaceId":"agent-review","child":{"childId":"footer","canvasSessionId":"<footer-session-id>","role":"worker"}}' \
  --output-format json

npx opendevbrowser canvas --command canvas.workspace.status \
  --params '{"workspaceId":"agent-review"}' \
  --output-format json

# Route a child command through the workspace guardrails
npx opendevbrowser canvas --command canvas.workspace.child.execute \
  --params '{"workspaceId":"agent-review","childId":"hero","command":"canvas.session.status","params":{}}' \
  --output-format json

# Close the coordinator without deleting child sessions
npx opendevbrowser canvas --command canvas.workspace.close \
  --params '{"workspaceId":"agent-review"}' \
  --output-format json
```

`canvas.session.open` returns a handshake with `canvasSessionId`, `leaseId`, governance block states, required generation-plan fields, `generationPlanRequirements.allowedValues`, runtime budgets, warning classes, `generationPlanIssues`, `mutationPolicy.allowedBeforePlan`, and `guidance.recommendedNextCommands`. Repairable Canvas responses can also include `guidance.nextStepGuidance`, `guidance.paramsExamples`, `guidance.fieldExamples`, `guidance.validationChecks`, and `guidance.doNotProceedIf`. Treat `planStatus="missing"` with `preflightState="handshake_read"` as "submit a plan next." Treat `planStatus="invalid"` with `preflightState="plan_invalid"` as "fix the reported plan issues before mutation." A successful `canvas.plan.set` response is authoritative enough to proceed: it already returns accepted state plus next-step guidance, so `canvas.plan.get` is only needed when diagnosing invalid-plan responses or re-reading current state after attach. `canvas.document.patch` is blocked until `canvas.plan.set` succeeds. `canvas.document.save` and `canvas.document.export` return `policy_violation` until all `requiredBeforeSave` governance blocks are present. `canvas.document.import` now imports Figma file URLs, node URLs, or raw file-key inputs into the same lease-governed document surface; it resolves auth from `FIGMA_ACCESS_TOKEN` first and `integrations.figma.accessToken` second, caches image and SVG receipts under `.opendevbrowser/canvas/assets/figma/<fileKey>/`, records provenance in `document.meta.imports[]`, and degrades `variables/local` failures with typed feedback (`scope_denied`, `plan_limited`, `account_limited`, or `variables_unavailable`) instead of opaque fatal errors. `canvas.history.undo` and `canvas.history.redo` are now public lease-governed mutations; they are unavailable before the first accepted-plan edit, require the active lease holder, and invalidate deterministically when document revision drift makes the recorded stack stale. In extension mode, `canvas.tab.open` opens an extension-hosted `canvas.html` infinite-canvas editor that persists full page state in `IndexedDB`, converges same-origin tabs through `BroadcastChannel`, forwards editor-originated patch requests through `/canvas`, exposes pages/layers/properties/history controls plus keyboard shortcuts, and keeps freeform region annotation scoped to the extension-hosted stage. `canvas.feedback.poll` remains the snapshot query for cursor-based audits, and when the plan is still missing or invalid it synthesizes the same preflight blocker agents see from mutation commands. `canvas.feedback.subscribe`, `canvas.feedback.next`, and `canvas.feedback.unsubscribe` now expose the public pull-stream contract. In `stream-json` mode, the CLI emits `initialItems`, then loops on `canvas.feedback.next` until `feedback.complete` or CLI timeout, and finally best-effort calls `canvas.feedback.unsubscribe`. `canvas.inventory.list` now returns the merged reusable inventory surface: document-backed promoted items plus the shipped built-in kit catalog entries. `canvas.inventory.insert` materializes either kind of inventory template back onto the stage as a governed mutation. `canvas.starter.list` exposes the eight shipped built-in starters, and `canvas.starter.apply` now seeds a generation plan when needed, merges built-in kit token collections, installs required kit inventory entries into the live document, and inserts a starter shell with semantic fallback when the requested framework or adapter is unavailable. For starter application, prefer `libraryAdapterId` when selecting or reading the resolved built-in kit adapter; the legacy `adapterId` field remains as a backward-compatible alias and is distinct from code-sync `frameworkAdapterId`. The public `/canvas` surface is now `41` commands. Workspace commands persist refs-only manifests under `.opendevbrowser/canvas-workspace/<workspaceId>/workspace-manifest.json`, record operation latency and bounded memory samples, compute preview budgets (`focused_live`, `pinned_live`, `background_live`, `thumbnail`, `paused`, `degraded`), and block duplicate child ids, sessions, leases, document ids, repo paths, and code-sync binding ids. `canvas.workspace.close` preserves child sessions; use `canvas.workspace.child.close` or `canvas.session.close` when a child should actually close. The tool wrapper stays thin and uses the same public `opendevbrowser_canvas` commands through repeated calls.
`canvas.code.bind`, `canvas.code.unbind`, `canvas.code.pull`, `canvas.code.push`, `canvas.code.status`, and `canvas.code.resolve` add framework-adapter-backed code sync on top of the same session. When routed through a workspace, duplicate repo paths and duplicate code-sync binding ids are rejected before the child route mutates state, so child A cannot silently claim child B's source binding. Built-in lanes currently ship for `builtin:react-tsx-v2`, `builtin:html-static-v1`, `builtin:custom-elements-v1`, `builtin:vue-sfc-v1`, and `builtin:svelte-sfc-v1`; legacy `tsx-react-v1` bindings and manifests migrate on load to `builtin:react-tsx-v2` instead of failing ambiguously. Bound source manifests are stored under `.opendevbrowser/canvas/code-sync/<documentId>/<bindingId>.json`; preview targets default to projected `canvas_html` and only attempt `bound_app_runtime` reconciliation when the binding opts in and runtime bridge preflight succeeds. `canvas.code.status` returns `frameworkAdapterId`, `frameworkId`, `sourceFamily`, declared/granted capabilities, explicit denial entries, and deterministic `reasonCode` values such as `framework_migrated`, `manifest_migrated`, `plugin_not_found`, and `plugin_load_failed`. Repo-local BYO adapter plugins are discovered from workspace `package.json`, `.opendevbrowser/canvas/adapters.json`, and explicit local config declarations only; declaration-level `capabilityOverrides` narrow plugin capabilities rather than widening them. See `docs/CANVAS_ADAPTER_PLUGIN_CONTRACT.md` for the plugin manifest and trust model, and `node scripts/canvas-competitive-validation.mjs --out <report.json>` for the grouped canvas validator.

### RPC (power-user, internal)

Execute any daemon command directly. This bypasses the stable CLI command surface, is intentionally unsafe/internal, and requires `--unsafe-internal`.

```bash
# Minimal call (empty params object)
npx opendevbrowser rpc --unsafe-internal --name relay.status --output-format json

# With inline JSON params
npx opendevbrowser rpc --unsafe-internal --name nav.goto \
  --params '{"sessionId":"<session-id>","url":"https://example.com","waitUntil":"load","timeoutMs":30000}' \
  --timeout-ms 45000 --output-format json

# With params from file
npx opendevbrowser rpc --unsafe-internal --name session.status --params-file ./rpc-params.json --output-format json
```

Notes:
- Params must be a JSON object.
- Use `--output-format json` for machine-readable responses.
- `rpc` is CLI-only by design and is not part of the stable tool parity surface.
- `rpc` command names/params are internal and may change without compatibility guarantees.
- A bad `rpc` call can close sessions, navigate logged-in tabs, or trigger unintended side effects.
- Prefer stable commands (`goto`, `snapshot`, `click`, `type`, and related tool equivalents) whenever possible.
- Use `rpc` only when necessary, validate params carefully, and test in a disposable session first.

---

## Navigation commands (daemon required)

### Goto

```bash
npx opendevbrowser goto --session-id <session-id> --url https://example.com
npx opendevbrowser goto --session-id <session-id> --url https://example.com --wait-until load --timeout-ms 30000
```

### Wait

```bash
npx opendevbrowser wait --session-id <session-id> --until load
npx opendevbrowser wait --session-id <session-id> --ref r12 --state visible --timeout-ms 15000
```

### Snapshot

```bash
npx opendevbrowser snapshot --session-id <session-id>
npx opendevbrowser snapshot --session-id <session-id> --max-chars 16000 --cursor <cursor>
```

### Review

Use `review` as the explicit `snapshot -> review -> action` step when you want the active target summary plus a fresh actionables capture in one response.

```bash
npx opendevbrowser review --session-id <session-id>
npx opendevbrowser review --session-id <session-id> --target-id <target-id> --max-chars 16000 --cursor <cursor>
```

### Review desktop

Use `review-desktop` when you want the browser-owned review payload plus read-only desktop observation evidence in one correlated response.

```bash
npx opendevbrowser review-desktop --session-id <session-id> --reason "compare browser review with visible desktop state"
npx opendevbrowser review-desktop --session-id <session-id> --target-id <target-id> --reason "audit active target" --max-chars 16000 --cursor <cursor>
```

Notes:
- `review-desktop` keeps authority in the review family and augments it with public desktop observation evidence.
- Use `--reason` when you want explicit audit context recorded alongside the correlated desktop evidence.

---

## Interaction commands (daemon required)

### Click

```bash
npx opendevbrowser click --session-id <session-id> --ref r12 [--timeout-ms <ms>]
```

Notes:
- `--timeout-ms` sets the client-side daemon timeout for the click request.
- Without `--timeout-ms`, `click` uses a 60s client-side daemon timeout so blocking browser dialogs can be inspected and handled without the opener expiring immediately.

### Hover

```bash
npx opendevbrowser hover --session-id <session-id> --ref r12
```

### Press

```bash
npx opendevbrowser press --session-id <session-id> --key Enter
npx opendevbrowser press --session-id <session-id> --key Enter --ref r12
```

### Check / Uncheck

```bash
npx opendevbrowser check --session-id <session-id> --ref r12
npx opendevbrowser uncheck --session-id <session-id> --ref r12
```

### Type

```bash
npx opendevbrowser type --session-id <session-id> --ref r12 --text "hello"
npx opendevbrowser type --session-id <session-id> --ref r12 --text "hello" --clear --submit
```

### Select

```bash
npx opendevbrowser select --session-id <session-id> --ref r12 --values value1,value2
```

### Scroll

```bash
npx opendevbrowser scroll --session-id <session-id> --dy 500
npx opendevbrowser scroll --session-id <session-id> --ref r12 --dy 300
```

### Scroll into view

```bash
npx opendevbrowser scroll-into-view --session-id <session-id> --ref r12
```

### Upload

```bash
npx opendevbrowser upload --session-id <session-id> --ref r12 --files ./avatar.png
npx opendevbrowser upload --session-id <session-id> --target-id <target-id> --ref r12 --files ./front.png,./back.png
```

Notes:
- `--files` accepts a comma-separated list of host file paths.
- Upload resolves the target from the existing ref model and reports `mode` as `direct_input` or `file_chooser`.

---

## Annotation (direct + relay)

Annotations are available through both the CLI command and the `opendevbrowser_annotate` tool. The default transport (`auto`)
uses direct CDP when possible and falls back to relay in extension sessions. New captures and stored payloads use Annotation V2
compact handoff by default: `schemaVersion: 2`, `compact.screenshotMode="none"`, redaction metadata, selector bundles,
canvas identity when available, and screenshot-free shared inbox storage. `annotate --stored` resolves the shared repo-local
agent inbox first, then the extension-local stored payload fallback. See `docs/ANNOTATE.md` for setup and details.

```bash
npx opendevbrowser annotate --session-id <session-id>

# Force direct annotate on a target
npx opendevbrowser annotate --session-id <session-id> --transport direct --target-id <target-id>

# Force relay annotate on a specific tab
npx opendevbrowser annotate --session-id <session-id> --transport relay --tab-id 123

# With URL + context + debug metadata
npx opendevbrowser annotate --session-id <session-id> --url https://example.com \
  --screenshot-mode visible --context "Review the hero layout" --timeout-ms 90000 --debug

# Return the last stored annotation payload
npx opendevbrowser annotate --session-id <session-id> --stored

# Prefer the extension-local in-memory stored payload with screenshots when still available
npx opendevbrowser annotate --session-id <session-id> --stored --include-screenshots
```

---

## Target commands (daemon required)

### Targets list

```bash
npx opendevbrowser targets-list --session-id <session-id>
npx opendevbrowser targets-list --session-id <session-id> --include-urls
```

### Target use

```bash
npx opendevbrowser target-use --session-id <session-id> --target-id <target-id>
```

### Target new

```bash
npx opendevbrowser target-new --session-id <session-id> --url https://example.com
```

### Target close

```bash
npx opendevbrowser target-close --session-id <session-id> --target-id <target-id>
```

---

## Page commands (daemon required)

### Page open

```bash
npx opendevbrowser page --session-id <session-id> --name main
npx opendevbrowser page --session-id <session-id> --name main --url https://example.com
```

### Pages list

```bash
npx opendevbrowser pages --session-id <session-id>
```

### Page close

```bash
npx opendevbrowser page-close --session-id <session-id> --name main
```

---

## DOM commands (daemon required)

### DOM HTML

```bash
npx opendevbrowser dom-html --session-id <session-id> --ref r12 --max-chars 8000
```

### DOM Text

```bash
npx opendevbrowser dom-text --session-id <session-id> --ref r12 --max-chars 8000
```

### DOM Attribute

```bash
npx opendevbrowser dom-attr --session-id <session-id> --ref r12 --attr aria-label
```

### DOM Value

```bash
npx opendevbrowser dom-value --session-id <session-id> --ref r12
```

### DOM State Checks

```bash
npx opendevbrowser dom-visible --session-id <session-id> --ref r12
npx opendevbrowser dom-enabled --session-id <session-id> --ref r12
npx opendevbrowser dom-checked --session-id <session-id> --ref r12
```

---

## Export commands (daemon required)

### Clone page

```bash
npx opendevbrowser clone-page --session-id <session-id>
```

### Clone component

```bash
npx opendevbrowser clone-component --session-id <session-id> --ref r12
```

---

## Devtools commands (daemon required)

### Performance metrics

```bash
npx opendevbrowser perf --session-id <session-id>
```

### Screenshot

```bash
npx opendevbrowser screenshot --session-id <session-id>
npx opendevbrowser screenshot --session-id <session-id> --path ./capture.png
npx opendevbrowser screenshot --session-id <session-id> --ref r12
npx opendevbrowser screenshot --session-id <session-id> --full-page
npx opendevbrowser screenshot --session-id <session-id> --path ./capture.png --timeout-ms 60000
```

Notes:
- `--ref` and `--full-page` are mutually exclusive.
- `--timeout-ms` sets client-side daemon timeout for screenshot capture.
- When `--path` is omitted, screenshots save to `.opendevbrowser/screenshot/<uuid>/capture.png` and JSON output includes `path` plus `artifact_path`.
- Explicit `--path` remains caller-controlled and does not create the omitted-output screenshot artifact directory.
- Default visible capture may still report the existing viewport-only fallback warning when extension capture has to degrade, but ref and full-page requests do not silently reuse that fallback.

### Screencast start

```bash
npx opendevbrowser screencast-start --session-id <session-id>
npx opendevbrowser screencast-start \
  --session-id <session-id> \
  --target-id <target-id> \
  --output-dir ./artifacts/replay \
  --interval-ms 1000 \
  --max-frames 120
```

Notes:
- `screencast-start` is a manager-owned browser replay lane layered on the existing screenshot primitive.
- The recorder writes `replay.json`, `replay.html`, `frames/`, and `preview.png` into the chosen output directory.
- When `--output-dir` is omitted, screencasts save those replay files under `.opendevbrowser/screencast/<uuid>` and JSON output includes `artifact_path`.
- Explicit `--output-dir` remains caller-controlled and keeps replay files inside that directory.
- `--interval-ms` defaults to `1000` and must be at least `250`.
- `--max-frames` defaults to `300`.

### Screencast stop

```bash
npx opendevbrowser screencast-stop --session-id <session-id> --screencast-id <screencast-id>
```

Notes:
- `--session-id` and `--screencast-id` are both required.
- `--screencast-id` must match the id returned by `screencast-start` for that same session.
- Stop returns the final artifact metadata, including replay paths and the terminal `endedReason`.

### Dialog

```bash
npx opendevbrowser dialog --session-id <session-id>
npx opendevbrowser dialog --session-id <session-id> --action dismiss
npx opendevbrowser dialog --session-id <session-id> --action accept --prompt-text "Ship it"
```

Notes:
- `--action` supports `status`, `accept`, and `dismiss`.
- `--prompt-text` is only valid when `--action accept` is used for a prompt dialog.
- `--timeout-ms` sets the client-side daemon timeout for the dialog request.
- Without `--timeout-ms`, `dialog` uses a 30s client-side daemon timeout.

### Console poll

```bash
npx opendevbrowser console-poll --session-id <session-id> --since-seq 0 --max 50
```

### Network poll

```bash
npx opendevbrowser network-poll --session-id <session-id> --since-seq 0 --max 50
```

### Debug trace snapshot

```bash
npx opendevbrowser debug-trace-snapshot --session-id <session-id>
npx opendevbrowser debug-trace-snapshot \
  --session-id <session-id> \
  --since-console-seq 100 \
  --since-network-seq 80 \
  --since-exception-seq 10 \
  --max 200 \
  --request-id req-debug-001
```

### Session inspector

```bash
npx opendevbrowser session-inspector --session-id <session-id>
npx opendevbrowser session-inspector \
  --session-id <session-id> \
  --include-urls \
  --since-console-seq 100 \
  --since-network-seq 80 \
  --since-exception-seq 10 \
  --max 50 \
  --request-id req-session-inspector-001
```

Notes:
- Returns session status, relay health, target summary, proof artifact metadata, `healthState`, and a suggested next action in one payload.
- `--include-urls` keeps target URLs in the target summary. Omit it to use the default runtime behavior.
- `--since-console-seq`, `--since-network-seq`, `--since-exception-seq`, and `--max` mirror the trace cursors used by `debug-trace-snapshot`.

### Session inspector plan

```bash
npx opendevbrowser session-inspector-plan --session-id <session-id>
npx opendevbrowser session-inspector-plan --session-id <session-id> --target-id <target-id> --challenge-automation-mode browser_with_helper --timeout-ms 30000
```

Notes:
- Returns browser-scoped computer-use policy, eligibility, stand-down reasons, yield state, and safe suggested steps without running a provider workflow.
- `--challenge-automation-mode` lets you inspect the effective mode you want to compare against current session or config defaults.

### Session inspector audit

```bash
npx opendevbrowser session-inspector-audit --session-id <session-id> --reason "capture correlated operator audit"
npx opendevbrowser session-inspector-audit --session-id <session-id> --target-id <target-id> --reason "trace challenge state" --include-urls --request-id req-session-audit-001 --challenge-automation-mode browser_with_helper
```

Notes:
- Returns a correlated audit bundle across desktop evidence, browser review, session status, and browser-scoped policy state.
- `--reason`, `--request-id`, trace cursors, and `--include-urls` mirror the session-inspector and review audit lanes in one surface.

---

## Desktop observation commands (daemon required)

These commands expose the sibling `DesktopRuntimeLike` observation plane directly. They are read-only, return audit metadata on both success and failure, and are gated by separate `desktop.*` config plus local OS permissions.

### Desktop status

```bash
npx opendevbrowser desktop-status
```

### Desktop windows

```bash
npx opendevbrowser desktop-windows --reason "inspect visible windows before capture"
```

### Desktop active window

```bash
npx opendevbrowser desktop-active-window --reason "inspect current foreground window"
```

### Desktop capture desktop

```bash
npx opendevbrowser desktop-capture-desktop --reason "capture current desktop surface"
```

### Desktop capture window

```bash
npx opendevbrowser desktop-capture-window --window-id <window-id> --reason "capture a specific window"
```

### Desktop accessibility snapshot

```bash
npx opendevbrowser desktop-accessibility-snapshot --reason "capture accessibility tree"
npx opendevbrowser desktop-accessibility-snapshot --window-id <window-id> --reason "capture one window accessibility tree"
```

Notes:
- On supported macOS hosts, availability, window inventory, and accessibility probes require the local `swift` command. If `desktop-status` returns `desktop_unsupported` on macOS, install Xcode or a Swift toolchain and retry.
- Desktop screenshots use macOS `screencapture`.
- `desktop-status` reports availability, permissions, capabilities, and the configured audit artifacts directory.
- `desktop-windows` and `desktop-active-window` accept optional `--reason` values for audit context.
- `desktop-capture-desktop`, `desktop-capture-window`, and `desktop-accessibility-snapshot` require `--reason`.
- `desktop-capture-window` requires `--window-id`.
- `desktop-accessibility-snapshot` accepts an optional `--window-id`.
- This plane is public and observe-only. It is not a desktop agent and does not create a desktop `/ops` family.

---

## Flags reference

### Global flags

| Flag | Short | Description |
|------|-------|-------------|
| `--global` | `-g` | Install to `~/.config/opencode/opencode.json` |
| `--local` | `-l` | Install to `./opencode.json` |
| `--update` | `-u` | Repair OpenCode package caches and managed skill packs |
| `--uninstall` | | Remove plugin from config and clean managed skills |
| `--with-config` | | Also create `opendevbrowser.jsonc` |
| `--full` | `-f` | Create config and pre-extract extension assets |
| `--no-prompt` | | Skip prompts, use defaults |
| `--no-interactive` | | Alias of `--no-prompt` |
| `--quiet` | | Suppress output |
| `--output-format` | | `text`, `json`, or `stream-json` |
| `--transport` | | Transport selector for transport-aware commands (`status`: `relay|native`; `annotate`: `auto|direct|relay`) |
| `--daemon` | | Daemon status selector for `status` |
| `--skills-global` | | Install skills to global OpenCode/Codex-through-Agents/ClaudeCode/AmpCLI/Agents directories |
| `--skills-local` | | Install skills to project-local OpenCode/Codex-through-Agents/ClaudeCode/AmpCLI/Agents directories |
| `--no-skills` | | Skip installing bundled skills |
| `--help` | `-h` | Show usage information |
| `--version` | `-v` | Show version number |

### Serve flags

| Flag | Description |
|------|-------------|
| `--port` | Override daemon port |
| `--token` | Override daemon token |
| `--stop` | Stop the daemon |

### Command-specific flags

**Session + connection**

| Flag | Used by | Description |
|------|---------|-------------|
| `--session-id` | most daemon commands | Active session id from `launch`/`connect` |
| `--close-browser` | `disconnect` | Close managed browser on disconnect |
| `--ws-endpoint` | `connect` | Remote debugging websocket URL |
| `--host` | `connect` | Host for CDP connection (`--cdp-port` required) |
| `--cdp-port` | `connect` | CDP port for host-based connect |
| `--daemon` | `status` | Force daemon status mode (mutually exclusive with `--session-id`) |
| `--no-extension` | `launch` | Force managed mode (ignore extension) |
| `--extension-only` | `launch` | Fail if extension not connected |
| `--extension-legacy` | `launch`, `connect` | Use legacy extension relay (`/cdp`) |
| `--wait-for-extension` | `launch` | Wait for extension handshake |
| `--wait-timeout-ms` | `launch` | Max wait for extension handshake |
| `--profile` | `launch`, `run`, `connect`, `cdp-profile` | Named managed profile for launch/run, or registry-backed explicit CDP profile for connect and cdp-profile |
| `--google-auth-intent` | `launch`, `connect` | `user-owned` requires extension `/ops` for user-owned Google OAuth continuity and fails closed for managed, headless, legacy `/cdp`, and direct CDP |
| `--disable-system-cookie-bootstrap` | `launch`, `connect` | Skip managed or direct `cdpConnect` readable system cookie bootstrap for this run |
| `--allow-google-cookie-bootstrap` | `launch`, `connect` | Diagnostic override to explicitly include Google-sensitive cookies in managed or direct `cdpConnect` bootstrap |
| `--cookies-file` | `cookie-import` | Path to JSON file containing cookie objects; preferred for real cookies |
| `--cookies` | `cookie-import` | Inline JSON array for non-sensitive synthetic tests only because shell history and process arguments can expose values |
| `--strict` | `cookie-import` | Reject on invalid cookie entries (`true`/`false`) |
| `--request-id` | `cookie-import`, `cookie-list`, `debug-trace-snapshot`, `session-inspector`, `session-inspector-audit` | Optional request correlation id |
| `--expression` | `macro-resolve` | Macro expression to resolve |
| `--default-provider` | `macro-resolve` | Provider fallback for shorthand macros |
| `--include-catalog` | `macro-resolve` | Include macro catalog in response |
| `--execute` | `macro-resolve` | Execute the resolved provider action and include additive `meta.*` fields |
| `--timeout-ms` | `macro-resolve` | Client-side daemon call timeout in ms |
| `--browser-mode` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, `macro-resolve --execute` | Provider browser transport mode (`auto|extension|managed`); `extension` reuses relay-backed browser state, `managed` runs a deterministic managed browser |
| `--use-cookies` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, `macro-resolve --execute` | Enable/disable provider cookie injection for the run (`true|false`; bare flag means `true`) |
| `--profile` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest` | Named managed profile for provider workflow fallback; profile names are launch preference until trusted provenance verifies continuity |
| `--challenge-automation-mode` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, `macro-resolve --execute`, `status-capabilities`, `session-inspector-plan`, `session-inspector-audit` | Per-run or inspection challenge automation override stored as `challengeAutomationMode` (`off|browser|browser_with_helper`) with `run > session > config` precedence |
| `--cookie-policy-override` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, `macro-resolve --execute` | Per-run provider cookie policy override (`off|auto|required`) |
| `--cookie-policy` | `research run`, `shopping run`, `product-video run`, `inspiredesign run`, `inspiredesign harvest`, `macro-resolve --execute` | Alias of `--cookie-policy-override` |

**Browser launch (launch/run)**

| Flag | Used by | Description |
|------|---------|-------------|
| `--headless` | `launch`, `run` | Run managed Chrome headless |
| `--profile` | `launch`, `run` | Profile directory name |
| `--persist-profile` | `launch`, `run` | Keep profile directory after run |
| `--chrome-path` | `launch`, `run` | Explicit Chrome executable path |
| `--start-url` | `launch`, `run` | Initial URL for the session |
| `--flag` | `launch`, `run` | Additional Chrome flag (repeatable) |
| `--script` | `run` | JSON script path |

**Navigation + waiting**

| Flag | Used by | Description |
|------|---------|-------------|
| `--url` | `goto`, `page`, `target-new`, `cookie-list` | URL to navigate/open or filter cookie listing |
| `--wait-until` | `goto` | Load state (`load`, `domcontentloaded`, etc.) |
| `--timeout-ms` | `goto`, `wait`, `review`, `review-desktop`, `status-capabilities`, `session-inspector-plan`, `session-inspector-audit` | Timeout in ms |
| `--ref` | `wait` | Element ref to wait for |
| `--state` | `wait` | Element state (e.g. `visible`) |
| `--until` | `wait` | Page load state |
| `--mode` | `snapshot` | Snapshot mode (`outline` or `actionables`) |
| `--target-id` | `review`, `review-desktop`, `status-capabilities`, `session-inspector-plan`, `session-inspector-audit` | Optional target override for review or inspection payloads |
| `--max-chars` | `snapshot`, `review`, `review-desktop`, `session-inspector-audit`, `dom-*` | Max characters returned |
| `--cursor` | `snapshot`, `review`, `review-desktop`, `session-inspector-audit` | Snapshot pagination cursor |

**Annotation**

| Flag | Used by | Description |
|------|---------|-------------|
| `--transport` | `annotate` | `auto` (default), `direct`, or `relay` |
| `--target-id` | `annotate` | Target id for direct annotate |
| `--tab-id` | `annotate` | Chrome tab id for relay annotate |
| `--screenshot-mode` | `annotate` | `visible` (default), `full`, or `none` |
| `--include-screenshots` | `annotate` | When used with `--stored`, prefer the in-memory payload that still includes screenshots if available |
| `--context` | `annotate` | Optional context text pre-filled in the UI |
| `--debug` | `annotate` | Include debug metadata in the payload |
| `--stored` | `annotate` | Return the last stored annotation payload for the session |
| `--timeout-ms` | `annotate` | Annotation timeout in ms |

**Canvas**

| Flag | Used by | Description |
|------|---------|-------------|
| `--command` | `canvas` | `canvas.*` command name (for example `canvas.session.open`) |
| `--params` | `canvas` | Inline JSON object command params |
| `--params-file` | `canvas` | Path to JSON object command params |
| `--timeout-ms` | `canvas` | Client-side daemon call timeout in ms |

**RPC (internal)**

| Flag | Used by | Description |
|------|---------|-------------|
| `--unsafe-internal` | `rpc` | Required opt-in acknowledging unsafe/internal RPC execution (power-user only) |
| `--name` | `rpc` | Daemon command name (for example `relay.status`) |
| `--params` | `rpc` | Inline JSON object command params |
| `--params-file` | `rpc` | Path to JSON object params file |
| `--timeout-ms` | `rpc` | Client-side daemon call timeout in ms |

**Interaction**

| Flag | Used by | Description |
|------|---------|-------------|
| `--ref` | element commands | Element ref from `snapshot` |
| `--timeout-ms` | `click` | Client-side daemon call timeout in ms; defaults to 60s |
| `--text` | `type` | Text to type |
| `--clear` | `type` | Clear input before typing |
| `--submit` | `type` | Submit after typing |
| `--values` | `select` | Comma-separated values |
| `--files` | `upload` | Comma-separated file paths for upload |
| `--dy` | `scroll` | Scroll delta on Y axis |
| `--key` | `press` | Keyboard key name (e.g. `Enter`) |

**Targets + pages**

| Flag | Used by | Description |
|------|---------|-------------|
| `--target-id` | `target-use`, `target-close` | Target id from `targets-list` |
| `--include-urls` | `targets-list`, `session-inspector`, `session-inspector-audit` | Include URLs in target or session-inspector target-list output |
| `--name` | `page`, `page-close` | Named page identifier |

**Devtools**

| Flag | Used by | Description |
|------|---------|-------------|
| `--attr` | `dom-attr` | Attribute name to read |
| `--path` | `screenshot` | Output file path |
| `--ref` | `screenshot` | Capture an element screenshot by ref |
| `--full-page` | `screenshot` | Capture the full scrollable page |
| `--target-id` | `screencast-start` | Optional target override for screencast capture |
| `--output-dir` | `screencast-start` | Directory where screencast replay artifacts are written |
| `--interval-ms` | `screencast-start` | Frame capture interval in ms (minimum `250`) |
| `--max-frames` | `screencast-start` | Maximum frame count before auto-stop |
| `--screencast-id` | `screencast-stop` | Screencast id returned by `screencast-start` for the same session |
| `--action` | `dialog` | Dialog action: `status`, `accept`, or `dismiss` |
| `--prompt-text` | `dialog` | Prompt text to submit when accepting a prompt dialog |
| `--timeout-ms` | `screenshot`, `screencast-start`, `screencast-stop` | Explicit client-side daemon call timeout in ms |
| `--timeout-ms` | `dialog` | Client-side daemon call timeout in ms; defaults to 30s |
| `--since-seq` | `console-poll`, `network-poll` | Start sequence number |
| `--since-console-seq` | `debug-trace-snapshot`, `session-inspector`, `session-inspector-audit` | Resume cursor for console channel |
| `--since-network-seq` | `debug-trace-snapshot`, `session-inspector`, `session-inspector-audit` | Resume cursor for network channel |
| `--since-exception-seq` | `debug-trace-snapshot`, `session-inspector`, `session-inspector-audit` | Resume cursor for exception channel |
| `--max` | `console-poll`, `network-poll`, `debug-trace-snapshot`, `session-inspector`, `session-inspector-audit` | Max events to return per channel |

**Desktop observation**

| Flag | Used by | Description |
|------|---------|-------------|
| `--reason` | `review-desktop`, `session-inspector-audit`, `desktop-windows`, `desktop-active-window`, `desktop-capture-desktop`, `desktop-capture-window`, `desktop-accessibility-snapshot` | Audit reason recorded with review, audit, or desktop observation results |
| `--window-id` | `desktop-capture-window`, `desktop-accessibility-snapshot` | Window id for direct window capture or accessibility requests |
| `--timeout-ms` | all `desktop-*` commands | Client-side daemon call timeout in ms |

---

## CLI smoke test

Run the automated CLI coverage script (managed mode):

```bash
npm run build
node scripts/cli-smoke-test.mjs
```

The script uses temporary config/cache directories and exercises all CLI commands, including the new interaction and DOM state checks.
Validate extension mode separately with `launch` + `disconnect` while the extension is connected.

## Direct live regression harness

Run the direct scenario pack instead of the old broad matrix:

```bash
npm run build
node scripts/live-regression-direct.mjs
```

What it runs:
- `scripts/cli-smoke-test.mjs`
- `scripts/canvas-live-workflow.mjs --surface managed-headless`
- `scripts/canvas-live-workflow.mjs --surface managed-headed`
- `scripts/canvas-live-workflow.mjs --surface extension`
- `scripts/canvas-live-workflow.mjs --surface cdp`
- `scripts/annotate-live-probe.mjs --transport relay`
- `scripts/annotate-live-probe.mjs --transport direct`

Strict release gate mode:

```bash
node scripts/live-regression-direct.mjs --release-gate --out artifacts/release/vX.Y.Z/live-regression-direct.json
```

Behavior:
- Runs each scenario as an explicit child script with its own artifact instead of a nested matrix.
- Prints per-step progress so long runs do not idle silently.
- Fails on any direct scenario failure in `--release-gate` mode.
- Records `infra.daemon_status` as an initial preflight snapshot before child scenarios run. If a later child script repairs native messaging state, rerun `opendevbrowser status --daemon --output-format json` after the pack for final daemon/native truth.
- Uses temporary managed profiles for the managed canvas surfaces and the direct annotate probe so persisted-profile locks do not contaminate release runs.
- Waits for `/ops` ownership to drain before the legacy `/cdp` scenario so extension-backed CDP attach does not race a prior extension canvas run.
- Treats manual annotation timeout boundaries as `skipped` in `--release-gate` mode instead of misreporting them as runtime failures.
- Requires a healthy daemon and a connected extension before running extension or CDP scenarios.

## Direct provider runs

Run provider-by-provider live checks:

```bash
npm run build
node scripts/provider-direct-runs.mjs --out /tmp/odb-provider-direct-runs.json
```

Strict release gate mode:

```bash
npm run build
node scripts/provider-direct-runs.mjs --release-gate --out artifacts/release/vX.Y.Z/provider-direct-runs.json
```

Smoke mode:

```bash
npm run build
node scripts/provider-direct-runs.mjs --smoke --out /tmp/odb-provider-direct-runs-smoke.json
```

What it covers:
- Web/default direct search and fetch probes.
- Community/default direct search probes.
- Social/search and shopping/provider probes through the same direct CLI entrypoints used in real runs.
- Generic timeout outcomes stay visible as `fail`; `env_limited` is reserved for explicit environment/capability boundaries such as auth walls, browser-required shells, or challenge pages.
- Social platform search probes for every registered platform in `src/providers/social/index.ts`.
- Shopping provider runs for every registered shopping provider in `src/providers/shopping/index.ts`.
- Optional social post probes for explicitly requested write-path validation.

Key report fields:
- `data.guidanceReason` and `data.recommendedNextCommand` summarize the first actionable provider follow-up emitted by the workflow or failure envelope.
- Shopping and research workflow payloads keep the canonical structured source at `meta.primaryConstraint.guidance.reason` and `meta.primaryConstraint.guidance.recommendedNextCommands[]`.
- Product-video payloads expose `meta.presentationReadiness`, `meta.productVideoReadiness`, `manifest.readiness.presentation`, `manifest.readiness.productVideo`, `product.presentationReadiness`, `product.productVideoReadiness`, and `presentation-readiness.json`; inspect those gates before treating copy or features as production input.

Key options:
- `--include-auth-gated` includes auth-dependent provider scenarios.
- `--include-high-friction` includes high-friction shopping providers.
- `--include-social-posts` includes social post scenarios.
- `--release-gate` enables auth-gated + high-friction + social-post cases and fails on any non-`pass` status.

Run contract parity and skill-asset gates as part of release checks:

```bash
npm run test -- tests/parity-matrix.test.ts
npm run test -- tests/providers-performance-gate.test.ts
./skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh
```

These commands are release guards, not the live release-proof lane. Use the direct-run harness commands above for release evidence.

Release gate source of truth: `docs/RELEASE_RUNBOOK.md` and `docs/RELEASE_0.0.40_EVIDENCE.md`.
Benchmark fixture manifest: `docs/benchmarks/provider-fixtures.md`.

---

## Extension-only manual test (no OpenCode plugin)

Use this to validate the Chrome extension + relay without starting OpenCode.

1. Ensure the daemon is running: `npx opendevbrowser serve` (manual). `opendevbrowser daemon install` configures auto-start on supported platforms from a stable install location; for this manual test, use `npx opendevbrowser serve` as the explicit start command for the current session.
2. Build and load the extension: `npm run extension:build`, then Chrome → `chrome://extensions` → Developer mode → Load unpacked → `extension/`.
3. Open a normal `http(s)` tab (not `chrome://` or extension pages).
4. Open the extension popup, confirm Auto-connect + Auto-pair are ON, click Connect.
5. Verify the popup shows **Connected** and `npx opendevbrowser status --daemon` reports `extensionConnected: true` and `extensionHandshakeComplete: true`.
6. Optional session check: `npx opendevbrowser launch --extension-only --wait-for-extension`, then `npx opendevbrowser disconnect --session-id <id>`.
7. Optional annotation check (OpenCode): call `opendevbrowser_annotate` and confirm screenshots are written to a temp folder.

If it fails, run `npx opendevbrowser serve --stop` then `npx opendevbrowser serve`, and confirm site access includes `http://127.0.0.1/*` and `http://localhost/*` in the extension settings.

## Configuration files

### OpenCode config

The CLI modifies `opencode.json` to register the plugin:

```jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opendevbrowser"]
}
```

**Locations:**
- Global: `~/.config/opencode/opencode.json`
- Local: `./opencode.json`

### Plugin config (optional)

When using `--with-config`, a `opendevbrowser.jsonc` is created with documented defaults:

```jsonc
{
  "headless": false,
  "profile": "default",
  "persistProfile": true,
  "snapshot": { "maxChars": 16000, "maxNodes": 1000 },
  "export": { "maxNodes": 1000, "inlineStyles": true },
  "devtools": { "showFullUrls": false, "showFullConsole": false },
  "security": {
    "allowRawCDP": false,
    "allowNonLocalCdp": false,
    "allowUnsafeExport": false
  },
  "skillPaths": [],
  "skills": {
    "nudge": {
      "enabled": true,
      "keywords": ["quick start", "getting started", "launch", "connect", "setup"],
      "maxAgeMs": 60000
    }
  },
  "continuity": {
    "enabled": true,
    "filePath": "opendevbrowser_continuity.md",
    "nudge": {
      "enabled": true,
      "keywords": ["plan", "multi-step", "long-running", "refactor", "migration", "rollout", "continue"],
      "maxAgeMs": 60000
    }
  },
  "providers": {
    "antiBotPolicy": {
      "enabled": true,
      "cooldownMs": 30000,
      "maxChallengeRetries": 1,
      "allowBrowserEscalation": true
      // "proxyHint": "residential_pool_a",
      // "sessionHint": "warm_profile"
    },
    "transcript": {
      "modeDefault": "auto",
      "strategyOrder": ["youtubei", "native_caption_parse", "ytdlp_audio_asr", "apify"],
      "enableYtdlp": false,
      "enableAsr": false,
      "enableYtdlpAudioAsr": true,
      "enableApify": true,
      "apifyActorId": "streamers/youtube-scraper",
      "enableBrowserFallback": false,
      "ytdlpTimeoutMs": 10000
    }
  },
  "desktop": {
    "permissionLevel": "observe",
    "commandTimeoutMs": 10000,
    "auditArtifactsDir": ".opendevbrowser/desktop-runtime",
    "accessibilityMaxDepth": 2,
    "accessibilityMaxChildren": 25
  },
  "daemonPort": 8788,
  "daemonToken": "auto-generated-on-first-run",
  "relayPort": 8787,
  "discoveryPort": 8787,
  "flags": [],
  "checkForUpdates": false
}
```

The optional `skills.nudge` section controls the small one-time prompt hint that encourages early `skill(...)` usage on skill-relevant tasks. The optional `continuity` section controls the long-running task nudge and the ledger file path.
`discoveryPort` is the HTTP `/config` discovery listener used by the extension to learn the active relay port. Keep it at `8787` for normal extension discovery, or set it to the same isolated value as `relayPort` when running a separate daemon on isolated config/cache roots.
Fingerprint runtime defaults are Tier 1/2/3 enabled, with Tier 2 and Tier 3 driven by continuous signals (debug trace remains readout/reporting).
Provider runtime anti-bot/transcript controls default to a public-first YouTube resolver chain:
- Transcript mode semantics: `auto | web | no-auto | yt-dlp | apify`.
- Request filter precedence is `filters.youtube_mode > providers.transcript.modeDefault > auto`.
- No CLI mode flag is introduced in this phase; mode is configured in `providers.transcript.modeDefault` or per-request `youtube_mode` filter.
- Auto mode fallback chain is `youtubei -> native_caption_parse -> ytdlp_audio_asr -> apify`.
- `yt-dlp` audio transcription requires `providers.transcript.enableYtdlpAudioAsr=true`.
- Apify requires `providers.transcript.enableApify=true`, a valid `APIFY_TOKEN`, and legal checklist approval for `apify`.
- Browser-assisted fallback is opt-in only and requires `providers.transcript.enableBrowserFallback=true` plus `providers.antiBotPolicy.allowBrowserEscalation=true`.
- If browser fallback is enabled, run it in an isolated automation profile instead of a daily logged-in Google profile.

Provider workflow and execution outputs now include normalized transcript/anti-bot telemetry:
- Primary provider follow-up summary: `meta.primaryConstraintSummary`.
- Research and shopping workflow follow-up guidance: `meta.primaryConstraint.guidance.reason` and `meta.primaryConstraint.guidance.recommendedNextCommands[]` when provider recovery steps are known.
- Failure reason codes: `meta.metrics.reasonCodeDistribution` for research/shopping, `meta.reasonCodeDistribution` for product-video, readiness `reasonCodes` under `meta.presentationReadiness`, `meta.productVideoReadiness`, and `presentation-readiness.json`, plus `reasonCode` on provider failures.
- Transcript fallback diagnostics: `meta.metrics.transcript_strategy_failures` (legacy) and `meta.metrics.transcriptStrategyFailures` (camelCase alias).
- Strategy-detail diagnostics: `meta.metrics.transcript_strategy_detail_failures`/`meta.metrics.transcriptStrategyDetailFailures` and `meta.metrics.transcript_strategy_detail_distribution`/`meta.metrics.transcriptStrategyDetailDistribution`.
- Durability/pressure dimensions: `meta.metrics.transcriptDurability` and `meta.metrics.antiBotPressure` (snake_case aliases are also emitted).
- YouTube fetch metadata: `transcript_strategy` (legacy bucket), `transcript_strategy_detail` (exact strategy), `attempt_chain`, and failure `reasonCode` when transcript retrieval is unavailable.

Provider reliability criteria (resolver/browser fallback):
- `npm run test -- tests/providers-performance-gate.test.ts` must pass.
- Latest observation window must satisfy `meta.metrics.transcriptDurability.attempted >= 10` and `meta.metrics.transcriptDurability.success_rate >= 0.85`.
- Latest observation window must satisfy `meta.metrics.antiBotPressure.anti_bot_failure_ratio <= 0.15`.
- Trigger remediation immediately if either condition fails in two consecutive windows.
