@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.
- package/dist/browser-cli-args.d.ts +3 -9
- package/dist/browser-cli-args.js +12 -75
- package/dist/commands/browser.js +18 -118
- package/dist/control-client.d.ts +19 -1
- package/dist/control-client.js +54 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +1 -5
- package/package.json +6 -7
- package/skill-guides/browser.md +66 -27
|
@@ -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" | "
|
|
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;
|
package/dist/browser-cli-args.js
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
}
|
package/dist/commands/browser.js
CHANGED
|
@@ -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,
|
|
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 (
|
|
111
|
-
|
|
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
|
-
}
|
package/dist/control-client.d.ts
CHANGED
|
@@ -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;
|
package/dist/control-client.js
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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.
|
|
55
|
-
"@rynx-ai/daemon": "0.1.11-beta.
|
|
56
|
-
"@rynx-ai/
|
|
57
|
-
"@rynx-ai/
|
|
58
|
-
"@rynx-ai/
|
|
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"
|
package/skill-guides/browser.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
57
|
-
rynx browser
|
|
58
|
-
rynx browser
|
|
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
|
-
|
|
62
|
-
|
|
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 `
|
|
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.
|