@oya-ai/cli 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/dist/index.js +136 -21
- package/package.json +14 -3
package/dist/index.js
CHANGED
|
@@ -586,7 +586,8 @@ function wouldClone(where) {
|
|
|
586
586
|
|
|
587
587
|
// src/install/llm.ts
|
|
588
588
|
var LLM_PRESETS = {
|
|
589
|
-
|
|
589
|
+
// Same defaults as the server's catalog (server/src/modules/config/llm-catalog.ts).
|
|
590
|
+
anthropic: { base: "https://api.anthropic.com/v1", model: "claude-opus-5", label: "Anthropic (Claude)" },
|
|
590
591
|
openai: { base: "https://api.openai.com/v1", model: "gpt-4o-mini", label: "OpenAI" },
|
|
591
592
|
gemini: {
|
|
592
593
|
base: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
@@ -598,14 +599,16 @@ var LLM_PRESETS = {
|
|
|
598
599
|
base: "https://aiplatform.googleapis.com/v1/publishers/google",
|
|
599
600
|
model: "gemini-2.5-flash",
|
|
600
601
|
label: "Gemini Enterprise (Vertex AI)"
|
|
601
|
-
}
|
|
602
|
+
},
|
|
603
|
+
openrouter: { base: "https://openrouter.ai/api/v1", model: "anthropic/claude-sonnet-5", label: "OpenRouter" }
|
|
602
604
|
};
|
|
603
605
|
var LLM_OPTIONS = [
|
|
604
606
|
{ id: "anthropic", label: "Anthropic (Claude)" },
|
|
605
607
|
{ id: "openai", label: "OpenAI" },
|
|
606
608
|
{ id: "gemini", label: "Gemini (Google)" },
|
|
607
609
|
{ id: "vertex", label: "Gemini Enterprise (Vertex AI)", note: "express-mode API key" },
|
|
608
|
-
{ id: "
|
|
610
|
+
{ id: "openrouter", label: "OpenRouter", note: "one key for Claude, GPT, Gemini, Grok, DeepSeek and more" },
|
|
611
|
+
{ id: "compatible", label: "An OpenAI-compatible endpoint", note: "Together, Groq, Azure" },
|
|
609
612
|
{ id: "local", label: "A local model", note: "Ollama, vLLM, LM Studio" },
|
|
610
613
|
{ id: "skip", label: "Skip", note: "no agent control; add it later with `oya config`" }
|
|
611
614
|
];
|
|
@@ -1423,6 +1426,7 @@ var Status2 = {
|
|
|
1423
1426
|
/** A quota or a persona concurrency cap. */
|
|
1424
1427
|
TOO_MANY_REQUESTS: 429
|
|
1425
1428
|
};
|
|
1429
|
+
var MODELS_SHOWN = 8;
|
|
1426
1430
|
|
|
1427
1431
|
// src/config.ts
|
|
1428
1432
|
var FILE = join6(process.env.OYA_CONFIG_HOME || join6(homedir(), ".oya"), "config.json");
|
|
@@ -1499,6 +1503,9 @@ var Status3 = {
|
|
|
1499
1503
|
/** A browser did not come up in time. */
|
|
1500
1504
|
GATEWAY_TIMEOUT: 504
|
|
1501
1505
|
};
|
|
1506
|
+
var DESKTOP_PROVIDER = "oya-desktop";
|
|
1507
|
+
var DESKTOP_TIMEOUT_MS = 3e5;
|
|
1508
|
+
var DESKTOP_DOWNLOAD_URL = "https://oyabrowser.com/#download";
|
|
1502
1509
|
var OyaError = class extends Error {
|
|
1503
1510
|
/** The HTTP status, or the SDK's own status for errors it raises itself. */
|
|
1504
1511
|
status;
|
|
@@ -1536,6 +1543,18 @@ function savedConfig() {
|
|
|
1536
1543
|
return {};
|
|
1537
1544
|
}
|
|
1538
1545
|
}
|
|
1546
|
+
var PRIVATE_FILE = 384;
|
|
1547
|
+
var JSON_INDENT3 = 2;
|
|
1548
|
+
function saveKey(apiKey, baseUrl) {
|
|
1549
|
+
const fs = builtin("node:fs");
|
|
1550
|
+
const file2 = configFile();
|
|
1551
|
+
if (!fs || !file2 || savedConfig().apiKey) return false;
|
|
1552
|
+
fs.mkdirSync(file2.slice(0, file2.lastIndexOf("/")), { recursive: true });
|
|
1553
|
+
fs.writeFileSync(file2, JSON.stringify({ ...savedConfig(), apiKey, baseUrl }, null, JSON_INDENT3), {
|
|
1554
|
+
mode: PRIVATE_FILE
|
|
1555
|
+
});
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1539
1558
|
var OPTIONS_ORIGIN = { apiKeyFrom: "the apiKey option", baseUrlFrom: "the baseUrl option", savedKey: false };
|
|
1540
1559
|
var Http = class {
|
|
1541
1560
|
/** Stores where to call, with which key, how long to wait, which fetch to use, and where the key and address came from. */
|
|
@@ -2257,6 +2276,81 @@ async function assertNotEnded(checks, id) {
|
|
|
2257
2276
|
if (!(e instanceof OyaError) || e.status !== Status3.NOT_FOUND) throw e;
|
|
2258
2277
|
}
|
|
2259
2278
|
}
|
|
2279
|
+
var NO_KEY = "signup";
|
|
2280
|
+
var solves = (crypto, challenge, nonce, zeros) => crypto.createHash("sha256").update(challenge + nonce).digest("hex").startsWith(zeros);
|
|
2281
|
+
function solve({ challenge, difficulty }) {
|
|
2282
|
+
const crypto = builtin("node:crypto");
|
|
2283
|
+
if (!crypto) throw new Error("Oya.signup() needs Node 22.3 or newer.");
|
|
2284
|
+
const zeros = "0".repeat(difficulty);
|
|
2285
|
+
let nonce = 0;
|
|
2286
|
+
while (!solves(crypto, challenge, nonce, zeros)) nonce++;
|
|
2287
|
+
return String(nonce);
|
|
2288
|
+
}
|
|
2289
|
+
async function signup(options) {
|
|
2290
|
+
const http = createHttp({ baseUrl: options.baseUrl, fetch: options.fetch, apiKey: NO_KEY });
|
|
2291
|
+
const puzzle = await http.request("GET", "/api/auth/agent/challenge");
|
|
2292
|
+
const body = { email: options.email, challenge: puzzle.challenge, nonce: solve(puzzle) };
|
|
2293
|
+
const answer = await http.request("POST", "/api/auth/agent/signup", body);
|
|
2294
|
+
const saved = options.save !== false && saveKey(answer.api_key, http.baseUrl);
|
|
2295
|
+
return { apiKey: answer.api_key, claimUrl: answer.claim_url, cloudBrowsers: answer.cloud_browsers, saved };
|
|
2296
|
+
}
|
|
2297
|
+
var desktopOf = (all, persona) => all.find(
|
|
2298
|
+
(b) => b.provider === DESKTOP_PROVIDER && b.health !== "dead" && (!persona || b.persona === persona || b.personaName === persona)
|
|
2299
|
+
);
|
|
2300
|
+
async function pairingLink(http, persona) {
|
|
2301
|
+
const { code } = await http.request("POST", "/api/pairing", {
|
|
2302
|
+
persona
|
|
2303
|
+
});
|
|
2304
|
+
const server = `${http.baseUrl.replace(/^http/, "ws")}/ws`;
|
|
2305
|
+
return `oya://connect?code=${encodeURIComponent(code)}&server=${encodeURIComponent(server)}`;
|
|
2306
|
+
}
|
|
2307
|
+
var OPENERS = {
|
|
2308
|
+
darwin: ["open"],
|
|
2309
|
+
linux: ["xdg-open"],
|
|
2310
|
+
win32: ["rundll32", "url.dll,FileProtocolHandler"]
|
|
2311
|
+
};
|
|
2312
|
+
function openLink(link) {
|
|
2313
|
+
const platform = globalThis.process?.platform ?? "";
|
|
2314
|
+
const cp = builtin("node:child_process");
|
|
2315
|
+
if (!cp || !Object.hasOwn(OPENERS, platform)) return;
|
|
2316
|
+
const [cmd, ...args] = OPENERS[platform];
|
|
2317
|
+
const child = cp.spawn(cmd, [...args, link], { detached: true, stdio: "ignore" });
|
|
2318
|
+
child.on("error", () => {
|
|
2319
|
+
});
|
|
2320
|
+
child.unref();
|
|
2321
|
+
}
|
|
2322
|
+
async function waitForDesktop(list2, link, timeoutMs, persona) {
|
|
2323
|
+
const deadline = Date.now() + timeoutMs;
|
|
2324
|
+
while (Date.now() < deadline) {
|
|
2325
|
+
const found = desktopOf(await list2(), persona);
|
|
2326
|
+
if (found) return found;
|
|
2327
|
+
await new Promise((r) => setTimeout(r, READY_POLL_MS2));
|
|
2328
|
+
}
|
|
2329
|
+
const how = `Install it from ${DESKTOP_DOWNLOAD_URL} if needed, then open ${link} and click Connect.`;
|
|
2330
|
+
throw new OyaError(`The Oya desktop app did not connect. ${how}`, Status3.GATEWAY_TIMEOUT, { link });
|
|
2331
|
+
}
|
|
2332
|
+
async function handleOn(http, id) {
|
|
2333
|
+
const found = await http.request("GET", `/api/browsers/${segment(id)}`);
|
|
2334
|
+
const started = { id, provider: DESKTOP_PROVIDER, persona: found.persona || "default", cdpUrl: found.cdpUrl };
|
|
2335
|
+
return new Browser(http, { ...started, status: "ready", reused: true }, false);
|
|
2336
|
+
}
|
|
2337
|
+
var desktopApi = (http, list2) => ({
|
|
2338
|
+
/**
|
|
2339
|
+
* The person's own desktop browser, signed in to their sites. When it is not
|
|
2340
|
+
* connected yet this pairs it: it opens a link, the person clicks Connect in
|
|
2341
|
+
* the app (with "Import my logins" ticked, their Chrome logins come along),
|
|
2342
|
+
* and this waits until it is up. With `persona`, a running app signed in as
|
|
2343
|
+
* another persona is switched the same way: the link reconnects it as this one.
|
|
2344
|
+
*/
|
|
2345
|
+
connect: async (options = {}) => {
|
|
2346
|
+
const running = desktopOf(await list2(), options.persona);
|
|
2347
|
+
if (running) return handleOn(http(), running.id);
|
|
2348
|
+
const link = await pairingLink(http(), options.persona);
|
|
2349
|
+
if (options.open !== false) openLink(link);
|
|
2350
|
+
const found = await waitForDesktop(list2, link, options.timeoutMs ?? DESKTOP_TIMEOUT_MS, options.persona);
|
|
2351
|
+
return handleOn(http(), found.id);
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2260
2354
|
var MAX_FILE_BYTES = MAX_FILE_MB * BYTES_PER_MB;
|
|
2261
2355
|
var Oya = class {
|
|
2262
2356
|
/** The one HTTP path every call goes through. */
|
|
@@ -2270,6 +2364,18 @@ var Oya = class {
|
|
|
2270
2364
|
() => this.http,
|
|
2271
2365
|
(id, timeoutMs) => this.waitUntilConnected(id, timeoutMs)
|
|
2272
2366
|
);
|
|
2367
|
+
/** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
|
|
2368
|
+
desktop = desktopApi(
|
|
2369
|
+
() => this.http,
|
|
2370
|
+
() => this.browser.list()
|
|
2371
|
+
);
|
|
2372
|
+
/**
|
|
2373
|
+
* An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
|
|
2374
|
+
* Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
|
|
2375
|
+
*/
|
|
2376
|
+
static signup(options) {
|
|
2377
|
+
return signup(options);
|
|
2378
|
+
}
|
|
2273
2379
|
/** Durable operational controls, including disconnected and cleanup-pending sessions. */
|
|
2274
2380
|
control = controlApi(() => this.http);
|
|
2275
2381
|
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
@@ -2403,30 +2509,39 @@ function signedIn(baseUrl) {
|
|
|
2403
2509
|
}
|
|
2404
2510
|
|
|
2405
2511
|
// src/commands/init.ts
|
|
2406
|
-
var
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2512
|
+
var LEGACY_CATALOG = [
|
|
2513
|
+
{ id: "anthropic", label: "Claude (Anthropic)", model: "claude-opus-5" },
|
|
2514
|
+
{ id: "openai", label: "OpenAI", model: "gpt-4o-mini" },
|
|
2515
|
+
{ id: "gemini", label: "Gemini (Google)", model: "gemini-3.8-flash" },
|
|
2516
|
+
{ id: "vertex", label: "Gemini Enterprise (Vertex AI)", model: "gemini-2.5-flash" }
|
|
2517
|
+
];
|
|
2518
|
+
var catalogOf = (current) => current.llm_catalog?.length ? current.llm_catalog : LEGACY_CATALOG;
|
|
2412
2519
|
var modelOptions = (current) => [
|
|
2413
|
-
{ id:
|
|
2414
|
-
{ id: "openai", label: "OpenAI" },
|
|
2415
|
-
{ id: "gemini", label: "Gemini (Google)" },
|
|
2416
|
-
{ id: "vertex", label: "Gemini Enterprise (Vertex AI)" },
|
|
2520
|
+
...catalogOf(current).map((p) => ({ id: p.id, label: p.label })),
|
|
2417
2521
|
{ id: "skip", label: "Skip", note: current.has_openai_key ? "keep what is configured" : "no agent control" }
|
|
2418
2522
|
];
|
|
2523
|
+
function llmUpdates(current, provider, key, model) {
|
|
2524
|
+
const updates = { llm_provider: provider, chat_model: model || null };
|
|
2525
|
+
if (key) updates.openai_api_key = key;
|
|
2526
|
+
if (provider !== current.llm_provider) updates.openai_base_url = null;
|
|
2527
|
+
return updates;
|
|
2528
|
+
}
|
|
2529
|
+
function showModels(choice) {
|
|
2530
|
+
const ids = (choice.models || []).map((m) => m.id);
|
|
2531
|
+
const more = ids.length > MODELS_SHOWN ? ", \u2026" : "";
|
|
2532
|
+
if (ids.length) console.log(` Models: ${ids.slice(0, MODELS_SHOWN).join(", ")}${more}`);
|
|
2533
|
+
}
|
|
2419
2534
|
async function askModel(current, updates) {
|
|
2420
2535
|
console.log("\n\u2500\u2500 1. Your model \u2500\u2500");
|
|
2421
2536
|
const llm = await choose("Which LLM should agents use?", modelOptions(current));
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
const key = await askSecret(`${
|
|
2427
|
-
|
|
2428
|
-
const model = await ask("Default model:",
|
|
2429
|
-
|
|
2537
|
+
const choice = catalogOf(current).find((p) => p.id === llm);
|
|
2538
|
+
if (choice) Object.assign(updates, await askModelDetails(current, choice));
|
|
2539
|
+
}
|
|
2540
|
+
async function askModelDetails(current, choice) {
|
|
2541
|
+
const key = await askSecret(`${choice.label} API key:`);
|
|
2542
|
+
showModels(choice);
|
|
2543
|
+
const model = await ask("Default model:", choice.model);
|
|
2544
|
+
return llmUpdates(current, choice.id, key, model);
|
|
2430
2545
|
}
|
|
2431
2546
|
var needsNote = (p) => p.needs.some((f) => f.endsWith("_url")) ? "needs its ws:// address" : "needs an API key";
|
|
2432
2547
|
function fieldLabel(p, field) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oya-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.129",
|
|
4
4
|
"description": "Command line for the Oya browser control plane.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://oyabrowser.com",
|
|
@@ -19,7 +19,18 @@
|
|
|
19
19
|
"browserbase",
|
|
20
20
|
"steel",
|
|
21
21
|
"browser-use",
|
|
22
|
-
"mcp"
|
|
22
|
+
"mcp",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"agent-browser",
|
|
25
|
+
"browser-agent",
|
|
26
|
+
"agent-skills",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"codex",
|
|
29
|
+
"openclaw",
|
|
30
|
+
"hermes-agent",
|
|
31
|
+
"computer-use",
|
|
32
|
+
"mcp-server",
|
|
33
|
+
"llms-txt"
|
|
23
34
|
],
|
|
24
35
|
"type": "module",
|
|
25
36
|
"bin": {
|
|
@@ -39,7 +50,7 @@
|
|
|
39
50
|
"access": "public"
|
|
40
51
|
},
|
|
41
52
|
"devDependencies": {
|
|
42
|
-
"@oya-ai/browser": "1.0.
|
|
53
|
+
"@oya-ai/browser": "1.0.129",
|
|
43
54
|
"@types/node": "^20.19.43",
|
|
44
55
|
"tsup": "^8.0.0",
|
|
45
56
|
"typescript": "^5.4.0"
|