Metadata-Version: 2.4
Name: X5Browser
Version: 1.0.0
Summary: Browser library for AI agents: real Chrome over CDP (every action returns result + stable map + screenshot), ad-defense shield, live view.
Author: X5Coder
License: MIT
Keywords: browser,chrome,cdp,ai-agent,automation,live-view,vnc,adblock
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.9
Requires-Dist: websockets>=15
Provides-Extra: annotate
Requires-Dist: Pillow>=10; extra == "annotate"
Dynamic: license-file

# X5Browser

Real Chrome for AI agents: drive a headed Chromium over raw CDP (no Playwright/Puppeteer/Selenium), read pages as numbered text maps, survive ad-infested sites with a built-in shield, and hand the **same browser** to a human and back.

```bash
pip install X5Browser
```

```python
import asyncio
from x5browser import X5Browser, BrowserConfig

async def main():
    async with X5Browser(BrowserConfig.default()) as browser:
        await browser.navigate("https://example.com")
        print(await browser.page_snapshot())          # numbered element map
        refs = await browser.find_elements("Learn more")
        # ... pick a ref, e.g. 3
        result = await browser.click(ref=3)           # real mouse click
        print(result.message)
        print(result.observation)                     # fresh stable map, attached

asyncio.run(main())
```

Every **acting** tool waits for full page stability, then replies with
`message` + fresh stable element map + one screenshot — the agent always sees
the outcome at once, no extra snapshot call needed.

---

## 1. Two facades

| Class | Use it when | Import |
|---|---|---|
| `X5Browser` | You wire your own agent loop / UI | `from x5browser import X5Browser, BrowserConfig` |
| `LiveBrowser` | You want hidden Chrome + live link + timeline in one object | `from x5browser import LiveBrowser` |

```python
from x5browser import LiveBrowser

live = LiveBrowser()              # ./profile data, headed Chrome
await live.start()
print(live.live_url)              # open in any browser / <iframe>
await live.navigate("https://example.com")
for step in live.steps():         # timeline rows with screenshots
    ...
await live.take_control()         # user drives the SAME browser
await live.agent_control()        # agent resumes with a fresh map
await live.stop_browser()         # quiet close, data kept
```

## 2. Tool catalog (36 tools, 9 groups)

`X5Browser.tools()` returns the default 18-tool surface; `tools(full=True)`
returns all 36. Model descriptions are concise professional English.
All interaction is genuine input events (`Input.*`); there is no JS-drive tool.

### Perception (read-only)

| Tool | What it does | Example |
|---|---|---|
| `page_snapshot` | Numbered map of interactive elements (modals, iframes). Start here | `page_snapshot(scope="viewport")` |
| `read_text` | Visible page/element text | `read_text(ref=5)` |
| `find_elements` | Find by name/role, returns refs directly | `find_elements(query="checkout")` |
| `inspect_element` | Deep diagnosis: attributes, state, box, covering element, HTML | `inspect_element(ref=12)` |
| `screenshot` | Viewport/element/full-page capture; `annotate` draws ref numbers | `screenshot(annotate=true)` |
| `extract_data` | Structured JSON: table, list, links, forms, images, meta | `extract_data(kind="table")` |
| `get_page_info` | Tab state: URL, title, control, viewport, scroll, frames, focus | `get_page_info()` |

### Navigation

| Tool | What it does | Example |
|---|---|---|
| `navigate` | Open URL / back / forward / reload; replies with fresh map | `navigate(to="https://example.com")` |
| `wait_for` | Wait for text, element, URL, network idle, download, seconds | `wait_for(condition="text", value="Done")` |

### Interaction (real input + fresh stable map in every reply)

| Tool | What it does | Example |
|---|---|---|
| `click` | Real click; follows real hrefs directly; absorbs ad hijacks (auto-clean + one re-click) | `click(ref=7)` |
| `click_verify` | Click plus checks in one call | `click_verify(ref=7, checks=[...])` |
| `fill` | Type into a field; optional Enter | `fill(ref=8, text="hi", submit=true)` |
| `fill_form` | Fill typed fields + optional submit (needs approval) | `fill_form(fields=[{ref:8,value:"hi"}])` |
| `press_keys` | Key/shortcut, optional focus + repeat | `press_keys(keys="Ctrl+A")` |
| `scroll` | Wheel / to element / in container / to edge | `scroll(direction="down", amount=600)` |
| `hover` | Gradual hover; replies with appeared elements | `hover(ref=6)` |
| `hover_click` | Hover menu open + click item, one call | `hover_click(menu_ref=6, target_ref=9)` |
| `choose_option` | Dropdown value (native/custom), verified | `choose_option(ref=13, label="Egypt")` |
| `drag_drop` | Gradual drag between refs/coordinates | `drag_drop(from_ref=3, to_ref=9)` |
| `upload_files` | Attach workspace files (needs approval) | `upload_files(ref=20, paths=["id.png"])` |

### Tabs / events / downloads

| Tool | What it does | Example |
|---|---|---|
| `manage_tabs` | List, open, switch, close tabs | `manage_tabs(action="list")` |
| `get_events` | New tabs, dialogs, permissions, downloads, crashes | `get_events()` |
| `answer_popup` | Answer dialog/permission/file-chooser (do first) | `answer_popup(kind="dialog", accept=true)` |
| `manage_downloads` | List / wait / cancel downloads | `manage_downloads(action="list")` |

### DevTools / task

| Tool | What it does | Example |
|---|---|---|
| `read_console` | Last 500 console messages, filterable | `read_console(level="error")` |
| `console_command` | One JS expression, read value (diagnosis only; needs `allow_unsafe_js=True`) | `console_command(script="document.title")` |
| `list_requests` | Always-on network log; `id` gives full detail | `list_requests(url_contains="api")` |
| `export_page` | Save PDF/MHTML/HTML (needs approval) | `export_page(format="pdf", path="p.pdf")` |
| `verify_state` | Pass/fail checks, cheaper than a snapshot | `verify_state(checks=[...])` |
| `batch_actions` | Several tools in one call, refs remap | `batch_actions(actions=[...])` |

### Lifecycle / handoff

| Tool | What it does | Example |
|---|---|---|
| `open_browser` / `close_browser` | Explicit start / quiet close (data kept) | `close_browser()` |
| `human_control` | Hand the live browser to the user (non-blocking) | `human_control(reason="pay")` |
| `take_control` / `agent_control` | Hand control over / resume agent | `take_control()` |
| `live_preview` | Watch-only link + JSON | `live_preview()` |

**Ref rules:** refs come from `page_snapshot`/`find_elements` and die on page
change (`ref_not_found` → re-snapshot). Optional `pre_wait`/`post_wait`
(0–30s) and test-note `reason` (≤500 chars) on every tool.

## 3. Ad-defense shield (automatic, no page JS)

A layer between Chrome and the agent. Ads are received normally on the wire
and cancelled at network/tab/map/display layers, so sites see ordinary HTTP.

| Line | What it does | Env |
|---|---|---|
| `NET` | Block/stub ad requests via `Fetch.*` (incl. empty-VAST for pre-roll) | `DEFENSE_NET=1` |
| `TABS` | Close ad popups, restore hijacked tabs (≤5), same-tab href bypass + sacrifice re-click in `click` | `DEFENSE_TABS=1` |
| `TREE` | Hide ad iframes/labels from the agent map | on with `NET` |
| `HEURISTIC` | Ad paths, affiliate redirects, rotating CDNs, brand creatives, tracking-param strip | `DEFENSE_HEURISTIC=1` |
| `SELFHEAL` | One calm reload with the harshest layer off per broken site | `DEFENSE_SELFHEAL=1` |
| `SHADOW` | Inspector stylesheet (CDP CSS, no script): ads render invisibly, clicks pass through | `DEFENSE_SHADOW=1` |

`COSMETIC/STUBS/OVERLAY/MEDIA` stay **off by design** (they need injected page
script). `DNS/EXT` are deployment-time (see `x5browser/defense/policy.json`).

```python
print(browser.defense_report())   # per-line counters: the impact meter
```

**Allowlist** (`x5browser/defense/allowlist.yaml`, editable): OAuth/SSO,
payments + bank 3DS, captchas, embeds, fonts, CDNs are never touched — with
built-in fallbacks even if the file is corrupt. Extra hosts:
```yaml
extra_hosts:
  - my-sso.example.com
```

**Blocklists:** built-in core + `defense/lists/custom.txt` (hand-kept) +
optional full lists (231k hosts):
```bash
python scripts/update_adlists.py   # refresh lists/*.txt at build time
python scripts/measure_defense.py  # blocked counts per line
```

## 4. Configuration

```python
BrowserConfig(
    profile_dir="...",      # default: inside the package (persistent)
    downloads_dir="...",     # default: <profile>/downloads
    debug_port=9222,         # loopback only, never public
    window_width=1280, window_height=800,
    headless=False,          # headed so live shows everything
    confirm_dangerous=True,  # approval gates for pay/delete/send/…
    allow_unsafe_js=False,   # enable console_command explicitly
    extra_chrome_args=(),    # appended verbatim
)
```

## 5. Same-browser handoff cycle

`agent → human_control → user → agent_control → agent`, same Chrome, same
profile, same tabs and cookies. No new session, no Done button. While the user
holds control, agent tools refuse with `control_not_agent`.

## 6. Security notes

- CDP binds `127.0.0.1` only; uploads/downloads confined to the workspace.
- Passwords and card numbers are masked (`••••`) in maps, logs, screenshots.
- No Playwright/Puppeteer/Selenium; no JS-driven interaction (`Input.*` only).
- Secrets: copy `.env.example` → `.env` (never committed).

## License

MIT — see `LICENSE`.
