# Session operations

The `targets`, `cookies`, `fetch`, `storage`, `eval`, `wait`, and
`wait-for-cookie` subcommands — and their MCP-tool equivalents
(`list_targets`, `cookies`, `storage_get`, `storage_set`, `wait_for_cookie`,
`fetch`) — are all thin wrappers over an engine-agnostic session module in
`src/session/`. This document describes the model that surface implements
and the guarantees you can rely on when integrating.

## The `PageSession` model

A `PageSession` is a handle to a single page in a running browser. It is
engine-agnostic: under the hood it is either a CDP target (Chrome, Edge,
Chromium, Brave) or a BiDi browsing context (Firefox), but the operations
exposed are the same.

```rust
let session = PageSession::attach(&endpoint, engine, target_url_regex).await?;
let value   = session.evaluate("document.title", true).await?;
session.navigate("https://example.com/").await?;
let png_b64 = session.screenshot(true).await?;
session.close().await;
```

`target_url_regex` is the same selector used by `--target` on the CLI and
`--url` on `targets`: an unanchored Rust regex matched against each open page
target's URL. When `None`, the engine's default page is used (CDP: first
`type == "page"`; BiDi: top-level browsing context). When the regex matches
no targets, attach fails with a descriptive error.

## `TargetInfo` shape

`session::targets::list(endpoint, engine, url_regex)` returns a normalised
list of `TargetInfo`:

| Field   | Type   | Notes                                                    |
|---------|--------|----------------------------------------------------------|
| `id`    | string | CDP target id, or BiDi browsing-context id.              |
| `url`   | string | Live URL at probe time.                                  |
| `title` | string | Document title (`""` when none).                         |
| `kind`  | string | `"page"` for normal pages; engines may add more later.   |

Both the CLI table (`KIND ID URL TITLE`) and the MCP `list_targets` tool
return this shape directly. Tools that take `--target URLREGEX` use the same
regex semantics as `targets --url`.

## `cookies --format netscape`

The `netscape` formatter emits the classic Mozilla `cookies.txt` format,
byte-compatible with what `curl --cookie`, `wget --load-cookies`, and
`yt-dlp --cookies` expect.

Layout:

```
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by browser-control. Edit at your own risk.

<domain>\t<include_subdomains>\t<path>\t<secure>\t<expires>\t<name>\t<value>
```

Column rules:

1. **`domain`** — exact cookie domain. If the cookie applies to subdomains
   (i.e. CDP `domain` starts with `.` or `Network.getAllCookies` reports
   `hostOnly == false`), a leading `.` is prepended; host-only cookies are
   written without one.
2. **`include_subdomains`** — `TRUE` when the domain has the leading dot,
   `FALSE` otherwise. Always uppercase.
3. **`path`** — verbatim cookie path (typically `/`).
4. **`secure`** — `TRUE` or `FALSE`, uppercase.
5. **`expires`** — Unix epoch seconds as an integer. `0` denotes a session
   cookie (no expiry); writers that expect missing expiries should treat `0`
   as "session". Negative expirations are clamped to `0`.
6. **`name`** — cookie name.
7. **`value`** — cookie value, verbatim. No quoting or escaping is applied;
   the format does not support tabs or newlines in values and `browser-control`
   does not produce them.

Fields are separated by single tab characters; lines end with `\n`. Files
written via `-o FILE` are `chmod 0600` on Unix.

## Profile semantics

`browser-control start` always uses a single persisted profile per browser
kind, located under the OS app-data dir:

- macOS:   `~/Library/Application Support/browser-control/profiles/<kind>/default/`
- Linux:   `~/.config/browser-control/profiles/<kind>/default/`
- Windows: `%APPDATA%\browser-control\profiles\<kind>\default\`

(`<kind>` is `chrome`, `edge`, `chromium`, `brave`, or `firefox`.
`BROWSER_CONTROL_CONFIG_DIR` overrides the root for tests.)

Consequences:

- The same browser state (cookies, logins, extensions) is reused across all
  `browser-control` invocations, MCP agents, and shells on the machine.
- Restarting the browser, the user's shell, or the host does not lose state.
- Per-kind separation exists because Chrome and Firefox profile formats are
  not interchangeable.
- There is intentionally no `--profile` flag: agents that pass a custom
  profile would otherwise create ephemeral sessions and force the user to
  re-authenticate every time.

Named profiles (`profiles/<kind>/<name>/`) are deferred to a future release;
the current layout leaves room for them with no migration.

## Writing your own integrations

If shelling out to the CLI is not a fit — say, you're embedding browser
control in a larger Rust tool — call into `src/session/` directly. The
public surface mirrors what the CLI uses:

- `session::PageSession::attach(endpoint, engine, target_url_regex)` —
  attach to a page; returns a `PageSession`.
- `session::PageSession::evaluate(expression, await_promise)` — run JS,
  returns `serde_json::Value`.
- `session::PageSession::navigate(url)` — drive navigation.
- `session::PageSession::screenshot(full_page)` — base64 PNG.
- `session::PageSession::close()` — release the attachment.
- `session::targets::list(endpoint, engine, url_regex)` — enumerate pages.

Higher-level operations (cookie export, storage get/set, fetch, wait loops)
currently live in the `cli::` modules and call `session::*` under the hood.
If you want to reuse them as a library, the cleanest path today is to shell
out to the `browser-control` binary; promoting them into `session::` is on
the roadmap. Engine dispatch is internal; callers pass an `Engine` and the
right protocol path is used.

The registry (`src/registry.rs`) and `BrowserRow` give you the `endpoint`
and `engine` for any browser the user has started; see
`cli::env_resolver::resolve` for the canonical resolution rules.
