@oya-ai/cli 1.0.124 → 1.0.127

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 (2) hide show
  1. package/dist/index.js +133 -21
  2. 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
- anthropic: { base: "https://api.anthropic.com/v1", model: "claude-sonnet-5", label: "Anthropic (Claude)" },
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: "compatible", label: "An OpenAI-compatible endpoint", note: "OpenRouter, Together, Groq, Azure" },
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,78 @@ 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) => all.find((b) => b.provider === DESKTOP_PROVIDER && b.health !== "dead");
2298
+ async function pairingLink(http, persona) {
2299
+ const { code } = await http.request("POST", "/api/pairing", {
2300
+ persona
2301
+ });
2302
+ const server = `${http.baseUrl.replace(/^http/, "ws")}/ws`;
2303
+ return `oya://connect?code=${encodeURIComponent(code)}&server=${encodeURIComponent(server)}`;
2304
+ }
2305
+ var OPENERS = {
2306
+ darwin: ["open"],
2307
+ linux: ["xdg-open"],
2308
+ win32: ["rundll32", "url.dll,FileProtocolHandler"]
2309
+ };
2310
+ function openLink(link) {
2311
+ const platform = globalThis.process?.platform ?? "";
2312
+ const cp = builtin("node:child_process");
2313
+ if (!cp || !Object.hasOwn(OPENERS, platform)) return;
2314
+ const [cmd, ...args] = OPENERS[platform];
2315
+ const child = cp.spawn(cmd, [...args, link], { detached: true, stdio: "ignore" });
2316
+ child.on("error", () => {
2317
+ });
2318
+ child.unref();
2319
+ }
2320
+ async function waitForDesktop(list2, link, timeoutMs) {
2321
+ const deadline = Date.now() + timeoutMs;
2322
+ while (Date.now() < deadline) {
2323
+ const found = desktopOf(await list2());
2324
+ if (found) return found;
2325
+ await new Promise((r) => setTimeout(r, READY_POLL_MS2));
2326
+ }
2327
+ const how = `Install it from ${DESKTOP_DOWNLOAD_URL} if needed, then open ${link} and click Connect.`;
2328
+ throw new OyaError(`The Oya desktop app did not connect. ${how}`, Status3.GATEWAY_TIMEOUT, { link });
2329
+ }
2330
+ async function handleOn(http, id) {
2331
+ const found = await http.request("GET", `/api/browsers/${segment(id)}`);
2332
+ const started = { id, provider: DESKTOP_PROVIDER, persona: found.persona || "default", cdpUrl: found.cdpUrl };
2333
+ return new Browser(http, { ...started, status: "ready", reused: true }, false);
2334
+ }
2335
+ var desktopApi = (http, list2) => ({
2336
+ /**
2337
+ * The person's own desktop browser, signed in to their sites. When it is not
2338
+ * connected yet this pairs it: it opens a link, the person clicks Connect in
2339
+ * the app (with "Import my logins" ticked, their Chrome logins come along),
2340
+ * and this waits until it is up.
2341
+ */
2342
+ connect: async (options = {}) => {
2343
+ const running = desktopOf(await list2());
2344
+ if (running) return handleOn(http(), running.id);
2345
+ const link = await pairingLink(http(), options.persona);
2346
+ if (options.open !== false) openLink(link);
2347
+ const found = await waitForDesktop(list2, link, options.timeoutMs ?? DESKTOP_TIMEOUT_MS);
2348
+ return handleOn(http(), found.id);
2349
+ }
2350
+ });
2260
2351
  var MAX_FILE_BYTES = MAX_FILE_MB * BYTES_PER_MB;
2261
2352
  var Oya = class {
2262
2353
  /** The one HTTP path every call goes through. */
@@ -2270,6 +2361,18 @@ var Oya = class {
2270
2361
  () => this.http,
2271
2362
  (id, timeoutMs) => this.waitUntilConnected(id, timeoutMs)
2272
2363
  );
2364
+ /** The person's own desktop browser, with their logins: `await oya.desktop.connect()` pairs it when it is not up yet. */
2365
+ desktop = desktopApi(
2366
+ () => this.http,
2367
+ () => this.browser.list()
2368
+ );
2369
+ /**
2370
+ * An agent's own key, with no person or dashboard: `await Oya.signup({ email })`.
2371
+ * Saved to ~/.oya/config.json, so a later `new Oya()` finds it.
2372
+ */
2373
+ static signup(options) {
2374
+ return signup(options);
2375
+ }
2273
2376
  /** Durable operational controls, including disconnected and cleanup-pending sessions. */
2274
2377
  control = controlApi(() => this.http);
2275
2378
  /** Playbooks saved with `browser.toPlaybook()`. */
@@ -2403,30 +2506,39 @@ function signedIn(baseUrl) {
2403
2506
  }
2404
2507
 
2405
2508
  // src/commands/init.ts
2406
- var PRESETS = {
2407
- anthropic: { name: "Anthropic", model: "claude-sonnet-5" },
2408
- openai: { name: "OpenAI", model: "gpt-4o-mini" },
2409
- gemini: { name: "Gemini", model: "gemini-3.8-flash" },
2410
- vertex: { name: "Gemini Enterprise", model: "gemini-2.5-flash" }
2411
- };
2509
+ var LEGACY_CATALOG = [
2510
+ { id: "anthropic", label: "Claude (Anthropic)", model: "claude-opus-5" },
2511
+ { id: "openai", label: "OpenAI", model: "gpt-4o-mini" },
2512
+ { id: "gemini", label: "Gemini (Google)", model: "gemini-3.8-flash" },
2513
+ { id: "vertex", label: "Gemini Enterprise (Vertex AI)", model: "gemini-2.5-flash" }
2514
+ ];
2515
+ var catalogOf = (current) => current.llm_catalog?.length ? current.llm_catalog : LEGACY_CATALOG;
2412
2516
  var modelOptions = (current) => [
2413
- { id: "anthropic", label: "Claude (Anthropic)" },
2414
- { id: "openai", label: "OpenAI" },
2415
- { id: "gemini", label: "Gemini (Google)" },
2416
- { id: "vertex", label: "Gemini Enterprise (Vertex AI)" },
2517
+ ...catalogOf(current).map((p) => ({ id: p.id, label: p.label })),
2417
2518
  { id: "skip", label: "Skip", note: current.has_openai_key ? "keep what is configured" : "no agent control" }
2418
2519
  ];
2520
+ function llmUpdates(current, provider, key, model) {
2521
+ const updates = { llm_provider: provider, chat_model: model || null };
2522
+ if (key) updates.openai_api_key = key;
2523
+ if (provider !== current.llm_provider) updates.openai_base_url = null;
2524
+ return updates;
2525
+ }
2526
+ function showModels(choice) {
2527
+ const ids = (choice.models || []).map((m) => m.id);
2528
+ const more = ids.length > MODELS_SHOWN ? ", \u2026" : "";
2529
+ if (ids.length) console.log(` Models: ${ids.slice(0, MODELS_SHOWN).join(", ")}${more}`);
2530
+ }
2419
2531
  async function askModel(current, updates) {
2420
2532
  console.log("\n\u2500\u2500 1. Your model \u2500\u2500");
2421
2533
  const llm = await choose("Which LLM should agents use?", modelOptions(current));
2422
- if (llm !== "skip") await askModelDetails(llm, updates);
2423
- }
2424
- async function askModelDetails(llm, updates) {
2425
- updates.llm_provider = llm;
2426
- const key = await askSecret(`${PRESETS[llm].name} API key:`);
2427
- if (key) updates.openai_api_key = key;
2428
- const model = await ask("Default model:", PRESETS[llm].model);
2429
- if (model) updates.chat_model = model;
2534
+ const choice = catalogOf(current).find((p) => p.id === llm);
2535
+ if (choice) Object.assign(updates, await askModelDetails(current, choice));
2536
+ }
2537
+ async function askModelDetails(current, choice) {
2538
+ const key = await askSecret(`${choice.label} API key:`);
2539
+ showModels(choice);
2540
+ const model = await ask("Default model:", choice.model);
2541
+ return llmUpdates(current, choice.id, key, model);
2430
2542
  }
2431
2543
  var needsNote = (p) => p.needs.some((f) => f.endsWith("_url")) ? "needs its ws:// address" : "needs an API key";
2432
2544
  function fieldLabel(p, field) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oya-ai/cli",
3
- "version": "1.0.124",
3
+ "version": "1.0.127",
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.124",
53
+ "@oya-ai/browser": "1.0.127",
43
54
  "@types/node": "^20.19.43",
44
55
  "tsup": "^8.0.0",
45
56
  "typescript": "^5.4.0"