@rynx-ai/cli 0.1.11-beta.45 → 0.1.11-beta.48

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.
@@ -1,22 +1,16 @@
1
1
  import type { RuntimeBrowserBootstrapCredential } from "@rynx-ai/protocol/runtime-browser-bootstrap";
2
- export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "snapshot" | "navigate" | "click" | "type" | "screenshot" | "headers" | "close";
2
+ export type BrowserCliSubcommand = "exec" | "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "headers" | "close";
3
3
  export interface BrowserCliArgs {
4
4
  subcommand: BrowserCliSubcommand;
5
5
  json: boolean;
6
6
  ensure: boolean;
7
+ argv?: string[];
7
8
  channel?: string;
8
9
  version?: string;
9
10
  runtime?: string;
10
11
  session?: string;
12
+ page?: string;
11
13
  url?: string;
12
- ref?: string;
13
- selector?: string;
14
- text?: string;
15
- output?: string;
16
- format?: "png" | "jpeg" | "webp";
17
- quality?: number;
18
- x?: number;
19
- y?: number;
20
14
  headersAction?: "get" | "set";
21
15
  headers?: string[];
22
16
  expectedRevision?: string;
@@ -1,4 +1,5 @@
1
1
  const SPECS = {
2
+ exec: { values: ["--session", "--page"], flags: ["--json"] },
2
3
  install: { values: ["--channel", "--version"], flags: ["--json"] },
3
4
  update: { values: ["--channel"], flags: ["--json"] },
4
5
  version: { values: [], flags: ["--json"] },
@@ -11,24 +12,6 @@ const SPECS = {
11
12
  status: { values: ["--session", "--runtime"], flags: ["--json"] },
12
13
  pages: { values: ["--session", "--runtime"], flags: ["--json"] },
13
14
  endpoint: { values: ["--session"], flags: ["--ensure", "--json"] },
14
- snapshot: { values: ["--session"], flags: ["--json"] },
15
- navigate: {
16
- values: ["--url", "--session"],
17
- flags: ["--json"],
18
- positionalUrl: true,
19
- },
20
- click: {
21
- values: ["--ref", "--selector", "--x", "--y", "--session"],
22
- flags: ["--json"],
23
- },
24
- type: {
25
- values: ["--ref", "--selector", "--text", "--session"],
26
- flags: ["--json"],
27
- },
28
- screenshot: {
29
- values: ["--output", "--format", "--quality", "--session"],
30
- flags: ["--json"],
31
- },
32
15
  headers: {
33
16
  values: ["--session", "--runtime", "--header", "--expected-revision"],
34
17
  flags: ["--json", "--enable", "--disable", "--clear", "--show-values"],
@@ -37,10 +20,18 @@ const SPECS = {
37
20
  close: { values: ["--session", "--runtime"], flags: ["--json"] },
38
21
  };
39
22
  export function parseBrowserCliArgs(args) {
23
+ let argv;
24
+ if (args[0] === "exec") {
25
+ const boundary = args.indexOf("--");
26
+ if (boundary < 1 || boundary === args.length - 1)
27
+ throw new Error("browser exec: use rynx browser exec [--page <id>] -- <agent-browser command and arguments>");
28
+ argv = args.slice(boundary + 1);
29
+ args = args.slice(0, boundary);
30
+ }
40
31
  const subcommand = args[0];
41
32
  if (!isBrowserSubcommand(subcommand)) {
42
33
  throw new Error("browser: expected install, update, version, clean, open, status, pages, endpoint, " +
43
- "snapshot, navigate, click, type, screenshot, headers, or close");
34
+ "exec, headers, or close; page actions use browser exec -- <agent-browser command>");
44
35
  }
45
36
  const spec = SPECS[subcommand];
46
37
  const values = new Map();
@@ -88,10 +79,12 @@ export function parseBrowserCliArgs(args) {
88
79
  subcommand,
89
80
  json: flags.has("--json"),
90
81
  ensure: flags.has("--ensure"),
82
+ ...(argv ? { argv } : {}),
91
83
  ...(values.has("--channel") ? { channel: values.get("--channel") } : {}),
92
84
  ...(values.has("--version") ? { version: values.get("--version") } : {}),
93
85
  ...(values.has("--runtime") ? { runtime: values.get("--runtime") } : {}),
94
86
  ...(values.has("--session") ? { session: values.get("--session") } : {}),
87
+ ...(values.has("--page") ? { page: nonEmpty(values.get("--page"), "--page") } : {}),
95
88
  ...(spec.positionalUrl && (values.has("--url") || positionals[0])
96
89
  ? { url: values.get("--url") ?? positionals[0] }
97
90
  : {}),
@@ -131,49 +124,6 @@ export function parseBrowserCliArgs(args) {
131
124
  throw new Error("browser headers set: --clear cannot be combined with --header");
132
125
  }
133
126
  }
134
- if (values.has("--ref"))
135
- result.ref = nonEmpty(values.get("--ref"), "--ref");
136
- if (values.has("--selector"))
137
- result.selector = nonEmpty(values.get("--selector"), "--selector");
138
- if (values.has("--text"))
139
- result.text = values.get("--text");
140
- if (values.has("--output"))
141
- result.output = nonEmpty(values.get("--output"), "--output");
142
- if (values.has("--format")) {
143
- const format = values.get("--format");
144
- if (format !== "png" && format !== "jpeg" && format !== "webp") {
145
- throw new Error(`browser screenshot: --format must be png, jpeg, or webp`);
146
- }
147
- result.format = format;
148
- }
149
- if (values.has("--quality")) {
150
- result.quality = integer(values.get("--quality"), "--quality", 0, 100);
151
- }
152
- if (values.has("--x"))
153
- result.x = finiteNumber(values.get("--x"), "--x");
154
- if (values.has("--y"))
155
- result.y = finiteNumber(values.get("--y"), "--y");
156
- if (subcommand === "navigate" && !result.url) {
157
- throw new Error("browser navigate: URL is required");
158
- }
159
- if (subcommand === "click") {
160
- const targets = Number(result.ref !== undefined)
161
- + Number(result.selector !== undefined)
162
- + Number(result.x !== undefined || result.y !== undefined);
163
- if (targets !== 1 || (result.x === undefined) !== (result.y === undefined)) {
164
- throw new Error("browser click: pass exactly one of --ref, --selector, or both --x and --y");
165
- }
166
- }
167
- if (subcommand === "type") {
168
- if ((result.ref === undefined) === (result.selector === undefined)) {
169
- throw new Error("browser type: pass exactly one of --ref or --selector");
170
- }
171
- if (result.text === undefined)
172
- throw new Error("browser type: --text is required");
173
- }
174
- if (subcommand === "screenshot" && !result.output) {
175
- throw new Error("browser screenshot: --output is required");
176
- }
177
127
  return result;
178
128
  }
179
129
  /** Resolve authority without ever turning a managed Session into an operator. */
@@ -211,16 +161,3 @@ function nonEmpty(value, option) {
211
161
  throw new Error(`${option} must not be empty`);
212
162
  return value;
213
163
  }
214
- function finiteNumber(value, option) {
215
- const parsed = Number(value);
216
- if (!Number.isFinite(parsed))
217
- throw new Error(`${option} must be a finite number`);
218
- return parsed;
219
- }
220
- function integer(value, option, minimum, maximum) {
221
- const parsed = Number(value);
222
- if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
223
- throw new Error(`${option} must be an integer from ${minimum} to ${maximum}`);
224
- }
225
- return parsed;
226
- }
@@ -1,9 +1,5 @@
1
- import { writeFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { connectBrowserAutomation, } from "@rynx-ai/browser-cdp";
4
- import WebSocket from "ws";
5
1
  import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
6
- import { callManagedRuntimeBrowser, callResidentRuntime, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
2
+ import { callManagedRuntimeBrowser, callResidentRuntime, executeResidentBrowserCommand, ResidentBrowserCommandError, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
7
3
  import { createProgressDisplay } from "../progress-display.js";
8
4
  import { fail } from "./errors.js";
9
5
  export async function runBrowserCommand(args) {
@@ -107,8 +103,23 @@ export async function runBrowserCommand(args) {
107
103
  printRequestHeadersPolicy(updated, parsed.json, false);
108
104
  return 0;
109
105
  }
110
- if (isAutomationCommand(parsed.subcommand)) {
111
- return runBrowserAutomation(parsed, target);
106
+ if (parsed.subcommand === "exec") {
107
+ try {
108
+ const result = await executeResidentBrowserCommand({ argv: parsed.argv, ...(parsed.page ? { pageId: parsed.page } : {}) }, { sessionId: target.sessionId, credential: target.credential });
109
+ if (!parsed.json && typeof result.data?.help === "string")
110
+ console.log(result.data.help);
111
+ else if (!parsed.json && typeof result.data.snapshot === "string")
112
+ console.log(result.data.snapshot);
113
+ else
114
+ console.log(JSON.stringify(result, null, 2));
115
+ return 0;
116
+ }
117
+ catch (error) {
118
+ if (!(error instanceof ResidentBrowserCommandError))
119
+ throw error;
120
+ console.error(parsed.json ? JSON.stringify({ ...error.details, status: error.status }) : error.message);
121
+ return 1;
122
+ }
112
123
  }
113
124
  const sessionId = target.sessionId;
114
125
  if (parsed.subcommand === "open") {
@@ -157,86 +168,6 @@ function browserReleaseChannel(value) {
157
168
  }
158
169
  return normalized;
159
170
  }
160
- async function runBrowserAutomation(parsed, target) {
161
- if (target.runtimeSelector !== "local") {
162
- fail(`browser ${parsed.subcommand}: automation is available only on the local Runtime`);
163
- }
164
- const automationAccess = await getResidentRuntimeLocalBrowserAutomationAccess({
165
- ...(target.credential ? {} : { sessionId: target.sessionId }),
166
- });
167
- const descriptor = automationAccess.descriptor;
168
- let client;
169
- try {
170
- client = await connectBrowserAutomation({
171
- endpoint: descriptor.endpoint,
172
- ...(descriptor.pageTargetId ? { pageTargetId: descriptor.pageTargetId } : {}),
173
- referenceKey: automationAccess.referenceKey,
174
- createWebSocket: (endpoint) => new WebSocket(endpoint),
175
- });
176
- if (parsed.subcommand === "snapshot") {
177
- const snapshot = await client.snapshot();
178
- if (parsed.json) {
179
- console.log(JSON.stringify({
180
- sessionId: descriptor.sessionId,
181
- browserGeneration: descriptor.browserGeneration,
182
- snapshot,
183
- }, null, 2));
184
- }
185
- else {
186
- printAutomationSnapshot(snapshot.nodes);
187
- }
188
- return 0;
189
- }
190
- if (parsed.subcommand === "navigate") {
191
- const navigation = await client.navigate(parsed.url);
192
- printAutomationResult(parsed, descriptor.browserGeneration, navigation);
193
- return 0;
194
- }
195
- if (parsed.subcommand === "click") {
196
- const clickTarget = parsed.ref !== undefined
197
- ? { ref: parsed.ref }
198
- : parsed.selector !== undefined
199
- ? { selector: parsed.selector }
200
- : { x: parsed.x, y: parsed.y };
201
- await client.click(clickTarget);
202
- printAutomationResult(parsed, descriptor.browserGeneration);
203
- return 0;
204
- }
205
- if (parsed.subcommand === "type") {
206
- const typeTarget = parsed.ref !== undefined
207
- ? { ref: parsed.ref }
208
- : { selector: parsed.selector };
209
- await client.type(typeTarget, parsed.text);
210
- printAutomationResult(parsed, descriptor.browserGeneration);
211
- return 0;
212
- }
213
- const screenshot = await client.screenshot({
214
- ...(parsed.format ? { format: parsed.format } : {}),
215
- ...(parsed.quality !== undefined ? { quality: parsed.quality } : {}),
216
- });
217
- const output = path.resolve(parsed.output);
218
- if (!path.isAbsolute(parsed.output)) {
219
- fail("browser screenshot: --output must be an absolute path");
220
- }
221
- await writeFile(output, Buffer.from(screenshot.data, "base64"), { mode: 0o600 });
222
- if (parsed.json) {
223
- console.log(JSON.stringify({
224
- completed: true,
225
- browserGeneration: descriptor.browserGeneration,
226
- output,
227
- format: screenshot.format,
228
- mimeType: screenshot.mimeType,
229
- }, null, 2));
230
- }
231
- else {
232
- console.log(output);
233
- }
234
- return 0;
235
- }
236
- finally {
237
- client?.close();
238
- }
239
- }
240
171
  async function resolveBrowserTarget(args) {
241
172
  const credential = await readOptionalManagedRuntimeBrowserCredential();
242
173
  try {
@@ -313,34 +244,3 @@ export function projectRequestHeadersPolicyForOutput(policy, showValues) {
313
244
  })),
314
245
  };
315
246
  }
316
- function isAutomationCommand(subcommand) {
317
- return subcommand === "snapshot"
318
- || subcommand === "navigate"
319
- || subcommand === "click"
320
- || subcommand === "type"
321
- || subcommand === "screenshot";
322
- }
323
- function printAutomationResult(parsed, browserGeneration, result = {}) {
324
- if (parsed.json) {
325
- console.log(JSON.stringify({
326
- completed: true,
327
- browserGeneration,
328
- ...result,
329
- }, null, 2));
330
- }
331
- else {
332
- console.log(`Browser ${parsed.subcommand} completed.`);
333
- }
334
- }
335
- function printAutomationSnapshot(nodes) {
336
- if (nodes.length === 0) {
337
- console.log("(empty accessibility tree)");
338
- return;
339
- }
340
- for (const node of nodes) {
341
- const name = node.name ? ` ${JSON.stringify(node.name)}` : "";
342
- const value = node.value ? ` value=${JSON.stringify(node.value)}` : "";
343
- const ref = node.ref ? ` ref=${node.ref}` : "";
344
- console.log(`${node.role}${name}${value}${ref}`);
345
- }
346
- }
@@ -2,7 +2,7 @@ import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
2
2
  import { type DaemonCleanupSessionsInput, type DaemonCleanupSessionsResult, type DaemonChromeInspectionConfigureInput, type DaemonChromeInspectionStatus, type DaemonShutdownIfIdleResult } from "@rynx-ai/protocol/control";
3
3
  import { type RemoteRuntimeRpcMethod, type RemoteRuntimeRpcParams, type RemoteRuntimeRpcResultFor } from "@rynx-ai/protocol/remote-runtime-rpc";
4
4
  import { type PairingOffer } from "@rynx-ai/protocol/direct-runtime";
5
- import { type RuntimeBrowserBootstrapCredential, type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
5
+ import { type RuntimeBrowserAutomationCommand, type RuntimeBrowserAutomationResult, type RuntimeBrowserBootstrapCredential, type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
6
6
  import { type PluginInstallCommitInput, type PluginInstallCommitResult, type PluginInstallPreparation, type PluginInstallPrepareInput, type PluginManagementItem, type PluginManagementState, type PluginMarketplaceAddInput, type PluginMarketplaceItem } from "@rynx-ai/protocol/plugin-management";
7
7
  import { type DaemonControlEndpoint } from "./control-endpoint.js";
8
8
  export { connectResidentDesktopBrowserHost, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostFailure, } from "./desktop-browser-host-client.js";
@@ -114,6 +114,24 @@ export declare function readOptionalManagedRuntimeBrowserCredential(env?: NodeJS
114
114
  export declare function callManagedRuntimeBrowser<M extends RemoteRuntimeRpcMethod>(credential: RuntimeBrowserBootstrapCredential, method: M, params: RemoteRuntimeRpcParams<M>, options?: {
115
115
  signal?: AbortSignal;
116
116
  }): Promise<RemoteRuntimeRpcResultFor<M>>;
117
+ /** Execute native page-command argv within the caller's Session on this Runtime. */
118
+ export declare function executeResidentBrowserCommand(command: RuntimeBrowserAutomationCommand, options?: {
119
+ sessionId?: string;
120
+ credential?: RuntimeBrowserBootstrapCredential;
121
+ }): Promise<RuntimeBrowserAutomationResult>;
122
+ /** Keep the daemon's bounded public diagnostics, including no-replay guidance. */
123
+ export declare class ResidentBrowserCommandError extends Error {
124
+ readonly status: number;
125
+ readonly code: string;
126
+ readonly details: {
127
+ error: string;
128
+ code?: string;
129
+ message?: string;
130
+ hint?: string;
131
+ outcome?: "unknown" | "not_started";
132
+ };
133
+ constructor(status: number, body: string);
134
+ }
117
135
  /** Resolve native CDP for the caller's own Session on this Runtime only. */
118
136
  export declare function getResidentRuntimeLocalBrowserEndpoint(options?: {
119
137
  env?: NodeJS.ProcessEnv;
@@ -4,7 +4,7 @@ import { isDaemonCoreCompatible, parseDaemonStatus, } from "@rynx-ai/protocol/re
4
4
  import { DAEMON_CHROME_INSPECTION_PATH, DAEMON_CLEANUP_SESSIONS_PATH, DAEMON_SHUTDOWN_IF_IDLE_PATH, parseDaemonCleanupSessionsResult, parseDaemonChromeInspectionConfigureInput, parseDaemonChromeInspectionStatus, parseDaemonShutdownIfIdleResult, } from "@rynx-ai/protocol/control";
5
5
  import { parseRemoteRuntimeRpcRequest, parseRemoteRuntimeRpcResponseForMethod, REMOTE_RUNTIME_RPC_MAX_FRAME_BYTES, REMOTE_RUNTIME_RPC_METHOD_METADATA, } from "@rynx-ai/protocol/remote-runtime-rpc";
6
6
  import { parsePairingOffer, } from "@rynx-ai/protocol/direct-runtime";
7
- import { RUNTIME_BROWSER_BOOTSTRAP_PATH, RUNTIME_BROWSER_CAPABILITY_ENV, RUNTIME_BROWSER_CAPABILITY_HEADER, RUNTIME_BROWSER_CONTEXT_FILE_ENV, RUNTIME_BROWSER_MANAGEMENT_PATH_PREFIX, RUNTIME_BROWSER_RPC_PATH, RUNTIME_BROWSER_SESSION_ID_ENV, RUNTIME_BROWSER_SESSION_ID_HEADER, parseRuntimeBrowserBootstrapCredential, parseRuntimeBrowserEndpointDescriptor, } from "@rynx-ai/protocol/runtime-browser-bootstrap";
7
+ import { RUNTIME_BROWSER_BOOTSTRAP_PATH, RUNTIME_BROWSER_CAPABILITY_ENV, RUNTIME_BROWSER_CAPABILITY_HEADER, RUNTIME_BROWSER_CONTEXT_FILE_ENV, RUNTIME_BROWSER_MANAGEMENT_PATH_PREFIX, RUNTIME_BROWSER_RPC_PATH, RUNTIME_BROWSER_EXECUTE_PATH, RUNTIME_BROWSER_SESSION_ID_ENV, RUNTIME_BROWSER_SESSION_ID_HEADER, parseRuntimeBrowserBootstrapCredential, parseRuntimeBrowserAutomationCommand, parseRuntimeBrowserEndpointDescriptor, } from "@rynx-ai/protocol/runtime-browser-bootstrap";
8
8
  import { parseRuntimeBrowserStateGetParams } from "@rynx-ai/protocol/runtime-browser";
9
9
  import { PLUGIN_INSTALL_PREPARATIONS_PATH, PLUGIN_MARKETPLACES_PATH, parsePluginInstallCommitInput, parsePluginInstallCommitResult, parsePluginInstallPreparation, parsePluginInstallPrepareInput, parsePluginManagementItem, parsePluginMarketplaceAddInput, parsePluginMarketplaceItem, pluginInstallCommitPath, pluginInstallPreparationPath, } from "@rynx-ai/protocol/plugin-management";
10
10
  import { ensureDaemonControlEndpoint, } from "./control-endpoint.js";
@@ -566,6 +566,59 @@ export async function callManagedRuntimeBrowser(credential, method, params, opti
566
566
  : "daemon returned an invalid Runtime call result", response.status, { cause: error, ...(neverRetry ? { outcome: "unknown" } : {}) });
567
567
  }
568
568
  }
569
+ /** Execute native page-command argv within the caller's Session on this Runtime. */
570
+ export async function executeResidentBrowserCommand(command, options = {}) {
571
+ const body = parseRuntimeBrowserAutomationCommand(command);
572
+ const credential = options.credential;
573
+ const sessionId = credential?.sessionId ?? options.sessionId;
574
+ if (!sessionId)
575
+ throw new Error("Browser automation requires a Session");
576
+ const signal = AbortSignal.timeout(180_000);
577
+ const endpoint = await ensureDaemonControlEndpoint({ signal });
578
+ assertLoopbackOrigin(endpoint.origin);
579
+ const route = credential ? RUNTIME_BROWSER_EXECUTE_PATH : `${RUNTIME_BROWSER_MANAGEMENT_PATH_PREFIX}/${encodeURIComponent(sessionId)}/execute`;
580
+ const response = await fetch(`${endpoint.origin}${route}`, {
581
+ method: "POST", signal,
582
+ headers: {
583
+ "content-type": "application/json",
584
+ ...(credential ? { [RUNTIME_BROWSER_SESSION_ID_HEADER]: credential.sessionId, [RUNTIME_BROWSER_CAPABILITY_HEADER]: credential.capability } : managementHeaders(endpoint.managementToken)),
585
+ },
586
+ body: JSON.stringify(body),
587
+ });
588
+ const raw = await readBoundedResponse(response, 8 * 1024 * 1024);
589
+ if (!response.ok)
590
+ throw new ResidentBrowserCommandError(response.status, raw);
591
+ const result = JSON.parse(raw);
592
+ if (!result || result.schemaVersion !== 2 || result.sessionId !== sessionId || typeof result.pageId !== "string" ||
593
+ !Number.isSafeInteger(result.browserGeneration) || result.browserGeneration < 1 || result.completed !== true ||
594
+ !result.data || typeof result.data !== "object" || Array.isArray(result.data))
595
+ throw new Error("daemon returned an invalid Browser execution result");
596
+ return result;
597
+ }
598
+ /** Keep the daemon's bounded public diagnostics, including no-replay guidance. */
599
+ export class ResidentBrowserCommandError extends Error {
600
+ status;
601
+ code;
602
+ details;
603
+ constructor(status, body) {
604
+ let parsed;
605
+ try {
606
+ parsed = parseJsonObject(body, "invalid Browser error");
607
+ }
608
+ catch { /* Plain-text errors remain useful. */ }
609
+ const bounded = (value, max = 2048) => typeof value === "string" && value.length > 0 ? value.slice(0, max) : undefined;
610
+ const error = bounded(parsed?.error, 128) ?? "browser_execution_failed";
611
+ const code = bounded(parsed?.code, 128);
612
+ const message = bounded(parsed?.message) ?? (parsed === undefined ? bounded(body.trim()) : undefined);
613
+ const hint = bounded(parsed?.hint);
614
+ super(`Runtime Browser execution failed (${status}): ${error}${message ? `: ${message}` : ""}${hint ? `\n${hint}` : ""}`);
615
+ this.status = status;
616
+ this.name = "ResidentBrowserCommandError";
617
+ this.code = code ?? error;
618
+ this.details = { error, ...(code ? { code } : {}), ...(message ? { message } : {}), ...(hint ? { hint } : {}),
619
+ ...(parsed?.outcome === "unknown" || parsed?.outcome === "not_started" ? { outcome: parsed.outcome } : {}) };
620
+ }
621
+ }
569
622
  /** Resolve native CDP for the caller's own Session on this Runtime only. */
570
623
  export async function getResidentRuntimeLocalBrowserEndpoint(options = {}) {
571
624
  return (await getResidentRuntimeLocalBrowserAutomationAccess(options)).descriptor;
package/dist/usage.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const USAGE = "Usage: rynx <command>\n\nGeneral:\n version [--json] | --version\n print the installed Rynx version\n\nSetup:\n setup [--non-interactive] [--default-runtime <codex|traex|claude>]\n [--host <host>] [--port <port>] [--log-level <level>]\n [--install-browser|--skip-browser] [--json|--result-file <path>]\n initialize configuration and local dependencies\n doctor read-only health check\n\nLifecycle:\n start | restart | stop [--if-idle] | status [--json] | logs\n autostart enable|disable|status\n update [version] [--check] [--json]\n\nPlugins:\n market list\n market add <git-or-local-source> [--alias <id>]\n market refresh [market-id]\n market remove <market-id>\n plugin list\n plugin install <source|plugin@market> [--force] [--expect-digest <sha256-...>]\n plugin update <plugin@market> [--expect-digest <sha256-...>]\n plugin enable|disable|uninstall <plugin@market>\n plugin <plugin@market> <command> invoke a plugin-owned command\n\nAgents:\n agent list\n agent show <id>\n agent add <id>\n agent rm <id>\n\nBuiltin Skills:\n skills list [--json]\n skills get <browser|emulator> [--full] [--json]\n\nMaintenance:\n cleanup sessions [--dry-run]\n\nEmulator:\n emulator <args...>\n\nRemote Runtime:\n runtime share --address <host|ws-url> [--label <label>] [--json]\n runtime add --pairing-code <rynx://...> [--name <name>]\n runtime list [--json]\n runtime test <local|daemon-id> [--json]\n runtime forget <daemon-id>\n runtime clients list [--json]\n runtime clients revoke <grant-id>\n\nSessions:\n session fork <session-id> [--title <title>] [--json]\n\nBrowser:\n browser install|update|version|clean [...]\n browser open [url] [--session <id>] [--runtime <local|daemon-id>] [--json]\n browser status|pages|close [--session <id>] [--runtime <local|daemon-id>] [--json]\n browser endpoint [--ensure] [--session <local-id>] [--json]\n browser snapshot [--session <local-id>] [--json]\n browser navigate <url> [--session <local-id>] [--json]\n browser click (--ref <ref>|--selector <css>|--x <n> --y <n>) [--session <local-id>] [--json]\n browser type (--ref <ref>|--selector <css>) --text <text> [--session <local-id>] [--json]\n browser screenshot --output <absolute-path> [--session <local-id>] [--json]\n";
1
+ export declare const USAGE = "Usage: rynx <command>\n\nGeneral:\n version [--json] | --version\n print the installed Rynx version\n\nSetup:\n setup [--non-interactive] [--default-runtime <codex|traex|claude>]\n [--host <host>] [--port <port>] [--log-level <level>]\n [--install-browser|--skip-browser] [--json|--result-file <path>]\n initialize configuration and local dependencies\n doctor read-only health check\n\nLifecycle:\n start | restart | stop [--if-idle] | status [--json] | logs\n autostart enable|disable|status\n update [version] [--check] [--json]\n\nPlugins:\n market list\n market add <git-or-local-source> [--alias <id>]\n market refresh [market-id]\n market remove <market-id>\n plugin list\n plugin install <source|plugin@market> [--force] [--expect-digest <sha256-...>]\n plugin update <plugin@market> [--expect-digest <sha256-...>]\n plugin enable|disable|uninstall <plugin@market>\n plugin <plugin@market> <command> invoke a plugin-owned command\n\nAgents:\n agent list\n agent show <id>\n agent add <id>\n agent rm <id>\n\nBuiltin Skills:\n skills list [--json]\n skills get <browser|emulator> [--full] [--json]\n\nMaintenance:\n cleanup sessions [--dry-run]\n\nEmulator:\n emulator <args...>\n\nRemote Runtime:\n runtime share --address <host|ws-url> [--label <label>] [--json]\n runtime add --pairing-code <rynx://...> [--name <name>]\n runtime list [--json]\n runtime test <local|daemon-id> [--json]\n runtime forget <daemon-id>\n runtime clients list [--json]\n runtime clients revoke <grant-id>\n\nSessions:\n session fork <session-id> [--title <title>] [--json]\n\nBrowser:\n browser install|update|version|clean [...]\n browser open [url] [--session <id>] [--runtime <local|daemon-id>] [--json]\n browser status|pages|close [--session <id>] [--runtime <local|daemon-id>] [--json]\n browser endpoint [--ensure] [--session <local-id>] [--json]\n browser exec [--page <id>] [--session <local-id>] [--json] -- <agent-browser command and args>\n";
package/dist/usage.js CHANGED
@@ -60,9 +60,5 @@ Browser:
60
60
  browser open [url] [--session <id>] [--runtime <local|daemon-id>] [--json]
61
61
  browser status|pages|close [--session <id>] [--runtime <local|daemon-id>] [--json]
62
62
  browser endpoint [--ensure] [--session <local-id>] [--json]
63
- browser snapshot [--session <local-id>] [--json]
64
- browser navigate <url> [--session <local-id>] [--json]
65
- browser click (--ref <ref>|--selector <css>|--x <n> --y <n>) [--session <local-id>] [--json]
66
- browser type (--ref <ref>|--selector <css>) --text <text> [--session <local-id>] [--json]
67
- browser screenshot --output <absolute-path> [--session <local-id>] [--json]
63
+ browser exec [--page <id>] [--session <local-id>] [--json] -- <agent-browser command and args>
68
64
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/cli",
3
- "version": "0.1.11-beta.45",
3
+ "version": "0.1.11-beta.48",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,12 +51,11 @@
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.6.0",
53
53
  "ws": "^8.21.0",
54
- "@rynx-ai/core": "0.1.11-beta.45",
55
- "@rynx-ai/daemon": "0.1.11-beta.45",
56
- "@rynx-ai/browser-cdp": "0.1.11-beta.45",
57
- "@rynx-ai/emulator": "0.1.11-beta.45",
58
- "@rynx-ai/protocol": "0.1.11-beta.45",
59
- "@rynx-ai/tmux": "0.1.11-beta.45"
54
+ "@rynx-ai/core": "0.1.11-beta.48",
55
+ "@rynx-ai/daemon": "0.1.11-beta.48",
56
+ "@rynx-ai/emulator": "0.1.11-beta.48",
57
+ "@rynx-ai/protocol": "0.1.11-beta.48",
58
+ "@rynx-ai/tmux": "0.1.11-beta.48"
60
59
  },
61
60
  "devDependencies": {
62
61
  "@types/ws": "^8.18.1"
@@ -24,42 +24,79 @@ installation, App, browser, or service. Do not scan `/Applications`, use `find`
24
24
  or `mdfind`, run `open`, `open -a`, or `osascript`, inspect Preview/dev builds,
25
25
  or launch/relaunch a Rynx App or daemon.
26
26
 
27
- ## Core loop
27
+ ## Core loop: upstream argv, not Rynx action aliases
28
28
 
29
- Open or navigate the Session Browser when needed:
29
+ Rynx owns Session/Browser/Page resources. All page operations use one general
30
+ entry point; the retired snapshot/click/type/navigate/screenshot aliases are
31
+ not accepted.
30
32
 
31
33
  ```sh
32
34
  rynx browser open https://example.com --json
33
- rynx browser navigate https://example.com/next --json
34
- ```
35
-
36
- Inspect before acting:
37
-
38
- ```sh
39
- rynx browser snapshot --json
40
- ```
41
-
42
- Use a returned accessibility `ref` for the next action:
43
-
44
- ```sh
45
- rynx browser click --ref <ref> --json
46
- rynx browser type --ref <ref> --text "hello" --json
35
+ rynx browser pages --json
36
+ rynx browser exec -- --help
37
+ rynx browser exec --page <pageId> --json -- snapshot -i
38
+ rynx browser exec --page <pageId> --json -- click @<ref>
39
+ rynx browser exec --page <pageId> --json -- fill @<ref> "带空格的中文"
40
+ rynx browser exec --page <pageId> --json -- screenshot /absolute/path/page.png
47
41
  ```
48
42
 
49
- Take a fresh snapshot after navigation or substantial DOM replacement because
50
- old refs become stale. Use `--selector` only when no useful accessibility ref
51
- exists. Use coordinate clicks only as a final fallback after a screenshot.
52
-
53
- Inspect pages and save visual evidence with:
43
+ Everything after the separator is an argv array sent to the selected native
44
+ agent-browser CLI, without a shell or a second string parser. New upstream
45
+ page commands/options do not require a Rynx command mapping. Command-first
46
+ syntax is required. Use upstream help from this exact engine for frame, wait,
47
+ find, get, scroll, keyboard and other operations; do not copy a command table
48
+ from a different installed version. The outer --json controls Rynx output;
49
+ do not pass an inner --json or override --session, --cdp, configuration,
50
+ provider, namespace, Browser lifecycle or helper settings.
51
+
52
+ JSON is schemaVersion 2 with sessionId, browserGeneration, pageId, driverVersion
53
+ and upstream data. A snapshot has data.snapshot (text) and data.refs (map).
54
+ Upstream data and native refs are returned unchanged. Use the native refs from
55
+ the latest response (for example @e1); do not invent them. Rynx does not wrap,
56
+ translate or track refs. Ref validity belongs to agent-browser, not Rynx.
57
+ For multi-page tasks, keep --page fixed for the snapshot and subsequent actions:
58
+ refs do not identify their originating Page. Without --page, the command targets
59
+ the Session's current Page when admitted. A missing explicit Page never falls
60
+ back to a neighbor, and switching tabs cannot redirect an already queued command.
61
+
62
+ Frame selection is also upstream state for this Page helper:
54
63
 
55
64
  ```sh
56
- rynx browser status --json
57
- rynx browser pages --json
58
- rynx browser screenshot --output /absolute/path/page.png --json
65
+ rynx browser exec --page <pageId> -- frame @<iframe-ref>
66
+ rynx browser exec --page <pageId> --json -- snapshot -i
67
+ rynx browser exec --page <pageId> -- frame main
59
68
  ```
60
69
 
61
- Do not request or connect to the raw CDP endpoint unless diagnosing Rynx itself.
62
- Do not close a Browser generation the user still needs.
70
+ Use the latest native references after snapshot, annotated screenshot or diff;
71
+ these commands may replace the engine's reference map. Take a fresh snapshot
72
+ after switching frames, navigation, substantial DOM replacement or helper/engine
73
+ restart. Old native ref numbers may be reused; Rynx adds no stale-ref protection.
74
+ type inserts; fill replaces, exactly as documented by the selected engine.
75
+ Screenshot formats/options also follow that engine; Rynx does not implement
76
+ a parallel image encoder. Use absolute output paths; default helper artifacts
77
+ are temporary and may disappear on helper cleanup.
78
+
79
+ Commands serialize per Page. Different Pages progress independently. An active
80
+ human Control lease rejects Agent writes until released. An outcome_unknown
81
+ error means a write may already have happened: inspect, never blindly replay.
82
+ A complete upstream command error is returned without destroying the helper.
83
+
84
+ Do not request raw CDP, change the engine, start/replace a Browser outside Rynx,
85
+ or close a Browser generation the user still needs. Resource creation/close,
86
+ request-header policy and the human 9333 inspection gateway stay Rynx-owned.
87
+
88
+ ## Engine upgrades (operators only)
89
+
90
+ The default engine is bundled and pinned. An operator may set the absolute
91
+ native executable path in the Runtime's config.json using
92
+ RYNX_AGENT_BROWSER_EXECUTABLE. This is a daemon-owner setting, not an Agent
93
+ argument and not a PATH fallback. Select a versioned agent-browser native
94
+ binary for the Runtime OS/architecture, then restart the Runtime after changing
95
+ the setting. Upgrading compatible engine versions requires no Rynx CLI build
96
+ or new page-command mappings. Re-run real backend conformance before promoting
97
+ an engine; an upstream CDP/lifecycle/transport breaking change can still require
98
+ an adapter update. Ref names and command-result fields need no Rynx mapping.
99
+ Never upgrade silently in the middle of an Agent action.
63
100
 
64
101
  ## Session-wide request headers
65
102
 
@@ -105,7 +142,9 @@ sensitive value. Do not attempt to bypass this boundary from an active Session.
105
142
 
106
143
  - On endpoint access failure, follow **Sandbox boundary** and make no fallback
107
144
  attempt.
108
- - On `stale reference`, take a new snapshot and retry once.
145
+ - On an upstream ref error or `driver_restarted`, take a new snapshot on the same
146
+ --page and re-evaluate the intended action. Do not replay an action whose
147
+ outcome was reported as unknown.
109
148
  - On `busy`, preserve the current Turn or Terminal for the user to resolve.
110
149
  - On an unavailable capability, report the missing App integration or macOS
111
150
  permission; do not install an unpinned replacement.