Metadata-Version: 2.4
Name: agent-browser-control
Version: 0.2.0
Summary: Official Python client for AgentBrowser — a browser that acts on your agents' behalf.
Author: AgentBrowser
License-Expression: MIT
Project-URL: Homepage, https://getagentbrowser.com
Project-URL: Repository, https://github.com/ericmacdougall/agentbrowser
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# AgentBrowser — Python SDK

The official Python client for [AgentBrowser](https://getagentbrowser.com) — a
browser that acts on your agents' behalf. **Standard library only, zero
dependencies.** Python 3.8+.

## Install

A real, versioned, installable package (`pyproject.toml`, semantic version in
`agentbrowser.__version__`):

```bash
pip install agent-browser-control  # PyPI name (GoodQA #91; import remains `agentbrowser`)
pip install ./sdk/python          # or: pip install -e ./sdk/python for development
```

Still just one small package if you'd rather vendor it — copy the whole
`agentbrowser/` directory (not a single file anymore, since `__init__.py` is
now versioned and packaged) into your project:

```bash
cp -r sdk/python/agentbrowser your_project/
```

## Quickstart

```python
from agentbrowser import AgentBrowser

ab = AgentBrowser(api_key="gbk_...")            # from the console → API keys

with ab.session(url="https://example.com", record=True) as s:
    png = s.screenshot()                        # -> bytes (PNG)
    pdf = s.pdf()                               # -> bytes (PDF)
    shot = s.screenshot(as_artifact=True)       # -> ArtifactRef (durable)
    data = s.extract_all({"title": "h1", "price": ".price"})
    print(data)                                 # {"title": "...", "price": "..."}
# session auto-closes; because record=True the bundle is persisted server-side

# Use a separate non-recorded session for credentials. Once a hosted credential
# is filled, page, cookie, screenshot, and PDF reads are refused for that session.
with ab.session(url="https://example.com/login") as s:
    s.login("app-login")                        # hosted vault; may await approval
    s.click("#submit")

# Later, fetch the recording:
for rec in ab.recordings():
    open("session.tar.gz", "wb").write(ab.download_recording(rec["id"]))
```

## Bring your own framework (raw CDP)

```python
s = ab.session(cdp=True, url="https://example.com", profile="mobile")
print(s.cdp_url)     # wss://app.getagentbrowser.com/api/sessions/<id>/cdp?ticket=...
# The URL is ready to use — it carries a short-lived ticket scoped to this one
# session, so your account key never ends up in a log. Just:
#   browser = playwright.chromium.connect_over_cdp(s.cdp_url)
```

The dedicated browser honors `url` and `profile` (`desktop` or `mobile`) at
launch. All hosted traffic is forced through a node-owned public-only proxy that
re-resolves and validates every HTTP request and HTTPS tunnel; loopback, private,
link-local, metadata, mixed public/private DNS answers, and non-HTTP navigation
are rejected. Caller `proxy` and `proxy_bypass` options are rejected because they
would bypass that boundary. Do not combine `cdp=True` with `record`, `dom`, or
`no_video`; raw-CDP capture is owned by your Playwright/Puppeteer/CDP client.

Use the returned `cdp_url` unchanged: its `ticket` is short-lived and scoped to
that session. A client that constructs the session CDP endpoint itself must send
`Authorization: Bearer gbk_...` with a key carrying `sessions:write`. Never append
a reusable API key as `?key=`.

Structured action output has fixed safety budgets: 1 MiB for snapshot, page
text, extraction, evaluation, and cookies; 32 MiB for screenshots; and 64 MiB
for PDFs. The service returns a typed 422 `action_output_too_large` response
instead of truncating JSON or binary data. Retry after reducing the requested
page/output scope; a 429 `action_output_busy` means the node's single heavy
output slot is occupied and the request should be retried with backoff.

## Scheduled jobs & webhooks

```python
ab.create_job(name="price-check", url="https://shop.example/item",
              action="screenshot", interval=3600)          # hourly
ab.create_webhook("https://your-app.com/hooks",
                  events=["job.completed", "session.recording_ready"])
```

Webhook deliveries are HMAC-signed — verify `X-AgentBrowser-Signature`
(`sha256=<hex>` over the raw body) with the secret returned on creation.

## API surface

- `AgentBrowser(api_key, base_url="https://app.getagentbrowser.com", timeout=75.0)` — `session()`, `recordings()`,
  `download_recording()`, `delete_recording()`, `webhooks()`, `create_webhook()`,
  `delete_webhook()`, `jobs()`, `create_job()`, `delete_job()`, `artifacts()`,
  `download_artifact()`, `delete_artifact()` (artifacts covers recordings,
  `as_artifact=True` screenshots/PDFs, and durably-persisted downloads under
  one listing).
- `Session` — `navigate`, `click`, `type`, `select`, `check`, `hover`, `press`,
  `scroll`, `back`, `forward`, `reload`, `click_at`, `move_to`, `drag`,
  `read_page`, `extract`, `extract_all`, `evaluate`, `wait_for`, `snapshot`,
  `mark`, `login`, `screenshot` / `pdf` (pass `as_artifact=True` to persist a durable
  `ArtifactRef` instead of inlined bytes), `get_cookies`, `set_cookies`,
  `set_file_input`, `share`, `status`, `cancel`, `stream` (a generator
  yielding live NDJSON event envelopes), `close`, plus:
  - Downloads: `wait_for_download`, `get_download`, `get_download_info`,
    `list_downloads`, `cancel_download`
  - Dialogs: `wait_for_dialog`, `handle_dialog`, `get_dialog`
  - Tabs: `tabs`, `current_tab`, `switch_tab`, `new_tab`, `close_tab`, `wait_for_popup`
  - Network: `wait_for_response`, `get_response_body`, `block_requests`,
    `set_header_overrides`, `unblock_requests`
  - Human takeover: `request_human_takeover`, `resume_from_takeover`
  - Storage state: `export_storage_state`, `import_storage_state`
  - Any verb the server doesn't yet have a typed wrapper for: `act(verb, **params)`

  Use `Session` as a context manager to auto-close.

Errors raise `AgentBrowserError` (`.status`, `.body`, and `.code` when the
server's error body carries a machine-readable one, e.g.
`action_output_too_large`). 500/502 responses from gb-server and gb-noded
carry a typed error envelope (see `internal/platform/errenvelope.go` in the
main repo): in addition to `.code`, `AgentBrowserError` exposes `.retryable`
(bool — whether the identical request is safe to retry as-is), `.request_id`
and `.session_id` (correlation ids for support/log grepping), and `.details`
(a dict with error-specific context, e.g. a `debug_bundle_id`). All four
default to `False`/`None` for older or not-yet-converted error bodies.

### Transport

`GET` requests (read-only, side-effect-free) automatically retry up to twice
with exponential backoff on a connection failure or a 502/503/504.

`Session.act()` (which every action helper — `click`, `navigate`, `type`,
`login`, ...— goes through) retries the same way, but only when it's
provably safe: a read-only verb (`extract`, `screenshot`, `read_page`, ...)
retries freely, exactly like a GET. Every other (write) verb retries only
behind an `idempotency_key` — the server (P1-111) caches that action's
terminal result per key, so a retried call replays the original result
instead of executing again. Pass your own `idempotency_key=` to `act()` to
control it yourself (e.g. to make your OWN later retry, after your process
restarts, safe too); if you don't, `act()` generates one automatically per
call so the built-in retry is never a bare, unprotected write retry.

Every other write (`AgentBrowser.session()`, `create_webhook`,
`create_job`, `delete_*`, ...) is **not** auto-retried — those endpoints
have no idempotency-key support server-side, so retrying one on a transport
failure could double-create or double-delete. `Session.close()` keeps its
own narrow, deliberate exception: a second `close()` call is always safe (a
404 on delete is treated as already-closed).

Pass a per-call `timeout=` to `AgentBrowser._request`/`_download` to
override the client's default for one call.

## CLI

Installing the package (`pip install ./sdk/python`) also installs an
`agentbrowser` command — a deterministic way to exercise the hosted API
outside an agent, reproduce a failure, or script CI checks.

```bash
agentbrowser auth set --api-key gbk_...          # stores ~/.agentbrowser/config.json (0600)
agentbrowser session create --url https://example.com --record --json
agentbrowser session act sess_123 click --param selector=#submit
agentbrowser session act sess_123 type --param selector=#user --param text=me@example.com
agentbrowser session status sess_123
agentbrowser session stream sess_123             # tails live NDJSON events; Ctrl-C to stop
agentbrowser session close sess_123

agentbrowser recording list
agentbrowser recording download rec_123 -o bundle.tar.gz
agentbrowser artifact list                       # recordings + as_artifact screenshots/PDFs + durable downloads
agentbrowser artifact download art_123 -o file.bin
agentbrowser job create --name price-check --url https://shop.example --action screenshot --interval 3600
agentbrowser webhook create --url https://your-app.com/hooks --events job.completed,session.recording_ready

agentbrowser doctor          # config/credentials/connectivity/api-key checks; exits 1 if unhealthy
```

Every command prints one JSON object to stdout: `{"request_id": "...", "ok":
true, "data": {...}}` on success, `{"request_id": "...", "ok": false,
"error": "...", "error_code": "..."}` on failure (exit code 1). `--json`
gives the compact single-line form for scripts; the default is the same
object pretty-printed. `request_id` is generated client-side per invocation
for support correlation — the server doesn't (yet) echo or log it.
`session stream` and `... download -o -` are the two exceptions: a live
event tail and raw downloaded bytes each print directly to stdout instead of
being wrapped in the envelope, since mixing binary/streaming output with a
single JSON object on the same stream would corrupt both.

Credentials resolve in order: `--api-key`/`--base-url` flags (work before or
after the subcommand), `AGENTBROWSER_API_KEY`/`AGENTBROWSER_BASE_URL` env
vars, then the current (or `--profile`-named) profile from `agentbrowser
auth set`. `agentbrowser profile list` / `agentbrowser profile use NAME`
manage multiple stored profiles.

`doctor`'s checks are scoped to what's actually reachable with an API key
(config file, credential resolution, `/healthz` connectivity, and an
authenticated `recordings:read` probe) — node/infra diagnostics need
operator-level auth this CLI doesn't have, and aren't attempted. A 401 on
the recordings probe is invalid authentication (unhealthy). A 403 is treated
as proof the key is valid but missing `recordings:read` — a warning, so a
least-privilege key still exits 0.

## Publishing

Release steps (build/verify/`twine upload`) are documented internally for
maintainers, not published here.
