@oya-ai/browser 1.0.125 → 1.0.129
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.
- package/README.md +12 -0
- package/dist/index.cjs +104 -0
- package/dist/index.d.cts +68 -2
- package/dist/index.d.ts +68 -2
- package/dist/index.js +104 -0
- package/package.json +13 -2
package/README.md
CHANGED
|
@@ -50,6 +50,18 @@ Node.js 20 or newer; the examples are ES modules. `OYA_BASE_URL` points at a sel
|
|
|
50
50
|
control plane. On Node.js 24+, `await using browser = await oya.browser.start()` stops it
|
|
51
51
|
when the scope exits, errors included.
|
|
52
52
|
|
|
53
|
+
|
|
54
|
+
## For AI agents
|
|
55
|
+
|
|
56
|
+
No key yet? Get your own, and use your person's own browser with their logins:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
await Oya.signup({ email: 'person@example.com' }); // saved to ~/.oya/config.json
|
|
60
|
+
const browser = await new Oya().desktop.connect(); // pairs the Oya desktop app
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Written for agents: [oyabrowser.com/llms.txt](https://oyabrowser.com/llms.txt).
|
|
64
|
+
|
|
53
65
|
## Portal automation: record once, replay with new inputs
|
|
54
66
|
|
|
55
67
|
This example adapts the portal-automation project's workflow: reuse a persona, attach to an existing browser or start one, run a prompt the first time, then replay its saved playbook. The portal, workflow names, and request values below are fictional. Supply your own test portal and credentials through environment variables; adapt the task to its actual pages.
|
package/dist/index.cjs
CHANGED
|
@@ -70,6 +70,9 @@ var Status = {
|
|
|
70
70
|
/** A browser did not come up in time. */
|
|
71
71
|
GATEWAY_TIMEOUT: 504
|
|
72
72
|
};
|
|
73
|
+
var DESKTOP_PROVIDER = "oya-desktop";
|
|
74
|
+
var DESKTOP_TIMEOUT_MS = 3e5;
|
|
75
|
+
var DESKTOP_DOWNLOAD_URL = "https://oyabrowser.com/#download";
|
|
73
76
|
|
|
74
77
|
// src/errors.ts
|
|
75
78
|
var OyaError = class extends Error {
|
|
@@ -111,6 +114,18 @@ function savedConfig() {
|
|
|
111
114
|
return {};
|
|
112
115
|
}
|
|
113
116
|
}
|
|
117
|
+
var PRIVATE_FILE = 384;
|
|
118
|
+
var JSON_INDENT = 2;
|
|
119
|
+
function saveKey(apiKey, baseUrl) {
|
|
120
|
+
const fs = builtin("node:fs");
|
|
121
|
+
const file2 = configFile();
|
|
122
|
+
if (!fs || !file2 || savedConfig().apiKey) return false;
|
|
123
|
+
fs.mkdirSync(file2.slice(0, file2.lastIndexOf("/")), { recursive: true });
|
|
124
|
+
fs.writeFileSync(file2, JSON.stringify({ ...savedConfig(), apiKey, baseUrl }, null, JSON_INDENT), {
|
|
125
|
+
mode: PRIVATE_FILE
|
|
126
|
+
});
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
114
129
|
|
|
115
130
|
// src/client.ts
|
|
116
131
|
var OPTIONS_ORIGIN = { apiKeyFrom: "the apiKey option", baseUrlFrom: "the baseUrl option", savedKey: false };
|
|
@@ -855,6 +870,83 @@ async function assertNotEnded(checks, id) {
|
|
|
855
870
|
}
|
|
856
871
|
}
|
|
857
872
|
|
|
873
|
+
// src/api/agent.ts
|
|
874
|
+
var NO_KEY = "signup";
|
|
875
|
+
var solves = (crypto, challenge, nonce, zeros) => crypto.createHash("sha256").update(challenge + nonce).digest("hex").startsWith(zeros);
|
|
876
|
+
function solve({ challenge, difficulty }) {
|
|
877
|
+
const crypto = builtin("node:crypto");
|
|
878
|
+
if (!crypto) throw new Error("Oya.signup() needs Node 22.3 or newer.");
|
|
879
|
+
const zeros = "0".repeat(difficulty);
|
|
880
|
+
let nonce = 0;
|
|
881
|
+
while (!solves(crypto, challenge, nonce, zeros)) nonce++;
|
|
882
|
+
return String(nonce);
|
|
883
|
+
}
|
|
884
|
+
async function signup(options) {
|
|
885
|
+
const http = createHttp({ baseUrl: options.baseUrl, fetch: options.fetch, apiKey: NO_KEY });
|
|
886
|
+
const puzzle = await http.request("GET", "/api/auth/agent/challenge");
|
|
887
|
+
const body = { email: options.email, challenge: puzzle.challenge, nonce: solve(puzzle) };
|
|
888
|
+
const answer = await http.request("POST", "/api/auth/agent/signup", body);
|
|
889
|
+
const saved = options.save !== false && saveKey(answer.api_key, http.baseUrl);
|
|
890
|
+
return { apiKey: answer.api_key, claimUrl: answer.claim_url, cloudBrowsers: answer.cloud_browsers, saved };
|
|
891
|
+
}
|
|
892
|
+
var desktopOf = (all, persona) => all.find(
|
|
893
|
+
(b) => b.provider === DESKTOP_PROVIDER && b.health !== "dead" && (!persona || b.persona === persona || b.personaName === persona)
|
|
894
|
+
);
|
|
895
|
+
async function pairingLink(http, persona) {
|
|
896
|
+
const { code } = await http.request("POST", "/api/pairing", {
|
|
897
|
+
persona
|
|
898
|
+
});
|
|
899
|
+
const server = `${http.baseUrl.replace(/^http/, "ws")}/ws`;
|
|
900
|
+
return `oya://connect?code=${encodeURIComponent(code)}&server=${encodeURIComponent(server)}`;
|
|
901
|
+
}
|
|
902
|
+
var OPENERS = {
|
|
903
|
+
darwin: ["open"],
|
|
904
|
+
linux: ["xdg-open"],
|
|
905
|
+
win32: ["rundll32", "url.dll,FileProtocolHandler"]
|
|
906
|
+
};
|
|
907
|
+
function openLink(link) {
|
|
908
|
+
const platform = globalThis.process?.platform ?? "";
|
|
909
|
+
const cp = builtin("node:child_process");
|
|
910
|
+
if (!cp || !Object.hasOwn(OPENERS, platform)) return;
|
|
911
|
+
const [cmd, ...args] = OPENERS[platform];
|
|
912
|
+
const child = cp.spawn(cmd, [...args, link], { detached: true, stdio: "ignore" });
|
|
913
|
+
child.on("error", () => {
|
|
914
|
+
});
|
|
915
|
+
child.unref();
|
|
916
|
+
}
|
|
917
|
+
async function waitForDesktop(list, link, timeoutMs, persona) {
|
|
918
|
+
const deadline = Date.now() + timeoutMs;
|
|
919
|
+
while (Date.now() < deadline) {
|
|
920
|
+
const found = desktopOf(await list(), persona);
|
|
921
|
+
if (found) return found;
|
|
922
|
+
await new Promise((r) => setTimeout(r, READY_POLL_MS));
|
|
923
|
+
}
|
|
924
|
+
const how = `Install it from ${DESKTOP_DOWNLOAD_URL} if needed, then open ${link} and click Connect.`;
|
|
925
|
+
throw new OyaError(`The Oya desktop app did not connect. ${how}`, Status.GATEWAY_TIMEOUT, { link });
|
|
926
|
+
}
|
|
927
|
+
async function handleOn(http, id) {
|
|
928
|
+
const found = await http.request("GET", `/api/browsers/${segment(id)}`);
|
|
929
|
+
const started = { id, provider: DESKTOP_PROVIDER, persona: found.persona || "default", cdpUrl: found.cdpUrl };
|
|
930
|
+
return new Browser(http, { ...started, status: "ready", reused: true }, false);
|
|
931
|
+
}
|
|
932
|
+
var desktopApi = (http, list) => ({
|
|
933
|
+
/**
|
|
934
|
+
* The person's own desktop browser, signed in to their sites. When it is not
|
|
935
|
+
* connected yet this pairs it: it opens a link, the person clicks Connect in
|
|
936
|
+
* the app (with "Import my logins" ticked, their Chrome logins come along),
|
|
937
|
+
* and this waits until it is up. With `persona`, a running app signed in as
|
|
938
|
+
* another persona is switched the same way: the link reconnects it as this one.
|
|
939
|
+
*/
|
|
940
|
+
connect: async (options = {}) => {
|
|
941
|
+
const running = desktopOf(await list(), options.persona);
|
|
942
|
+
if (running) return handleOn(http(), running.id);
|
|
943
|
+
const link = await pairingLink(http(), options.persona);
|
|
944
|
+
if (options.open !== false) openLink(link);
|
|
945
|
+
const found = await waitForDesktop(list, link, options.timeoutMs ?? DESKTOP_TIMEOUT_MS, options.persona);
|
|
946
|
+
return handleOn(http(), found.id);
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
|
|
858
950
|
// src/file.ts
|
|
859
951
|
var MAX_FILE_BYTES = MAX_FILE_MB * BYTES_PER_MB;
|
|
860
952
|
var NODE_FS = "node:fs/promises";
|
|
@@ -934,6 +1026,18 @@ var Oya = class {
|
|
|
934
1026
|
() => this.http,
|
|
935
1027
|
(id, timeoutMs) => this.waitUntilConnected(id, timeoutMs)
|
|
936
1028
|
);
|
|
1029
|
+
/** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
|
|
1030
|
+
desktop = desktopApi(
|
|
1031
|
+
() => this.http,
|
|
1032
|
+
() => this.browser.list()
|
|
1033
|
+
);
|
|
1034
|
+
/**
|
|
1035
|
+
* An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
|
|
1036
|
+
* Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
|
|
1037
|
+
*/
|
|
1038
|
+
static signup(options) {
|
|
1039
|
+
return signup(options);
|
|
1040
|
+
}
|
|
937
1041
|
/** Durable operational controls, including disconnected and cleanup-pending sessions. */
|
|
938
1042
|
control = controlApi(() => this.http);
|
|
939
1043
|
/** Playbooks saved with `browser.toPlaybook()`. */
|
package/dist/index.d.cts
CHANGED
|
@@ -272,7 +272,9 @@ type LlmProvider = 'openai' | 'anthropic'
|
|
|
272
272
|
| 'gemini'
|
|
273
273
|
/** Gemini Enterprise, ex-Vertex AI. Express mode by default; set `openai_base_url` to a
|
|
274
274
|
* project-scoped `.../endpoints/openapi` endpoint to use an enterprise project. */
|
|
275
|
-
| 'vertex'
|
|
275
|
+
| 'vertex'
|
|
276
|
+
/** OpenRouter: one key for many vendors' models, named vendor/model (e.g. 'anthropic/claude-sonnet-5'). */
|
|
277
|
+
| 'openrouter';
|
|
276
278
|
/**
|
|
277
279
|
* Which runtime this key's `'oya-cloud'` browsers run on. `'docker'` and `'k8s'`
|
|
278
280
|
* run on the deployment's own daemon or cluster, exactly as its operator set them up;
|
|
@@ -422,6 +424,30 @@ interface Config extends Omit<ConfigUpdate, 'llm_provider' | 'browser_provider'
|
|
|
422
424
|
/** Whether those fields are set. */
|
|
423
425
|
configured: boolean;
|
|
424
426
|
}>;
|
|
427
|
+
/** Every LLM provider and the models it offers; the console, desktop app and CLI build their pickers from this. */
|
|
428
|
+
llm_catalog: LlmCatalogEntry[];
|
|
429
|
+
}
|
|
430
|
+
/** One LLM provider in `Config.llm_catalog`. */
|
|
431
|
+
interface LlmCatalogEntry {
|
|
432
|
+
/** The `llm_provider` value. */
|
|
433
|
+
id: LlmProvider;
|
|
434
|
+
/** Its name. */
|
|
435
|
+
label: string;
|
|
436
|
+
/** How its key looks. */
|
|
437
|
+
hint: string;
|
|
438
|
+
/** Where to get a key. */
|
|
439
|
+
keysUrl: string;
|
|
440
|
+
/** Its endpoint. */
|
|
441
|
+
base: string;
|
|
442
|
+
/** The model a key runs on when it sets no `chat_model`. */
|
|
443
|
+
model: string;
|
|
444
|
+
/** The models to offer (OpenRouter's is its live list); any other id works as `chat_model` too. */
|
|
445
|
+
models: Array<{
|
|
446
|
+
/** The id the provider's API expects. */
|
|
447
|
+
id: string;
|
|
448
|
+
/** A readable name. */
|
|
449
|
+
label: string;
|
|
450
|
+
}>;
|
|
425
451
|
}
|
|
426
452
|
|
|
427
453
|
/** A failed API call or browser command. */
|
|
@@ -918,6 +944,37 @@ interface OyaOptions {
|
|
|
918
944
|
/** A fetch to use instead of the global one, for tests, proxies or old runtimes. */
|
|
919
945
|
fetch?: typeof globalThis.fetch;
|
|
920
946
|
}
|
|
947
|
+
/** What `Oya.signup()` takes. */
|
|
948
|
+
interface SignupOptions {
|
|
949
|
+
/** The email of the person the agent works for. They get the claim link that unlocks cloud browsers. */
|
|
950
|
+
email: string;
|
|
951
|
+
/** Defaults to OYA_BASE_URL, then https://oyabrowser.com. */
|
|
952
|
+
baseUrl?: string;
|
|
953
|
+
/** Save the key to ~/.oya/config.json, where `new Oya()` finds it later. On by default; a key already saved there is never replaced. */
|
|
954
|
+
save?: boolean;
|
|
955
|
+
/** A fetch to use instead of the global one. */
|
|
956
|
+
fetch?: typeof globalThis.fetch;
|
|
957
|
+
}
|
|
958
|
+
/** A new key from `Oya.signup()`. */
|
|
959
|
+
interface Signup {
|
|
960
|
+
/** The key. Shown once; keep it secret. */
|
|
961
|
+
apiKey: string;
|
|
962
|
+
/** Send this to the person: opening it while signed in to Oya claims the key and unlocks cloud browsers. */
|
|
963
|
+
claimUrl: string;
|
|
964
|
+
/** Whether cloud browsers work yet (false until the key is claimed). */
|
|
965
|
+
cloudBrowsers: boolean;
|
|
966
|
+
/** Whether the key was saved to ~/.oya/config.json. */
|
|
967
|
+
saved: boolean;
|
|
968
|
+
}
|
|
969
|
+
/** What `oya.desktop.connect()` takes. */
|
|
970
|
+
interface DesktopOptions {
|
|
971
|
+
/** The persona (id or name) the desktop app signs in as, switching a running app that is on another one. Defaults to the key's own. */
|
|
972
|
+
persona?: string;
|
|
973
|
+
/** Open the pairing link on this machine (the default). Off, the link is only in the error when the app does not connect. */
|
|
974
|
+
open?: boolean;
|
|
975
|
+
/** How long to wait for the person to click Connect in the app. Five minutes by default. */
|
|
976
|
+
timeoutMs?: number;
|
|
977
|
+
}
|
|
921
978
|
|
|
922
979
|
/** Where the key and the address came from, so a message names the one to fix. */
|
|
923
980
|
interface Origin {
|
|
@@ -1412,6 +1469,15 @@ declare class Oya {
|
|
|
1412
1469
|
stop: (ids: string[] | "all") => Promise<StopManyResult>;
|
|
1413
1470
|
stopAll: () => Promise<number>;
|
|
1414
1471
|
};
|
|
1472
|
+
/** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
|
|
1473
|
+
readonly desktop: {
|
|
1474
|
+
connect: (options?: DesktopOptions) => Promise<Browser>;
|
|
1475
|
+
};
|
|
1476
|
+
/**
|
|
1477
|
+
* An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
|
|
1478
|
+
* Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
|
|
1479
|
+
*/
|
|
1480
|
+
static signup(options: SignupOptions): Promise<Signup>;
|
|
1415
1481
|
/** Durable operational controls, including disconnected and cleanup-pending sessions. */
|
|
1416
1482
|
readonly control: {
|
|
1417
1483
|
createWebhook: (url: string, types?: string[]) => Promise<Webhook>;
|
|
@@ -1513,4 +1579,4 @@ declare class Oya {
|
|
|
1513
1579
|
private waitUntilConnected;
|
|
1514
1580
|
}
|
|
1515
1581
|
|
|
1516
|
-
export { type Activity, type Analysis, type AnalyzeOptions, type AttentionRequest, type Block, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Cookie, type CookieFormat, type EcsAuth, type EcsConfig, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PageFormat, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type RecoverOptions, Run, type RunData, type RunInfo, type RunResult, type SandboxRuntime, type SiteCredentials, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
|
|
1582
|
+
export { type Activity, type Analysis, type AnalyzeOptions, type AttentionRequest, type Block, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Cookie, type CookieFormat, type DesktopOptions, type EcsAuth, type EcsConfig, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmCatalogEntry, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PageFormat, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type RecoverOptions, Run, type RunData, type RunInfo, type RunResult, type SandboxRuntime, type Signup, type SignupOptions, type SiteCredentials, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
|
package/dist/index.d.ts
CHANGED
|
@@ -272,7 +272,9 @@ type LlmProvider = 'openai' | 'anthropic'
|
|
|
272
272
|
| 'gemini'
|
|
273
273
|
/** Gemini Enterprise, ex-Vertex AI. Express mode by default; set `openai_base_url` to a
|
|
274
274
|
* project-scoped `.../endpoints/openapi` endpoint to use an enterprise project. */
|
|
275
|
-
| 'vertex'
|
|
275
|
+
| 'vertex'
|
|
276
|
+
/** OpenRouter: one key for many vendors' models, named vendor/model (e.g. 'anthropic/claude-sonnet-5'). */
|
|
277
|
+
| 'openrouter';
|
|
276
278
|
/**
|
|
277
279
|
* Which runtime this key's `'oya-cloud'` browsers run on. `'docker'` and `'k8s'`
|
|
278
280
|
* run on the deployment's own daemon or cluster, exactly as its operator set them up;
|
|
@@ -422,6 +424,30 @@ interface Config extends Omit<ConfigUpdate, 'llm_provider' | 'browser_provider'
|
|
|
422
424
|
/** Whether those fields are set. */
|
|
423
425
|
configured: boolean;
|
|
424
426
|
}>;
|
|
427
|
+
/** Every LLM provider and the models it offers; the console, desktop app and CLI build their pickers from this. */
|
|
428
|
+
llm_catalog: LlmCatalogEntry[];
|
|
429
|
+
}
|
|
430
|
+
/** One LLM provider in `Config.llm_catalog`. */
|
|
431
|
+
interface LlmCatalogEntry {
|
|
432
|
+
/** The `llm_provider` value. */
|
|
433
|
+
id: LlmProvider;
|
|
434
|
+
/** Its name. */
|
|
435
|
+
label: string;
|
|
436
|
+
/** How its key looks. */
|
|
437
|
+
hint: string;
|
|
438
|
+
/** Where to get a key. */
|
|
439
|
+
keysUrl: string;
|
|
440
|
+
/** Its endpoint. */
|
|
441
|
+
base: string;
|
|
442
|
+
/** The model a key runs on when it sets no `chat_model`. */
|
|
443
|
+
model: string;
|
|
444
|
+
/** The models to offer (OpenRouter's is its live list); any other id works as `chat_model` too. */
|
|
445
|
+
models: Array<{
|
|
446
|
+
/** The id the provider's API expects. */
|
|
447
|
+
id: string;
|
|
448
|
+
/** A readable name. */
|
|
449
|
+
label: string;
|
|
450
|
+
}>;
|
|
425
451
|
}
|
|
426
452
|
|
|
427
453
|
/** A failed API call or browser command. */
|
|
@@ -918,6 +944,37 @@ interface OyaOptions {
|
|
|
918
944
|
/** A fetch to use instead of the global one, for tests, proxies or old runtimes. */
|
|
919
945
|
fetch?: typeof globalThis.fetch;
|
|
920
946
|
}
|
|
947
|
+
/** What `Oya.signup()` takes. */
|
|
948
|
+
interface SignupOptions {
|
|
949
|
+
/** The email of the person the agent works for. They get the claim link that unlocks cloud browsers. */
|
|
950
|
+
email: string;
|
|
951
|
+
/** Defaults to OYA_BASE_URL, then https://oyabrowser.com. */
|
|
952
|
+
baseUrl?: string;
|
|
953
|
+
/** Save the key to ~/.oya/config.json, where `new Oya()` finds it later. On by default; a key already saved there is never replaced. */
|
|
954
|
+
save?: boolean;
|
|
955
|
+
/** A fetch to use instead of the global one. */
|
|
956
|
+
fetch?: typeof globalThis.fetch;
|
|
957
|
+
}
|
|
958
|
+
/** A new key from `Oya.signup()`. */
|
|
959
|
+
interface Signup {
|
|
960
|
+
/** The key. Shown once; keep it secret. */
|
|
961
|
+
apiKey: string;
|
|
962
|
+
/** Send this to the person: opening it while signed in to Oya claims the key and unlocks cloud browsers. */
|
|
963
|
+
claimUrl: string;
|
|
964
|
+
/** Whether cloud browsers work yet (false until the key is claimed). */
|
|
965
|
+
cloudBrowsers: boolean;
|
|
966
|
+
/** Whether the key was saved to ~/.oya/config.json. */
|
|
967
|
+
saved: boolean;
|
|
968
|
+
}
|
|
969
|
+
/** What `oya.desktop.connect()` takes. */
|
|
970
|
+
interface DesktopOptions {
|
|
971
|
+
/** The persona (id or name) the desktop app signs in as, switching a running app that is on another one. Defaults to the key's own. */
|
|
972
|
+
persona?: string;
|
|
973
|
+
/** Open the pairing link on this machine (the default). Off, the link is only in the error when the app does not connect. */
|
|
974
|
+
open?: boolean;
|
|
975
|
+
/** How long to wait for the person to click Connect in the app. Five minutes by default. */
|
|
976
|
+
timeoutMs?: number;
|
|
977
|
+
}
|
|
921
978
|
|
|
922
979
|
/** Where the key and the address came from, so a message names the one to fix. */
|
|
923
980
|
interface Origin {
|
|
@@ -1412,6 +1469,15 @@ declare class Oya {
|
|
|
1412
1469
|
stop: (ids: string[] | "all") => Promise<StopManyResult>;
|
|
1413
1470
|
stopAll: () => Promise<number>;
|
|
1414
1471
|
};
|
|
1472
|
+
/** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
|
|
1473
|
+
readonly desktop: {
|
|
1474
|
+
connect: (options?: DesktopOptions) => Promise<Browser>;
|
|
1475
|
+
};
|
|
1476
|
+
/**
|
|
1477
|
+
* An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
|
|
1478
|
+
* Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
|
|
1479
|
+
*/
|
|
1480
|
+
static signup(options: SignupOptions): Promise<Signup>;
|
|
1415
1481
|
/** Durable operational controls, including disconnected and cleanup-pending sessions. */
|
|
1416
1482
|
readonly control: {
|
|
1417
1483
|
createWebhook: (url: string, types?: string[]) => Promise<Webhook>;
|
|
@@ -1513,4 +1579,4 @@ declare class Oya {
|
|
|
1513
1579
|
private waitUntilConnected;
|
|
1514
1580
|
}
|
|
1515
1581
|
|
|
1516
|
-
export { type Activity, type Analysis, type AnalyzeOptions, type AttentionRequest, type Block, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Cookie, type CookieFormat, type EcsAuth, type EcsConfig, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PageFormat, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type RecoverOptions, Run, type RunData, type RunInfo, type RunResult, type SandboxRuntime, type SiteCredentials, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
|
|
1582
|
+
export { type Activity, type Analysis, type AnalyzeOptions, type AttentionRequest, type Block, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Cookie, type CookieFormat, type DesktopOptions, type EcsAuth, type EcsConfig, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmCatalogEntry, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PageFormat, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type RecoverOptions, Run, type RunData, type RunInfo, type RunResult, type SandboxRuntime, type Signup, type SignupOptions, type SiteCredentials, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,9 @@ var Status = {
|
|
|
38
38
|
/** A browser did not come up in time. */
|
|
39
39
|
GATEWAY_TIMEOUT: 504
|
|
40
40
|
};
|
|
41
|
+
var DESKTOP_PROVIDER = "oya-desktop";
|
|
42
|
+
var DESKTOP_TIMEOUT_MS = 3e5;
|
|
43
|
+
var DESKTOP_DOWNLOAD_URL = "https://oyabrowser.com/#download";
|
|
41
44
|
|
|
42
45
|
// src/errors.ts
|
|
43
46
|
var OyaError = class extends Error {
|
|
@@ -79,6 +82,18 @@ function savedConfig() {
|
|
|
79
82
|
return {};
|
|
80
83
|
}
|
|
81
84
|
}
|
|
85
|
+
var PRIVATE_FILE = 384;
|
|
86
|
+
var JSON_INDENT = 2;
|
|
87
|
+
function saveKey(apiKey, baseUrl) {
|
|
88
|
+
const fs = builtin("node:fs");
|
|
89
|
+
const file2 = configFile();
|
|
90
|
+
if (!fs || !file2 || savedConfig().apiKey) return false;
|
|
91
|
+
fs.mkdirSync(file2.slice(0, file2.lastIndexOf("/")), { recursive: true });
|
|
92
|
+
fs.writeFileSync(file2, JSON.stringify({ ...savedConfig(), apiKey, baseUrl }, null, JSON_INDENT), {
|
|
93
|
+
mode: PRIVATE_FILE
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
82
97
|
|
|
83
98
|
// src/client.ts
|
|
84
99
|
var OPTIONS_ORIGIN = { apiKeyFrom: "the apiKey option", baseUrlFrom: "the baseUrl option", savedKey: false };
|
|
@@ -823,6 +838,83 @@ async function assertNotEnded(checks, id) {
|
|
|
823
838
|
}
|
|
824
839
|
}
|
|
825
840
|
|
|
841
|
+
// src/api/agent.ts
|
|
842
|
+
var NO_KEY = "signup";
|
|
843
|
+
var solves = (crypto, challenge, nonce, zeros) => crypto.createHash("sha256").update(challenge + nonce).digest("hex").startsWith(zeros);
|
|
844
|
+
function solve({ challenge, difficulty }) {
|
|
845
|
+
const crypto = builtin("node:crypto");
|
|
846
|
+
if (!crypto) throw new Error("Oya.signup() needs Node 22.3 or newer.");
|
|
847
|
+
const zeros = "0".repeat(difficulty);
|
|
848
|
+
let nonce = 0;
|
|
849
|
+
while (!solves(crypto, challenge, nonce, zeros)) nonce++;
|
|
850
|
+
return String(nonce);
|
|
851
|
+
}
|
|
852
|
+
async function signup(options) {
|
|
853
|
+
const http = createHttp({ baseUrl: options.baseUrl, fetch: options.fetch, apiKey: NO_KEY });
|
|
854
|
+
const puzzle = await http.request("GET", "/api/auth/agent/challenge");
|
|
855
|
+
const body = { email: options.email, challenge: puzzle.challenge, nonce: solve(puzzle) };
|
|
856
|
+
const answer = await http.request("POST", "/api/auth/agent/signup", body);
|
|
857
|
+
const saved = options.save !== false && saveKey(answer.api_key, http.baseUrl);
|
|
858
|
+
return { apiKey: answer.api_key, claimUrl: answer.claim_url, cloudBrowsers: answer.cloud_browsers, saved };
|
|
859
|
+
}
|
|
860
|
+
var desktopOf = (all, persona) => all.find(
|
|
861
|
+
(b) => b.provider === DESKTOP_PROVIDER && b.health !== "dead" && (!persona || b.persona === persona || b.personaName === persona)
|
|
862
|
+
);
|
|
863
|
+
async function pairingLink(http, persona) {
|
|
864
|
+
const { code } = await http.request("POST", "/api/pairing", {
|
|
865
|
+
persona
|
|
866
|
+
});
|
|
867
|
+
const server = `${http.baseUrl.replace(/^http/, "ws")}/ws`;
|
|
868
|
+
return `oya://connect?code=${encodeURIComponent(code)}&server=${encodeURIComponent(server)}`;
|
|
869
|
+
}
|
|
870
|
+
var OPENERS = {
|
|
871
|
+
darwin: ["open"],
|
|
872
|
+
linux: ["xdg-open"],
|
|
873
|
+
win32: ["rundll32", "url.dll,FileProtocolHandler"]
|
|
874
|
+
};
|
|
875
|
+
function openLink(link) {
|
|
876
|
+
const platform = globalThis.process?.platform ?? "";
|
|
877
|
+
const cp = builtin("node:child_process");
|
|
878
|
+
if (!cp || !Object.hasOwn(OPENERS, platform)) return;
|
|
879
|
+
const [cmd, ...args] = OPENERS[platform];
|
|
880
|
+
const child = cp.spawn(cmd, [...args, link], { detached: true, stdio: "ignore" });
|
|
881
|
+
child.on("error", () => {
|
|
882
|
+
});
|
|
883
|
+
child.unref();
|
|
884
|
+
}
|
|
885
|
+
async function waitForDesktop(list, link, timeoutMs, persona) {
|
|
886
|
+
const deadline = Date.now() + timeoutMs;
|
|
887
|
+
while (Date.now() < deadline) {
|
|
888
|
+
const found = desktopOf(await list(), persona);
|
|
889
|
+
if (found) return found;
|
|
890
|
+
await new Promise((r) => setTimeout(r, READY_POLL_MS));
|
|
891
|
+
}
|
|
892
|
+
const how = `Install it from ${DESKTOP_DOWNLOAD_URL} if needed, then open ${link} and click Connect.`;
|
|
893
|
+
throw new OyaError(`The Oya desktop app did not connect. ${how}`, Status.GATEWAY_TIMEOUT, { link });
|
|
894
|
+
}
|
|
895
|
+
async function handleOn(http, id) {
|
|
896
|
+
const found = await http.request("GET", `/api/browsers/${segment(id)}`);
|
|
897
|
+
const started = { id, provider: DESKTOP_PROVIDER, persona: found.persona || "default", cdpUrl: found.cdpUrl };
|
|
898
|
+
return new Browser(http, { ...started, status: "ready", reused: true }, false);
|
|
899
|
+
}
|
|
900
|
+
var desktopApi = (http, list) => ({
|
|
901
|
+
/**
|
|
902
|
+
* The person's own desktop browser, signed in to their sites. When it is not
|
|
903
|
+
* connected yet this pairs it: it opens a link, the person clicks Connect in
|
|
904
|
+
* the app (with "Import my logins" ticked, their Chrome logins come along),
|
|
905
|
+
* and this waits until it is up. With `persona`, a running app signed in as
|
|
906
|
+
* another persona is switched the same way: the link reconnects it as this one.
|
|
907
|
+
*/
|
|
908
|
+
connect: async (options = {}) => {
|
|
909
|
+
const running = desktopOf(await list(), options.persona);
|
|
910
|
+
if (running) return handleOn(http(), running.id);
|
|
911
|
+
const link = await pairingLink(http(), options.persona);
|
|
912
|
+
if (options.open !== false) openLink(link);
|
|
913
|
+
const found = await waitForDesktop(list, link, options.timeoutMs ?? DESKTOP_TIMEOUT_MS, options.persona);
|
|
914
|
+
return handleOn(http(), found.id);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
|
|
826
918
|
// src/file.ts
|
|
827
919
|
var MAX_FILE_BYTES = MAX_FILE_MB * BYTES_PER_MB;
|
|
828
920
|
var NODE_FS = "node:fs/promises";
|
|
@@ -902,6 +994,18 @@ var Oya = class {
|
|
|
902
994
|
() => this.http,
|
|
903
995
|
(id, timeoutMs) => this.waitUntilConnected(id, timeoutMs)
|
|
904
996
|
);
|
|
997
|
+
/** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
|
|
998
|
+
desktop = desktopApi(
|
|
999
|
+
() => this.http,
|
|
1000
|
+
() => this.browser.list()
|
|
1001
|
+
);
|
|
1002
|
+
/**
|
|
1003
|
+
* An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
|
|
1004
|
+
* Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
|
|
1005
|
+
*/
|
|
1006
|
+
static signup(options) {
|
|
1007
|
+
return signup(options);
|
|
1008
|
+
}
|
|
905
1009
|
/** Durable operational controls, including disconnected and cleanup-pending sessions. */
|
|
906
1010
|
control = controlApi(() => this.http);
|
|
907
1011
|
/** Playbooks saved with `browser.toPlaybook()`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oya-ai/browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.129",
|
|
4
4
|
"description": "Rotate thousands of browsers behind one API, personas, proxies, stealth, CAPTCHA and MFA.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://oyabrowser.com",
|
|
@@ -22,7 +22,18 @@
|
|
|
22
22
|
"browser-use",
|
|
23
23
|
"captcha",
|
|
24
24
|
"fingerprint",
|
|
25
|
-
"mcp"
|
|
25
|
+
"mcp",
|
|
26
|
+
"ai-agent",
|
|
27
|
+
"agent-browser",
|
|
28
|
+
"browser-agent",
|
|
29
|
+
"agent-skills",
|
|
30
|
+
"claude-code",
|
|
31
|
+
"codex",
|
|
32
|
+
"openclaw",
|
|
33
|
+
"hermes-agent",
|
|
34
|
+
"computer-use",
|
|
35
|
+
"mcp-server",
|
|
36
|
+
"llms-txt"
|
|
26
37
|
],
|
|
27
38
|
"type": "module",
|
|
28
39
|
"main": "./dist/index.cjs",
|