From e5aaea3aeaf28a456a4be15e117dcb4ad016cc12 Mon Sep 17 00:00:00 2001 From: Maciej Ugorski Date: Mon, 25 May 2026 22:12:27 +0200 Subject: [PATCH 1/2] docs: Add OpenClaw Docker setup guide Documents how to run opera-browser-cli inside an OpenClaw container, with a chromedp/headless-shell sidecar to work around Chromium's ARM64 Docker Desktop crash and Chrome CDP's non-localhost Host header rejection. --- .gitignore | 2 + README.md | 16 ++++ SKILL.md | 5 ++ openclaw/.env.example | 14 +++ openclaw/Dockerfile | 14 +++ openclaw/README.md | 171 ++++++++++++++++++++++++++++++++++++ openclaw/docker-compose.yml | 42 +++++++++ package.json | 1 + 8 files changed, 265 insertions(+) create mode 100644 openclaw/.env.example create mode 100644 openclaw/Dockerfile create mode 100644 openclaw/README.md create mode 100644 openclaw/docker-compose.yml diff --git a/.gitignore b/.gitignore index 66418dc..7c5df25 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ dist/ *.tar.gz *.png scripts/ +.env +docker/.env diff --git a/README.md b/README.md index 70e4357..39b0112 100644 --- a/README.md +++ b/README.md @@ -288,6 +288,22 @@ Set `OPERA_CLI_DISABLE_HOOKS=1` to skip that auto-install behavior. Development entrypoints such as `npm run dev` and `bin/opera-browser-cli.ts` do not modify those hook files. +## Docker / OpenClaw + +A ready-made Docker setup runs `opera-browser-cli` as a tool inside an +[OpenClaw](https://openclaw.ai) agent gateway, with a headless Chrome sidecar handling +the browser. No local browser or Node.js install is required on the host — Docker is +the only prerequisite. The skill is registered automatically so OpenClaw agents can +invoke `opera-browser-cli` commands directly. + +The `docker/` directory ships with the npm package: + +```sh +cd "$(npm root -g)/opera-browser-cli/openclaw" +``` + +See [`openclaw/README.md`](openclaw/README.md) for the full setup guide. + ## Local Setup (Full Stack) Both `opera-devtools-mcp` and `opera-browser-cli` need to be built and linked so they're available in PATH. diff --git a/SKILL.md b/SKILL.md index 4240ffa..1c0c542 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,7 @@ --- name: opera-browser-cli description: Browser automation and web interaction using the opera-browser-cli tool. Use for navigating pages, clicking elements, filling forms, taking screenshots, inspecting console/network, running performance audits, and Opera AI features (chat available on any Opera browser; invoke-do, make, research require Opera Neon). +metadata: {"openclaw": {"requires": {"bins": ["opera-browser-cli"]}}} --- # Skill: opera-browser-cli Browser Automation @@ -41,6 +42,10 @@ Use `opera-browser-cli url $uN` or `opera-browser-cli url @ref` to resolve a tok Commands that accept these flags: `open`, `snapshot`, `click`, `fill`, `type`, `press`, `scroll`, `back`, `hover`, `drag`, `fillform`, `upload`, `newpage`, `selectpage`. +## Running inside an OpenClaw container + +To wire this CLI into a Docker-based OpenClaw setup (Chromium sidecar, shared netns, config bootstrap), see [`openclaw/README.md`](openclaw/README.md). + ## Sign-in errors If you hit `Opera: user is not signed in` on an AI command, suggest signing in to their Opera account. Run `opera-browser-cli setup` or `opera-browser-cli doctor` to configure or diagnose. diff --git a/openclaw/.env.example b/openclaw/.env.example new file mode 100644 index 0000000..3306a7e --- /dev/null +++ b/openclaw/.env.example @@ -0,0 +1,14 @@ +# Copy to .env and fill in your values. +# docker-compose reads this file automatically when run from the openclaw/ directory. + +# --- AI provider keys (pass whichever providers OpenClaw is configured to use) --- +# ANTHROPIC_API_KEY=sk-ant-... +# OPENAI_API_KEY=sk-... + +# --- opera-browser-cli notes --- +# The compose file wires OPERA_CLI_BROWSER_URL=http://localhost:9222 automatically. +# In connect-to-existing-browser mode use `newpage ` instead of `open `: +# docker compose exec openclaw opera-browser-cli newpage https://example.com +# +# Additional Chrome flags (space-separated): +# OPERA_CLI_CHROME_ARGS=--some-flag diff --git a/openclaw/Dockerfile b/openclaw/Dockerfile new file mode 100644 index 0000000..59519ab --- /dev/null +++ b/openclaw/Dockerfile @@ -0,0 +1,14 @@ +FROM ghcr.io/openclaw/openclaw:latest + +USER root + +# Install opera-browser-cli globally so it is on PATH for OpenClaw agents. +# Browser is provided by the chromedp/headless-shell sidecar in docker-compose.yml. +# Also registers SKILL.md into ~/.agents/skills/ — a path OpenClaw scans for skills +# that is not covered by any named volume, so the file lives in the image layer. +RUN npm install -g opera-browser-cli && \ + mkdir -p /home/node/.agents/skills/opera-browser-cli && \ + cp "$(npm root -g)/opera-browser-cli/SKILL.md" /home/node/.agents/skills/opera-browser-cli/SKILL.md && \ + chown -R node:node /home/node/.agents + +USER node diff --git a/openclaw/README.md b/openclaw/README.md new file mode 100644 index 0000000..f5b2735 --- /dev/null +++ b/openclaw/README.md @@ -0,0 +1,171 @@ +# Adding opera-browser-cli to OpenClaw (Docker) + +`opera-browser-cli` can be added to any Docker-based OpenClaw setup. Two approaches — +pick whichever suits your workflow: + +- **[Option A: Runtime install](#option-a-runtime-install)** — install into the running + container, no image rebuild required +- **[Option B: Dockerfile](#option-b-dockerfile)** — extend the OpenClaw image so the + install is baked in and fully persistent + +> **Browser is generic headless Chromium, not Opera.** The sidecar uses +> `chromedp/headless-shell` (upstream Chromium). Standard automation commands +> (`open`, `newpage`, `snapshot`, `click`, `fill`, `type`, `screenshot`, `eval`, +> `pages`, `network`, `console`, `lighthouse`, …) work fine. Opera-specific +> commands — `chat`, `invoke-do`, `make`, `research` — **will not work**; they +> need an Opera browser with a signed-in user session. +> +> Do **not** run `opera-browser-cli setup` or `doctor` inside the container. +> Both are interactive Opera-Neon detectors and will fail. All required env vars +> are injected by the compose file — no further config is needed. + +## Prerequisites + +- Docker Desktop (Mac/Windows) or Docker Engine + Compose plugin (Linux) + +## Compose changes (required for both options) + +Add a headless Chrome sidecar and wire OpenClaw's network namespace to it: + +```yaml +services: + chrome: + image: chromedp/headless-shell:latest + ports: + - "18789:18789" # expose OpenClaw's gateway here — openclaw shares this netns + restart: unless-stopped + + openclaw: + # ... your existing config, with these additions: + network_mode: "service:chrome" + environment: + OPERA_CLI_BROWSER_URL: http://localhost:9222 +``` + +**Note:** `network_mode: "service:chrome"` is incompatible with `networks:` and `ports:` +on the `openclaw` service. Move any ports you were exposing on `openclaw` to the `chrome` +service instead (as shown above). + +Then start the stack: + +```bash +docker compose up -d +``` + +## Option A: Runtime install + +No Dockerfile or image rebuild needed. Once the stack is up, run: + +```bash +docker compose exec openclaw sh -c ' + npm install -g opera-browser-cli && + mkdir -p ~/.openclaw/skills/opera-browser-cli && + cp $(npm root -g)/opera-browser-cli/SKILL.md ~/.openclaw/skills/opera-browser-cli/SKILL.md +' +``` + +This installs the binary and registers the skill in one step. The SKILL.md is written +to the `openclaw-config` named volume and persists across restarts and `docker compose +down`. The binary lives in the container filesystem and is lost when the container is +recreated — re-run `npm install -g opera-browser-cli` after each `docker compose down`. + +## Option B: Dockerfile + +Extend the official OpenClaw image so the install is baked in and survives `docker +compose down`. Use the `Dockerfile` in this directory (the reference implementation +below), or create your own using it as a starting point. + +Point your compose service at it: + +```yaml +openclaw: + build: . # path to the directory containing the Dockerfile + # ... rest of your existing config +``` + +Build once, then start normally: + +```bash +docker compose build +docker compose up -d +``` + +Re-run `docker compose build` when a new version of `opera-browser-cli` is released. + +## Reference implementation + +This directory contains a complete working example for a standalone fresh install: + +- `Dockerfile` — the Option B extension above +- `docker-compose.yml` — a full compose file including the Chrome sidecar +- `.env.example` — template for environment variables + +For OpenClaw gateway configuration (mode, auth token, AI provider keys), see the +[OpenClaw docs](https://docs.openclaw.ai). + +## Using opera-browser-cli + +For the full command reference see [SKILL.md](../SKILL.md). + +**From the host** (for manual testing or debugging): + +```bash +docker compose exec openclaw opera-browser-cli +``` + +### Opening pages + +Because `OPERA_CLI_BROWSER_URL` is set, the CLI connects to the existing Chrome rather +than launching one. Use `newpage` to open a fresh tab: + +```bash +opera-browser-cli newpage https://example.com +``` + +`open ` also works, but only after a `stop` first — which resets the bridge so it +reconnects cleanly: + +```bash +opera-browser-cli stop +opera-browser-cli open https://example.com +``` + +Without `stop` first, `open` returns `No page selected` because no tab is pre-selected +when connecting to an already-running Chrome instance. + +## Environment variables + +`OPERA_CLI_BROWSER_URL` is the only variable required. Set it in your compose file as +shown above. + +| Variable | Set to | Purpose | +|---|---|---| +| `OPERA_CLI_BROWSER_URL` | `http://localhost:9222` | Connect to the headless-shell sidecar instead of launching a browser | + +Other `opera-browser-cli` variables (not needed unless customising): + +| Variable | Purpose | +|---|---| +| `OPERA_CLI_EXECUTABLE_PATH` | Path to a browser binary (launch mode only — not relevant here) | +| `OPERA_CLI_CHROME_ARGS` | Extra Chrome flags (launch mode only) | +| `OPERA_CLI_HEADED` | `1` for headed mode — not useful in a headless container | +| `OPERA_CLI_USER_DATA_DIR` | Persistent Chrome profile path (launch mode only) | +| `OPERA_CLI_PORT` | Bridge HTTP port inside the container (default `9225`) | + +## Troubleshooting + +**`opera-browser-cli open` returns "No page selected"** + +You are in connect mode. Use `newpage ` instead, or run `opera-browser-cli stop` +first and then `open`. + +**Chrome connection errors (`Failed to fetch browser webSocket URL`)** + +The headless-shell container may not be ready yet. Wait a few seconds and retry. +Check its status with `docker compose ps`. + +**Chromium SIGTRAP / crash if using a different base image or architecture** + +Debian's system Chromium (`apt install chromium`) crashes on ARM64 Docker Desktop +with `Trace/breakpoint trap (core dumped)`. Do not substitute it for the +`chromedp/headless-shell` sidecar. diff --git a/openclaw/docker-compose.yml b/openclaw/docker-compose.yml new file mode 100644 index 0000000..30b1bd1 --- /dev/null +++ b/openclaw/docker-compose.yml @@ -0,0 +1,42 @@ +services: + # Dedicated headless Chrome sidecar — ARM64-compatible, pre-configured for Docker. + # opera-browser-cli connects to it via CDP at localhost:9222 (shared network namespace). + chrome: + image: chromedp/headless-shell:latest + # Chrome's CDP HTTP server rejects non-IP/non-localhost Host headers. + # The openclaw service uses network_mode: service:chrome so it sees Chrome + # as localhost, bypassing that check without needing --remote-allow-origins tricks. + ports: + # Expose OpenClaw's gateway through the chrome container (openclaw shares this netns) + - "18789:18789" + restart: unless-stopped + + openclaw: + build: + context: . + dockerfile: Dockerfile + depends_on: + - chrome + # Share chrome's network namespace so localhost:9222 IS the Chrome CDP port + network_mode: "service:chrome" + environment: + # localhost:9222 resolves inside chrome's netns + OPERA_CLI_BROWSER_URL: http://localhost:9222 + + # OpenClaw: skip the interactive first-run wizard + OPENCLAW_SKIP_ONBOARDING: "1" + # Disable mDNS advertising (not useful inside Docker) + OPENCLAW_DISABLE_BONJOUR: "1" + + volumes: + # Persist OpenClaw config and workspace across container restarts + - openclaw-config:/home/node/.openclaw + - openclaw-auth:/home/node/.config/openclaw + + shm_size: 256m + + restart: unless-stopped + +volumes: + openclaw-config: + openclaw-auth: diff --git a/package.json b/package.json index ad0aeef..d9c5c37 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "files": [ "dist", + "openclaw", "LICENSE", "README.md", "SKILL.md" From 48675b1b312410cba837a816239cfbe31b63f445 Mon Sep 17 00:00:00 2001 From: Maciej Ugorski Date: Fri, 29 May 2026 11:38:42 +0200 Subject: [PATCH 2/2] fix: suppress setup hint and ensure Chrome readiness in Docker setup - Suppress "hint: run setup" when OPERA_CLI_BROWSER_URL is set; setup configures a browser executable path which is irrelevant in connect mode - Add healthcheck to chrome sidecar and condition: service_healthy on openclaw depends_on so the gateway never starts before CDP is ready - Update troubleshooting docs to reflect the healthcheck fix --- openclaw/README.md | 6 ++++-- openclaw/docker-compose.yml | 9 ++++++++- src/cli.ts | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openclaw/README.md b/openclaw/README.md index f5b2735..7350716 100644 --- a/openclaw/README.md +++ b/openclaw/README.md @@ -161,8 +161,10 @@ first and then `open`. **Chrome connection errors (`Failed to fetch browser webSocket URL`)** -The headless-shell container may not be ready yet. Wait a few seconds and retry. -Check its status with `docker compose ps`. +The reference `docker-compose.yml` uses a healthcheck on the `chrome` service so +OpenClaw only starts after Chrome's CDP port is confirmed open. If you see this error +with a custom compose file, add the healthcheck and `condition: service_healthy` to +your `depends_on` — see the reference `docker-compose.yml` for the exact config. **Chromium SIGTRAP / crash if using a different base image or architecture** diff --git a/openclaw/docker-compose.yml b/openclaw/docker-compose.yml index 30b1bd1..906a734 100644 --- a/openclaw/docker-compose.yml +++ b/openclaw/docker-compose.yml @@ -10,13 +10,20 @@ services: # Expose OpenClaw's gateway through the chrome container (openclaw shares this netns) - "18789:18789" restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "bash -c 'exec 3<>/dev/tcp/127.0.0.1/9222' 2>/dev/null"] + interval: 3s + timeout: 3s + retries: 10 + start_period: 5s openclaw: build: context: . dockerfile: Dockerfile depends_on: - - chrome + chrome: + condition: service_healthy # Share chrome's network namespace so localhost:9222 IS the Chrome CDP port network_mode: "service:chrome" environment: diff --git a/src/cli.ts b/src/cli.ts index 63db721..9a7f59e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2466,7 +2466,7 @@ function warnIfUnconfigured(argv: string[]): void { const cmd = argv[0]; if (cmd !== undefined && SETUP_SKIP_COMMANDS.has(cmd)) return; const configFile = join(homedir(), ".opera-browser-cli", "config"); - if (!existsSync(configFile) && !process.env.OPERA_CLI_EXECUTABLE_PATH) { + if (!existsSync(configFile) && !process.env.OPERA_CLI_EXECUTABLE_PATH && !process.env.OPERA_CLI_BROWSER_URL) { process.stderr.write( "hint: run `opera-browser-cli setup` to configure (first-time setup)\n", );