@oya-ai/browser 1.0.57 โ†’ 1.0.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +234 -54
  3. package/package.json +22 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oya AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,104 +1,284 @@
1
1
  # @oya-ai/browser
2
2
 
3
- Thousands of browsers behind one API: personas, proxies, stealth, CAPTCHA and MFA. The browser can run on Oya Cloud, Browserbase, Steel, Anchor, Browser Use, your own machines, or any CDP URL. Which one is a setting on your API key, so your code never changes.
3
+ <p align="center">
4
+ <strong>Thousands of browsers behind one API: personas, residential proxies, measured stealth, CAPTCHA, and MFA.</strong>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@oya-ai/browser"><img src="https://img.shields.io/npm/v/@oya-ai/browser?color=39ed35&label=@oya-ai/browser&logo=npm" alt="NPM Version"></a>
9
+ <a href="https://github.com/OyadotAI/oya-browser/blob/main/packages/sdk/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?color=39ed35" alt="License: MIT"></a>
10
+ <a href="https://www.typescriptlang.org"><img src="https://img.shields.io/badge/TypeScript-Ready-3178C6.svg?logo=typescript&logoColor=white" alt="TypeScript"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg" alt="Node Version"></a>
12
+ <a href="https://bundlephobia.com/package/@oya-ai/browser"><img src="https://img.shields.io/bundlephobia/minzip/@oya-ai/browser?color=39ed35" alt="Bundle Size"></a>
13
+ </p>
14
+
15
+ ---
16
+
17
+ Orchestrate browser instances running on **Oya Cloud sandboxes**, **Browserbase**, **Steel**, **Anchor**, **Browser Use**, or your own **private Chrome fleet**.
18
+
19
+ Oya does for browser vendors what OpenRouter does for LLM providers. The vendor behind a browser is a setting on your API key, or one `provider` parameter, so switching vendors never means rewriting your agent.
4
20
 
5
21
  ```bash
6
- npm i @oya-ai/browser
22
+ npm install @oya-ai/browser
7
23
  ```
8
24
 
9
- Get an API key at [browser.getoya.ai](https://browser.getoya.ai) and export it as `OYA_API_KEY`. Every snippet below runs as-is.
25
+ Get an API key at [browser.getoya.ai](https://browser.getoya.ai) or self-host your control plane, and set `OYA_API_KEY`.
10
26
 
11
- ## Quickstart
27
+ ---
28
+
29
+ ## โšก Quickstart
30
+
31
+ ### Natural Language Driving
12
32
 
13
33
  ```ts
14
- import { Oya } from '@oya-ai/browser';
34
+ import { Oya } from "@oya-ai/browser";
35
+
36
+ const oya = new Oya(); // Reads process.env.OYA_API_KEY
15
37
 
16
- const oya = new Oya(); // reads OYA_API_KEY
17
- await using browser = await oya.browser.start(); // stopped when the block exits, even on error
18
- await browser.goto('https://news.ycombinator.com');
19
- console.log(await browser.ask('What are the top 3 stories?'));
38
+ // Explicit resource management (Node 24+ / TS 5.2+)
39
+ // Stops the browser automatically when the scope exits, even on error
40
+ await using browser = await oya.browser.start({ persona: "auto", captcha: "auto" });
41
+
42
+ await browser.goto("https://news.ycombinator.com");
43
+ const answer = await browser.ask("What are the top 3 stories and their points?");
44
+ console.log(answer);
20
45
  ```
21
46
 
22
- `await using` needs Node 24+ or TypeScript 5.2+. Otherwise call `await browser.stop()` in a `finally` block. `ask()` uses the AI model set on your key in the dashboard.
47
+ > **Universal Lifecycle:** If you are not using `await using`, manage lifecycle with `try / finally`:
48
+ > ```ts
49
+ > const browser = await oya.browser.start();
50
+ > try {
51
+ > await browser.goto("https://example.com");
52
+ > } finally {
53
+ > await browser.stop();
54
+ > }
55
+ > ```
56
+
57
+ ---
23
58
 
24
- ## Personas
59
+ ## ๐Ÿ›ก๏ธ Deterministic Personas (Anti-Ban Identity)
25
60
 
26
- A persona is one device: fingerprint, cookie jar and exit IP, the same on every run.
61
+ A persona is a permanent, mathematically seeded device identity: **fingerprint + cookie jar + residential proxy**, identical on every run to eliminate bot-farm and device-farm flags.
27
62
 
28
63
  ```ts
29
- import { Oya } from '@oya-ai/browser';
64
+ import { Oya } from "@oya-ai/browser";
30
65
 
31
66
  const oya = new Oya();
67
+
68
+ // Create a persistent persona
32
69
  const persona = await oya.personas.create({
33
- name: 'us-shopper',
34
- prefs: { platform: 'MacIntel', timezone: 'America/New_York', locale: 'en-US' }, // fixed for life
35
- proxy: { geo: 'US' },
70
+ name: "us-shopper",
71
+ prefs: { platform: "MacIntel", timezone: "America/New_York", locale: "en-US" },
72
+ proxy: { geo: "US" },
73
+ maxConcurrent: 2, // Concurrency cap prevents device-farm detection
36
74
  });
37
- await using browser = await oya.browser.start({ persona: persona.id }); // or persona: 'auto' to rotate
38
- await browser.goto('https://example.com');
39
- console.log(persona.fingerprint.platform, persona.fingerprint.timezone); // the same on every run
75
+
76
+ // Launch a browser with this persona (or persona: 'auto' for least-recently-used)
77
+ await using browser = await oya.browser.start({ persona: persona.id });
78
+ await browser.goto("https://www.amazon.com");
79
+
80
+ // Hardware attributes remain byte-identical on subsequent sessions
81
+ console.log(persona.fingerprint.platform, persona.fingerprint.timezone);
82
+
83
+ // Need another device of the same class? Clone it with an empty cookie jar:
84
+ // const altDevice = await oya.personas.clone(persona.id, { name: "us-shopper-02" });
40
85
  ```
41
86
 
42
- ## CAPTCHA
87
+ ---
88
+
89
+ ## ๐Ÿง  Structured Agent Analysis & Interaction
43
90
 
44
- The vendor's own solver when it has one, otherwise your CapSolver or 2Captcha key.
91
+ Skip messy DOM traversal. Get clean markdown and numbered interactive elements:
45
92
 
46
93
  ```ts
47
- import { Oya } from '@oya-ai/browser';
94
+ import { Oya } from "@oya-ai/browser";
48
95
 
49
96
  const oya = new Oya();
50
- await using browser = await oya.browser.start(); // or start({ captcha: 'auto' }) to clear them on every goto()
51
- await browser.goto('https://www.google.com/recaptcha/api2/demo');
52
- console.log(await browser.solveCaptcha()); // { present, solved, method: 'provider' | 'solver' | 'none' }
97
+ await using browser = await oya.browser.start();
98
+ await browser.goto("https://github.com/trending");
99
+
100
+ // Analyze page: returns markdown and visible numbered elements
101
+ const { markdown, elements } = await browser.analyze();
102
+ console.log(markdown.slice(0, 300));
103
+
104
+ // Interact using numbered element IDs:
105
+ const firstRepo = elements.find((el) => el.tag === "a" && el.href?.includes("/stargazers"));
106
+ if (firstRepo) {
107
+ await browser.click(firstRepo.id); // clicks [data-ac-id="firstRepo.id"]
108
+ }
53
109
  ```
54
110
 
55
- ## MFA
111
+ ---
56
112
 
57
- The TOTP seed is sealed on the persona, and `completeMfa()` enters the code.
113
+ ## ๐Ÿงฉ Challenge Handling: Automated CAPTCHA & Sealed MFA
58
114
 
59
115
  ```ts
60
- import { Oya } from '@oya-ai/browser';
116
+ import { Oya } from "@oya-ai/browser";
61
117
 
62
118
  const oya = new Oya();
63
- const persona = await oya.personas.create({ name: 'billing-admin' });
64
- await oya.personas.setMfa(persona.id, { type: 'totp', secret: process.env.TOTP_SECRET! }); // sealed, never read back
119
+
120
+ // 1. Seal a TOTP secret on a persona (encrypted with AES-256-GCM at rest, never exposed over API)
121
+ const persona = await oya.personas.create({ name: "finance-admin" });
122
+ await oya.personas.setMfa(persona.id, {
123
+ type: "totp",
124
+ secret: process.env.TOTP_SECRET!,
125
+ });
65
126
 
66
127
  await using browser = await oya.browser.start({ persona: persona.id });
67
- await browser.goto(process.env.MFA_URL!); // after your login step: the page asking for the code
68
- const mfa = await browser.completeMfa(); // method: 'totp' | 'email' | 'sms' | 'handoff'
69
- if (!mfa.completed) console.log('A person can finish it here:', mfa.liveViewUrl);
128
+
129
+ // 2. Clear CAPTCHAs automatically (uses vendor solver or CapSolver/2Captcha fallback)
130
+ await browser.goto("https://www.google.com/recaptcha/api2/demo");
131
+ const captcha = await browser.solveCaptcha();
132
+ console.log("CAPTCHA Solved:", captcha.solved, "via", captcha.method);
133
+
134
+ // 3. Complete Two-Factor Authentication
135
+ await browser.goto(process.env.MFA_LOGIN_URL!);
136
+ const mfa = await browser.completeMfa();
137
+
138
+ if (!mfa.completed && mfa.liveViewUrl) {
139
+ // Hand off to human operator if interactive push notification or WebAuthn is needed
140
+ console.log("Interactive handoff required at:", mfa.liveViewUrl);
141
+ }
70
142
  ```
71
143
 
72
- ## Playwright, Puppeteer, Stagehand
144
+ ---
73
145
 
74
- `browser.cdpUrl` is a standard CDP endpoint on any CDP vendor.
146
+ ## ๐Ÿ”Œ Universal CDP Gateway (Playwright & Puppeteer)
147
+
148
+ Every browser exposes an authenticated `browser.cdpUrl` routed through Oya's gateway. Connect standard Playwright, Puppeteer, or Stagehand:
75
149
 
76
150
  ```ts
77
- import { chromium } from 'playwright-core';
78
- import { Oya } from '@oya-ai/browser';
151
+ import { chromium } from "playwright-core";
152
+ import { Oya } from "@oya-ai/browser";
79
153
 
80
154
  const oya = new Oya();
81
- await using browser = await oya.browser.start({ provider: 'browserbase' }); // or steel, anchor, browseruse
155
+
156
+ // Run on any underlying provider: browserbase, steel, anchor, browseruse, or oya-cloud
157
+ await using browser = await oya.browser.start({ provider: "browserbase" });
158
+
159
+ // Connect Playwright directly over Oya's gateway
82
160
  const context = (await chromium.connectOverCDP(browser.cdpUrl!)).contexts()[0];
83
- const page = context.pages()[0] ?? await context.newPage();
84
- await page.goto('https://example.com');
85
- console.log(await page.title());
161
+ const page = context.pages()[0] ?? (await context.newPage());
162
+
163
+ await page.goto("https://news.ycombinator.com");
164
+ console.log("Page Title:", await page.title());
165
+ ```
166
+
167
+ ---
168
+
169
+ ## ๐Ÿ“š Complete API Reference
170
+
171
+ ### Initialization
172
+
173
+ ```ts
174
+ import { Oya } from "@oya-ai/browser";
175
+
176
+ const oya = new Oya({
177
+ apiKey: "oya_...", // default: process.env.OYA_API_KEY
178
+ baseUrl: "https://browser.getoya.ai", // default: OYA_BASE_URL, then the hosted service
179
+ timeoutMs: 60_000, // per request
180
+ // fetch: customFetch, // any fetch-compatible implementation
181
+ });
86
182
  ```
87
183
 
88
- Vendor keys are set once on your Oya key in the dashboard. They never appear in code.
184
+ ### Browser Operations (`oya.browser`)
185
+
186
+ | Method | Signature | Description |
187
+ |:---|:---|:---|
188
+ | `start(options)` | `(options?: StartOptions) => Promise<Browser>` | Start a browser and wait until it is ready for commands |
189
+ | `get(id)` | `(id: string) => Promise<Browser>` | Reattach to an existing running browser |
190
+ | `list()` | `() => Promise<BrowserInfo[]>` | List all active running browser sessions |
191
+ | `stop(ids \| 'all')` | `(ids: string[] \| 'all') => Promise<{ stopped: number; results: StopResult[] }>` | Stop target browsers or all browsers |
192
+ | `stopAll()` | `() => Promise<number>` | Terminate all active browser sessions |
193
+
194
+ #### `StartOptions`
195
+
196
+ - `persona?: 'default' | 'auto' | string` โ€” Assign persistent identity
197
+ - `provider?: 'oya-cloud' | 'oya-selfhosted' | 'browserbase' | 'steel' | 'anchor' | 'browseruse' | 'cdp'`
198
+ - `wsUrl?: string`: required only for the `'cdp'` provider
199
+ - `name?: string`: display name in the console and `oya ls`
200
+ - `captcha?: 'auto' | 'off'` โ€” Automatically solve CAPTCHAs on navigation
201
+ - `queueMs?: number` โ€” Wait duration for fleet capacity (ms)
202
+ - `budgetUsd?: number` โ€” Enforce budget limit for session
203
+ - `idempotencyKey?: string` โ€” Safe retry token
204
+ - `governed?: boolean` โ€” Enforce strict isolation policies
89
205
 
90
- ## API
206
+ ### Browser Instance Methods (`browser.*`)
207
+
208
+ | Method | Returns | Description |
209
+ |:---|:---|:---|
210
+ | `goto(url)` | `Promise<void>` | Navigate to URL (with optional auto-CAPTCHA) |
211
+ | `ask(prompt)` | `Promise<string>` | Natural-language AI driving using key's configured model |
212
+ | `analyze()` | `Promise<Analysis>` | Returns markdown representation and numbered elements |
213
+ | `elements()` | `Promise<Element[]>` | Returns only visible interactable elements |
214
+ | `click(elementId)` | `Promise<void>` | Click element by numeric ID from `analyze()` |
215
+ | `type(elementId, text)` | `Promise<{ suggestions_visible?: boolean }>` | Type text into specified element |
216
+ | `pressKey(key)` | `Promise<void>` | Dispatch keyboard key event (e.g. `'Enter'`) |
217
+ | `scroll(dir, amount?, at?)` | `Promise<void>` | Scroll `'up' \| 'down' \| 'top' \| 'bottom'` |
218
+ | `waitFor(selector, timeout?)`| `Promise<void>` | Wait for DOM selector |
219
+ | `screenshot()` | `Promise<string>` | Capture page as base64 image data URL |
220
+ | `url()` | `Promise<string>` | Current active tab URL |
221
+ | `tabs()` | `Promise<Tab[]>` | List open tabs |
222
+ | `openTab(url?)` | `Promise<string>` | Open a new tab |
223
+ | `switchTab(tabId)` | `Promise<void>` | Switch active tab |
224
+ | `closeTab(tabId)` | `Promise<void>` | Close target tab |
225
+ | `solveCaptcha()` | `Promise<CaptchaResult>` | Detect and solve on-screen CAPTCHA |
226
+ | `completeMfa()` | `Promise<MfaResult>` | Resolve TOTP/SMS MFA or return `liveViewUrl` |
227
+ | `liveViewUrl()` | `string` | SSE JPEG stream URL for sub-second human takeover |
228
+ | `status()` | `Promise<BrowserDetail>` | Instance metrics, health, and recent activity log |
229
+ | `stop()` | `Promise<StopResult>` | Tear down sandbox and release CDP session |
230
+
231
+ ### Persona Management (`oya.personas`)
232
+
233
+ | Method | Description |
234
+ |:---|:---|
235
+ | `create({ name?, prefs?, proxy?, maxConcurrent? })` | Create new deterministic device identity |
236
+ | `list()` | List all saved personas and active concurrency |
237
+ | `get(id)` | Get persona profile details |
238
+ | `update(id, changes)` | Update name, concurrency limit, or proxy geo |
239
+ | `clone(id, options)` | Create fresh persona with same device traits but empty cookie jar |
240
+ | `preview(prefs)` | Preview generated hardware fingerprint before creating |
241
+ | `options()` | Available platforms, timezones, and valid locales |
242
+ | `pinProxy(id, proxyId)` | Bind persona permanently to a residential proxy exit node |
243
+ | `remove(id)` | Delete persona and associated cookie jar |
244
+ | `setMfa(id, config)` | Store TOTP secret (sealed at rest with AES-256-GCM) |
245
+ | `clearMfa(id)` | Remove MFA secret from persona |
246
+
247
+ ### Durable Governance & Control (`oya.control`)
248
+
249
+ | Method | Description |
250
+ |:---|:---|
251
+ | `overview()` | Fleet overview, spend, sessions, and active rate cards |
252
+ | `sessions()` | List all durable sessions (including cleanup-pending) |
253
+ | `session(id)` | Get detailed session execution state |
254
+ | `takeover(id, 'acquire' \| 'release' \| 'resume')` | Manage human control leases |
255
+ | `ticket(id)` | Generate single-use connection ticket for secure handoff |
256
+ | `events(after?)` | Stream append-only audit event log |
257
+ | `createCredential(options)` | Mint scoped service credential (`viewer` / `operator` / `administrator`) |
258
+ | `createWebhook(url, types)` | Register HMAC-signed webhook for fleet lifecycle events |
259
+
260
+ ---
261
+
262
+ ## ๐Ÿšจ Error Handling
263
+
264
+ All failed API and command operations throw an `OyaError`:
265
+
266
+ ```ts
267
+ import { Oya, OyaError } from "@oya-ai/browser";
268
+
269
+ try {
270
+ const oya = new Oya();
271
+ await oya.browser.start({ persona: "invalid-id" });
272
+ } catch (err) {
273
+ if (err instanceof OyaError) {
274
+ console.error(`Oya API Error (${err.status}):`, err.message);
275
+ console.error("Payload:", err.body);
276
+ }
277
+ }
278
+ ```
91
279
 
92
- | | |
93
- |---|---|
94
- | `new Oya({ apiKey?, baseUrl?, timeoutMs? })` | Defaults to `OYA_API_KEY` and `OYA_BASE_URL` |
95
- | `oya.browser` | `start(options)`, `get(id)`, `list()`, `stop(ids \| 'all')`, `stopAll()` |
96
- | `browser` | `goto`, `ask`, `analyze`, `elements`, `click`, `type`, `pressKey`, `scroll`, `waitFor`, `screenshot`, `url`, `tabs`, `openTab`, `switchTab`, `closeTab`, `solveCaptcha`, `completeMfa`, `liveViewUrl`, `status`, `stop`, `cdpUrl` |
97
- | `oya.personas` | `list`, `get`, `create`, `update`, `clone`, `preview`, `options`, `pinProxy`, `remove`, `setMfa`, `clearMfa` |
98
- | `oya.config` | `get()`, `set(values)`: model, provider and solver keys for this API key |
99
- | `oya.control` | Sessions, human takeover, events, members, credentials and webhooks |
100
- | `oya.usage()` | What this key has spent |
280
+ ---
101
281
 
102
- Errors are thrown as `OyaError` with `status` and `body`. ESM and CommonJS, fully typed, no runtime dependencies, Node 18+.
282
+ ## ๐Ÿ“„ License
103
283
 
104
- More: [examples](https://github.com/OyadotAI/AgentChrome/tree/main/examples) ยท [docs](https://browser.getoya.ai/docs) ยท [CLI](https://www.npmjs.com/package/@oya-ai/cli)
284
+ MIT ยฉ [Oya](https://getoya.ai)
package/package.json CHANGED
@@ -1,8 +1,29 @@
1
1
  {
2
2
  "name": "@oya-ai/browser",
3
- "version": "1.0.57",
3
+ "version": "1.0.58",
4
4
  "description": "Rotate thousands of browsers behind one API โ€” personas, proxies, stealth, CAPTCHA and MFA.",
5
5
  "license": "MIT",
6
+ "homepage": "https://browser.getoya.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/OyadotAI/oya-browser.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "bugs": "https://github.com/OyadotAI/oya-browser/issues",
13
+ "keywords": [
14
+ "browser",
15
+ "ai-agents",
16
+ "cdp",
17
+ "playwright",
18
+ "puppeteer",
19
+ "headless-chrome",
20
+ "browserbase",
21
+ "steel",
22
+ "browser-use",
23
+ "captcha",
24
+ "fingerprint",
25
+ "mcp"
26
+ ],
6
27
  "type": "module",
7
28
  "main": "./dist/index.cjs",
8
29
  "module": "./dist/index.js",