# The SDK entrypoint

`betterwright/sdk` is the import for driving BetterWright from your own code.
It carries the exports meant for programmatic use, plus one helper,
`withBrowser`, that owns the client's lifetime for you.

The root import, `betterwright`, is unchanged and stays the compatibility
surface. It still exports everything it always has, including lower-level
pieces most integrations never touch (CAPTCHA scoring, challenge detection,
skill loading), so existing code keeps working and nothing has to move.

## Install

```bash
bun add betterwright
bunx betterwright setup    # downloads the managed BetterChromium build once
```

Bun 1.4. The package is ESM, so use `import`, or `await import()`
from CommonJS.

## A complete example

```ts
import { BrowserError, withBrowser } from "betterwright/sdk";

const title = await withBrowser({ headless: false }, async (bw) => {
  await bw.run("await page.goto('https://example.com')", { note: "Opening example.com" });

  const result = await bw.run<string>("return page.title()");
  if (!result.ok) throw new BrowserError(result.error);
  if (typeof result.result !== "string") {
    throw new BrowserError(`Title output was truncated; inspect ${result.result.fullOutputPath}`);
  }

  await bw.run("return screenshot({ kind: 'proof', name: 'example-home' })");
  return result.result;
});

console.log(title);
```

`withBrowser` constructs a `BetterWright`, awaits your function, and closes the
client in a `finally`, so the worker process and the browser are released even
when the function throws. It resolves with whatever your function returned.
The callback may be synchronous or asynchronous. Pass the callback alone,
`withBrowser(fn)`, to take the default options.

The string each `run()` call takes is Playwright code, executed inside the
worker sandbox where `page`, `snapshot`, `screenshot`, `human`, and
`credentials` live. [browser-api.md](browser-api.md) documents those globals
and the result envelope `run()` returns.

## What it exports

Browser client:

| Export | What it is |
| --- | --- |
| `withBrowser(options?, fn)` | Run `fn` with a client and close that client afterwards. |
| `BetterWright` | The client itself: `run()`, sessions, live view, downloads, credential filling. Full reference in [javascript.md](javascript.md). |
| `BrowserError` | The error type to throw when a result envelope comes back with `ok: false`. |
| `validateCredentialMatchMode(value)` | Returns the value when it is one of the four credential URL scopes, and throws a `TypeError` otherwise. |
| `listCookieSourceBrowsers()` | List local browser sources supported by Cookie Sync. |
| `listCookieSourceProfiles(browser, options?)` | List profiles for one Cookie Sync source. See [Cookie Sync](cookie-sync.md). |
| `agentSystemPrompt(guardrails?)` | Operator guidance and optional behavioral limits for an external agent. See [agent-prompt.md](agent-prompt.md). |

Network policy:

| Export | What it is |
| --- | --- |
| `NetworkPolicy` | The allow/deny rules a client enforces on every request. See [network-policy.md](network-policy.md). |
| `METADATA_ADDRESSES` | The cloud metadata IP addresses in the unliftable network floor. |
| `METADATA_HOSTNAMES` | The hostnames in that same floor. |

Credential vault:

| Export | What it is |
| --- | --- |
| `createLocalCredentialVault(options?)` | The encrypted local vault, including the owner-only reads behind `betterwright vault`. See [credentials.md](credentials.md). |
| `LocalCredentialVault` | The vault class, for passing an instance to the client. |
| `LocalCredentialVaultError` | The error type vault operations throw. |
| `VAULT_CATEGORIES` | The credential categories a record can use. |
| `VAULT_MATCH_MODES` | The URL scopes a stored credential can be filled on. |

Built-in agent:

| Export | What it is |
| --- | --- |
| `runAgentTask(options)` | The task loop behind `betterwright exec`: a task in, one result out. See [agent.md](agent.md). |
| `resolveModel(model, options?)` | Turn a model id, or an object with a `complete` method, into a model adapter. |
| `resolveModelSelection(model, options?)` | The same, for a bare user-typed id: it searches the running and configured endpoint catalogs and resolves only when one source has it. |

Browser providers:

| Export | What it is |
| --- | --- |
| `BROWSER_PROVIDER_NAMES` | The named cloud providers `provider: { provider }` accepts. |
| `REST_BROWSER_PROVIDER_NAMES` | The six of those with create/list/get/stop session APIs. |
| `browserProviderInfo(name)` | Display name, docs URL, API-key env var, and `lifecycle` (`rest` or `connect`), or `null`. |
| `describeCdpUrl(value)` | A CDP URL with its credentials and key-like query values masked, for logging. |
| `createProviderSession(name, options?)` | Start a managed box (`betterwright boxes start`). |
| `listProviderSessions(name, options?)` | List boxes (`betterwright boxes list`). |
| `getProviderSession(name, id, options?)` | Fetch one box. |
| `stopProviderSession(name, id, options?)` | Release a box so the provider stops billing it. |

Bringing your own browser, and what each provider changes about the guard
proxy, is covered in [browser-providers.md](browser-providers.md).

The entrypoint also exports the public types, so
`import type { BetterWrightOptions, RunResult } from "betterwright/sdk"` works
without a second import path.

## Errors, timeouts, and the worker

Knowing which failures come back as a value and which ones throw is most of
what makes an integration robust.

**Anything that happened inside the browser is a value.** `run()`,
`fillCredential()`, and the live-view calls resolve with an envelope; when the
snippet threw, the page navigated away, the call timed out, or the worker died
mid-call, that envelope is `{ ok: false, error }`. Check `ok` and decide.
Throwing `BrowserError(result.error)` is the conventional way to turn one into
an exception.

**The client throws only when it cannot work at all**, and always as a
`BrowserError` (or a `TypeError` for a bad option at construction):

- the client was closed with `close()` and is then used again;
- the worker process could not start: the error says why, with the exit code
  or signal and the worker's last stderr lines (a missing module, a syntax
  error in a patched install, a permission problem), and it is raised as soon
  as the process exits rather than after a timeout;
- the worker started but never printed its ready handshake within the start
  timeout, 15 s by default. Set `BETTERWRIGHT_WORKER_START_TIMEOUT_MS` higher
  on a cold disk or a small ARM board; the hung process is killed either way.

**Timeouts restart the worker and its managed browser context, not the client.**
Each call takes a `timeout` in seconds (default `defaultTimeout`, 30; minimum 5).
On expiry the call resolves `{ ok: false, error }` and the worker is torn down.
The usual worker error is `Playwright code timed out after …ms`; the client's
fallback watchdog reports `Execution timed out after …s`. Do not match only one
literal error message.

The `BetterWright` client remains reusable: calls arriving during teardown wait
for the replacement worker. Live page handles and in-memory session `state` are
lost, while the persistent profile's on-disk cookies and logins remain available
to the replacement browser. Externally owned browser targets have their own
lifetime rules; the host-owned Electron tab is not destroyed by a worker restart.
After an interrupted action, inspect application state before deciding whether
it is safe to replay it. See [cancellation](javascript.md#cancellation) for the
related `run(code, { signal })` contract.

**Provider API calls are bounded.** A cloud-provider launch and every
`createProviderSession` / `listProviderSessions` / `getProviderSession` /
`stopProviderSession` call gives the provider 30 s and then fails with a
message that says so. A network failure names its cause
(`getaddrinfo ENOTFOUND …`, `ECONNREFUSED`).

**`withBrowser` owns the lifetime.** It closes the client whether your
function returns or throws. If your function throws and `close()` then fails
as well, your function's error is the one you see. A missing callback or a
non-object options bag is a `TypeError` before any client exists.

## Runnable example

[examples/typescript/sdk.ts](../examples/typescript/sdk.ts) demonstrates the same
workflow as a file you can run with `node examples/typescript/sdk.ts`.
