---
title: "Browser automation for AI agents with Browser Use CLI"
description: "How I connected my AI agent to a real browser with active sessions using Browser Use CLI and the Chrome DevTools Protocol, with Brave support."
date: "2026-03-21T00:00:00.000Z"
author: "Carlos Garavito"
tags: ["ai", "agents", "browser-automation", "openclaw", "tools"]
canonical_url: "https://cgaravito.dev/en/blog/browser-automation-ai-agents"
last_updated: "2026-03-21T00:00:00.000Z"
locale: "en"
---

My agent could search the web, read articles, run commands, and manage files, but `web_fetch` stopped being useful when a site required authentication or interaction. X, LinkedIn, and GitHub returned text with no access to my active session, controls, or visible page state.

I wanted the agent to use my existing Brave session, click and fill controls, and take screenshots. [Browser Use CLI](https://browser-use.com) is designed for LLM consumption and gave me that interface without requiring the agent to parse a page full of HTML.

## An interface an agent can navigate

The useful part of Browser Use CLI is the output of `browser-use state`. It turns visible page elements into numeric indices, so the model can navigate without raw HTML or CSS selectors.

```
[0] <button> Follow
[1] <a href="/home"> Home
[2] <input placeholder="Search...">
[3] <div class="tweet-text"> Post content...
```

The agent can inspect that state, choose an index, act, and inspect the result. Browser Use also runs as a persistent daemon, so the browser stays alive between commands and operations take about 50ms without launching Chrome again for every action.

Installation is one command.

```bash
curl -fsSL https://browser-use.com/cli/install.sh | bash
```

The installer creates a Python virtual environment at `~/.browser-use-env/`, downloads Chromium through Playwright, and puts the `browser-use` binary under `~/.browser-use/`. I checked the installation with:

```bash
browser-use --version
```

## Connecting Brave through CDP

Browser Use has a `--profile` flag for an existing Chrome profile, and that flag only works with Chrome. I use Brave, so I connected it through the Chrome DevTools Protocol. CDP is the protocol used by browser developer tools, Playwright, and Puppeteer, and Brave exposes it when launched with a remote debugging port.

```bash
# Close Brave if it's open, then relaunch with CDP enabled
open -a "Brave Browser" --args --remote-debugging-port=9222
```

Browser Use can then attach to that browser.

```bash
browser-use --cdp-url http://localhost:9222
```

I put the launch sequence in `brave-connect.sh` because I do not want to remember it each time.

```bash
#!/bin/bash
# Cleanly quit Brave
osascript -e 'quit app "Brave Browser"'
sleep 1

# Relaunch with CDP enabled
open -a "Brave Browser" --args \
  --remote-debugging-port=9222 \
  --no-first-run

# Wait for the port to be ready
echo "Waiting for Brave..."
until curl -s http://localhost:9222/json/version > /dev/null 2>&1; do
  sleep 0.5
done
echo "Brave ready on CDP port 9222"
```

## Testing the authenticated session

I connected the agent, opened X, and took a screenshot.

```bash
browser-use --cdp-url http://localhost:9222
browser-use open https://x.com
browser-use screenshot
```

The active session opened directly on my feed. `browser-use state` exposed posts from Brad Groux about the OpenClaw Foundation, Warp's support for the kitty keyboard protocol, and Browserbase's new CLI. I then opened a post from @anibal about dynamic skill injection in Claude Code.

```bash
browser-use state
```

The navigation loop stayed small and explicit.

```bash
# 1. Open a page
browser-use open https://x.com/@anibal/status/...

# 2. Get page state (indexed elements)
browser-use state

# 3. Click something if needed
browser-use click 4

# 4. Verify the result
browser-use screenshot
```

That loop matters more to me than a large browser abstraction. The model gets a compact state, performs one action, and verifies what changed.

## Keeping CDP temporary

Port 9222 is available only on localhost in this setup. DNS rebinding can still let a malicious page target a local debugging port and attempt to control the browser.

I treat CDP as temporary access. I enable it during an active work session, start the Browser Use daemon when I need it, and disable CDP when I finish. I would be more restrictive around sensitive data or inside a corporate environment because the tradeoff changes with the browser session.

## Packaging the workflow as an OpenClaw skill

Once the browser flow worked, I wrapped it in an OpenClaw skill with a short entry point and a separate command reference.

```
~/.openclaw/workspace/skills/browser-use/
├── SKILL.md              # Concise instructions for the agent
├── references/
│   └── commands.md       # Full reference for all commands
└── scripts/
    └── brave-connect.sh  # Helper to connect Brave via CDP
```

`SKILL.md` contains the normal path: connect through CDP, open a URL, inspect `state`, act, and verify. The longer command catalog stays in `references/commands.md`, so the agent only loads it when the task needs an uncommon operation. This progressive disclosure keeps the routine instructions short and preserves the full reference for the cases that need it.

The result is a narrow browser tool that works with authenticated X, LinkedIn, GitHub, and Gmail sessions. It can navigate, click, fill forms, and take screenshots, while the security boundary stays visible each time I launch Brave with CDP enabled.
