@rynx-ai/cli 0.1.11-beta.30 → 0.1.11-beta.32
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 +7 -1
- package/dist/browser-cli-args.js +51 -6
- package/dist/commands/browser.d.ts +4 -0
- package/dist/commands/browser.js +61 -0
- package/dist/control-client.js +2 -0
- package/dist/desktop-browser-host-client.js +43 -10
- package/package.json +7 -7
- package/skill-guides/browser.md +41 -1
- package/skills/rynx-cli/SKILL.md +9 -4
|
@@ -1,5 +1,5 @@
|
|
|
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" | "close";
|
|
2
|
+
export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "snapshot" | "navigate" | "click" | "type" | "screenshot" | "headers" | "close";
|
|
3
3
|
export interface BrowserCliArgs {
|
|
4
4
|
subcommand: BrowserCliSubcommand;
|
|
5
5
|
json: boolean;
|
|
@@ -17,6 +17,12 @@ export interface BrowserCliArgs {
|
|
|
17
17
|
quality?: number;
|
|
18
18
|
x?: number;
|
|
19
19
|
y?: number;
|
|
20
|
+
headersAction?: "get" | "set";
|
|
21
|
+
headers?: string[];
|
|
22
|
+
expectedRevision?: string;
|
|
23
|
+
headersEnabled?: boolean;
|
|
24
|
+
clearHeaders?: boolean;
|
|
25
|
+
showHeaderValues?: boolean;
|
|
20
26
|
}
|
|
21
27
|
export interface BrowserCliTarget {
|
|
22
28
|
runtimeSelector: string;
|
package/dist/browser-cli-args.js
CHANGED
|
@@ -29,17 +29,23 @@ const SPECS = {
|
|
|
29
29
|
values: ["--output", "--format", "--quality", "--session"],
|
|
30
30
|
flags: ["--json"],
|
|
31
31
|
},
|
|
32
|
+
headers: {
|
|
33
|
+
values: ["--session", "--runtime", "--header", "--expected-revision"],
|
|
34
|
+
flags: ["--json", "--enable", "--disable", "--clear", "--show-values"],
|
|
35
|
+
positionalAction: true,
|
|
36
|
+
},
|
|
32
37
|
close: { values: ["--session", "--runtime"], flags: ["--json"] },
|
|
33
38
|
};
|
|
34
39
|
export function parseBrowserCliArgs(args) {
|
|
35
40
|
const subcommand = args[0];
|
|
36
41
|
if (!isBrowserSubcommand(subcommand)) {
|
|
37
42
|
throw new Error("browser: expected install, update, version, clean, open, status, pages, endpoint, " +
|
|
38
|
-
"snapshot, navigate, click, type, screenshot, or close");
|
|
43
|
+
"snapshot, navigate, click, type, screenshot, headers, or close");
|
|
39
44
|
}
|
|
40
45
|
const spec = SPECS[subcommand];
|
|
41
46
|
const values = new Map();
|
|
42
47
|
const flags = new Set();
|
|
48
|
+
const headerValues = [];
|
|
43
49
|
const positionals = [];
|
|
44
50
|
for (let index = 1; index < args.length; index += 1) {
|
|
45
51
|
const arg = args[index];
|
|
@@ -51,13 +57,17 @@ export function parseBrowserCliArgs(args) {
|
|
|
51
57
|
continue;
|
|
52
58
|
}
|
|
53
59
|
if (spec.values.includes(arg)) {
|
|
54
|
-
if (values.has(arg))
|
|
60
|
+
if (arg !== "--header" && values.has(arg)) {
|
|
55
61
|
throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
|
|
62
|
+
}
|
|
56
63
|
const value = args[index + 1];
|
|
57
64
|
if (!value || value.startsWith("--")) {
|
|
58
65
|
throw new Error(`browser ${subcommand}: ${arg} requires a value`);
|
|
59
66
|
}
|
|
60
|
-
|
|
67
|
+
if (arg === "--header")
|
|
68
|
+
headerValues.push(value);
|
|
69
|
+
else
|
|
70
|
+
values.set(arg, value);
|
|
61
71
|
index += 1;
|
|
62
72
|
continue;
|
|
63
73
|
}
|
|
@@ -65,13 +75,13 @@ export function parseBrowserCliArgs(args) {
|
|
|
65
75
|
}
|
|
66
76
|
positionals.push(arg);
|
|
67
77
|
}
|
|
68
|
-
if (!spec.positionalUrl && positionals.length > 0) {
|
|
78
|
+
if (!spec.positionalUrl && !spec.positionalAction && positionals.length > 0) {
|
|
69
79
|
throw new Error(`browser ${subcommand}: unexpected argument ${positionals[0]}`);
|
|
70
80
|
}
|
|
71
81
|
if (positionals.length > 1) {
|
|
72
82
|
throw new Error(`browser ${subcommand}: unexpected extra argument ${positionals[1]}`);
|
|
73
83
|
}
|
|
74
|
-
if (positionals.length === 1 && values.has("--url")) {
|
|
84
|
+
if (spec.positionalUrl && positionals.length === 1 && values.has("--url")) {
|
|
75
85
|
throw new Error("browser open: pass the URL once, either positionally or with --url");
|
|
76
86
|
}
|
|
77
87
|
const result = {
|
|
@@ -82,10 +92,45 @@ export function parseBrowserCliArgs(args) {
|
|
|
82
92
|
...(values.has("--version") ? { version: values.get("--version") } : {}),
|
|
83
93
|
...(values.has("--runtime") ? { runtime: values.get("--runtime") } : {}),
|
|
84
94
|
...(values.has("--session") ? { session: values.get("--session") } : {}),
|
|
85
|
-
...(values.has("--url") || positionals[0]
|
|
95
|
+
...(spec.positionalUrl && (values.has("--url") || positionals[0])
|
|
86
96
|
? { url: values.get("--url") ?? positionals[0] }
|
|
87
97
|
: {}),
|
|
88
98
|
};
|
|
99
|
+
if (subcommand === "headers") {
|
|
100
|
+
const action = positionals[0];
|
|
101
|
+
if (action !== "get" && action !== "set") {
|
|
102
|
+
throw new Error("browser headers: expected get or set");
|
|
103
|
+
}
|
|
104
|
+
if (flags.has("--enable") && flags.has("--disable")) {
|
|
105
|
+
throw new Error("browser headers set: choose either --enable or --disable");
|
|
106
|
+
}
|
|
107
|
+
result.headersAction = action;
|
|
108
|
+
if (headerValues.length > 0)
|
|
109
|
+
result.headers = headerValues;
|
|
110
|
+
if (values.has("--expected-revision")) {
|
|
111
|
+
result.expectedRevision = nonEmpty(values.get("--expected-revision"), "--expected-revision");
|
|
112
|
+
}
|
|
113
|
+
if (flags.has("--enable"))
|
|
114
|
+
result.headersEnabled = true;
|
|
115
|
+
if (flags.has("--disable"))
|
|
116
|
+
result.headersEnabled = false;
|
|
117
|
+
if (flags.has("--clear"))
|
|
118
|
+
result.clearHeaders = true;
|
|
119
|
+
if (flags.has("--show-values"))
|
|
120
|
+
result.showHeaderValues = true;
|
|
121
|
+
if (action === "get" && (headerValues.length > 0 ||
|
|
122
|
+
result.expectedRevision !== undefined ||
|
|
123
|
+
result.headersEnabled !== undefined ||
|
|
124
|
+
result.clearHeaders)) {
|
|
125
|
+
throw new Error("browser headers get: update options are not allowed");
|
|
126
|
+
}
|
|
127
|
+
if (action === "set" && result.showHeaderValues) {
|
|
128
|
+
throw new Error("browser headers set: --show-values is only valid with get");
|
|
129
|
+
}
|
|
130
|
+
if (action === "set" && headerValues.length > 0 && result.clearHeaders) {
|
|
131
|
+
throw new Error("browser headers set: --clear cannot be combined with --header");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
89
134
|
if (values.has("--ref"))
|
|
90
135
|
result.ref = nonEmpty(values.get("--ref"), "--ref");
|
|
91
136
|
if (values.has("--selector"))
|
|
@@ -1 +1,5 @@
|
|
|
1
|
+
import type { RuntimeBrowserRequestHeadersPolicy } from "@rynx-ai/protocol/runtime-browser";
|
|
1
2
|
export declare function runBrowserCommand(args: readonly string[]): Promise<number>;
|
|
3
|
+
export declare function projectRequestHeadersPolicyForOutput(policy: RuntimeBrowserRequestHeadersPolicy, showValues: boolean): RuntimeBrowserRequestHeadersPolicy & {
|
|
4
|
+
valuesRedacted: boolean;
|
|
5
|
+
};
|
package/dist/commands/browser.js
CHANGED
|
@@ -83,6 +83,30 @@ export async function runBrowserCommand(args) {
|
|
|
83
83
|
return 0;
|
|
84
84
|
}
|
|
85
85
|
const target = await resolveBrowserTarget(parsed);
|
|
86
|
+
if (parsed.subcommand === "headers") {
|
|
87
|
+
const current = await callBrowserRuntime(target, "browser.request-headers.get", {
|
|
88
|
+
sessionId: target.sessionId,
|
|
89
|
+
});
|
|
90
|
+
if (parsed.headersAction === "get") {
|
|
91
|
+
if (parsed.showHeaderValues && target.credential) {
|
|
92
|
+
fail("browser headers get: --show-values is unavailable inside a managed Session; " +
|
|
93
|
+
"use the Session settings UI");
|
|
94
|
+
}
|
|
95
|
+
printRequestHeadersPolicy(current, parsed.json, parsed.showHeaderValues === true);
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
const headers = parsed.clearHeaders
|
|
99
|
+
? []
|
|
100
|
+
: parsed.headers?.map(parseCliRequestHeader) ?? current.headers;
|
|
101
|
+
const updated = await callBrowserRuntime(target, "browser.request-headers.set", {
|
|
102
|
+
sessionId: target.sessionId,
|
|
103
|
+
expectedRevision: parsed.expectedRevision ?? current.revision,
|
|
104
|
+
enabled: parsed.headersEnabled ?? current.enabled,
|
|
105
|
+
headers,
|
|
106
|
+
});
|
|
107
|
+
printRequestHeadersPolicy(updated, parsed.json, false);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
86
110
|
if (isAutomationCommand(parsed.subcommand)) {
|
|
87
111
|
return runBrowserAutomation(parsed, target);
|
|
88
112
|
}
|
|
@@ -252,6 +276,43 @@ function printBrowserPages(state) {
|
|
|
252
276
|
console.log(`${marker} ${page.pageId} ${page.url}${title}`);
|
|
253
277
|
}
|
|
254
278
|
}
|
|
279
|
+
function parseCliRequestHeader(value) {
|
|
280
|
+
const separator = value.indexOf(":");
|
|
281
|
+
if (separator <= 0) {
|
|
282
|
+
fail("browser headers set: --header must use 'Name: value'");
|
|
283
|
+
}
|
|
284
|
+
const name = value.slice(0, separator).trim();
|
|
285
|
+
const rawValue = value.slice(separator + 1);
|
|
286
|
+
return {
|
|
287
|
+
name,
|
|
288
|
+
value: rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function printRequestHeadersPolicy(policy, json, showValues) {
|
|
292
|
+
const output = projectRequestHeadersPolicyForOutput(policy, showValues);
|
|
293
|
+
if (json) {
|
|
294
|
+
console.log(JSON.stringify(output, null, 2));
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log(`Browser request headers ${output.enabled ? "enabled" : "disabled"} · revision ${output.revision}`);
|
|
298
|
+
if (output.headers.length === 0) {
|
|
299
|
+
console.log(" (no configured headers)");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
for (const header of output.headers)
|
|
303
|
+
console.log(` ${header.name}: ${header.value}`);
|
|
304
|
+
}
|
|
305
|
+
export function projectRequestHeadersPolicyForOutput(policy, showValues) {
|
|
306
|
+
return {
|
|
307
|
+
enabled: policy.enabled,
|
|
308
|
+
revision: policy.revision,
|
|
309
|
+
valuesRedacted: !showValues,
|
|
310
|
+
headers: policy.headers.map((header) => ({
|
|
311
|
+
name: header.name,
|
|
312
|
+
value: showValues ? header.value : "[redacted]",
|
|
313
|
+
})),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
255
316
|
function isAutomationCommand(subcommand) {
|
|
256
317
|
return subcommand === "snapshot"
|
|
257
318
|
|| subcommand === "navigate"
|
package/dist/control-client.js
CHANGED
|
@@ -437,6 +437,8 @@ const MANAGED_BROWSER_RPC_METHODS = new Set([
|
|
|
437
437
|
"browser.page.back",
|
|
438
438
|
"browser.page.forward",
|
|
439
439
|
"browser.page.reload",
|
|
440
|
+
"browser.request-headers.get",
|
|
441
|
+
"browser.request-headers.set",
|
|
440
442
|
]);
|
|
441
443
|
/**
|
|
442
444
|
* Invoke Browser control for the caller's own managed Session. Unlike the
|
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
import { DESKTOP_BROWSER_HOST_MAX_BINARY_FRAME_BYTES, DESKTOP_BROWSER_HOST_MAX_CONTROL_FRAME_BYTES, DESKTOP_BROWSER_HOST_MAX_PENDING_COMMANDS, DESKTOP_BROWSER_HOST_PATH, DESKTOP_BROWSER_HOST_PROTOCOL_VERSION, encodeDesktopBrowserHostSurfaceFrame, parseDesktopBrowserHostClientFrame, parseDesktopBrowserHostServerFrame, } from "@rynx-ai/protocol/desktop-browser-host";
|
|
1
|
+
import { DESKTOP_BROWSER_HOST_MAX_BINARY_FRAME_BYTES, DESKTOP_BROWSER_HOST_MAX_CONTROL_FRAME_BYTES, DESKTOP_BROWSER_HOST_MAX_PENDING_COMMANDS, DESKTOP_BROWSER_HOST_MINIMUM_PROTOCOL_VERSION, DESKTOP_BROWSER_HOST_PATH, DESKTOP_BROWSER_HOST_PROTOCOL_VERSION, encodeDesktopBrowserHostSurfaceFrame, parseDesktopBrowserHostClientFrame, parseDesktopBrowserHostServerFrame, } from "@rynx-ai/protocol/desktop-browser-host";
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
3
|
import { ensureDaemonControlEndpoint } from "./control-endpoint.js";
|
|
4
4
|
const CONNECT_TIMEOUT_MS = 5_000;
|
|
5
5
|
const MAX_BUFFERED_BYTES = DESKTOP_BROWSER_HOST_MAX_BINARY_FRAME_BYTES + DESKTOP_BROWSER_HOST_MAX_CONTROL_FRAME_BYTES;
|
|
6
6
|
/** Open the sole resident daemon's authenticated loopback Desktop Host lease. */
|
|
7
7
|
export async function connectResidentDesktopBrowserHost(options) {
|
|
8
|
+
options.signal?.throwIfAborted();
|
|
9
|
+
const endpoint = options.endpoint
|
|
10
|
+
?? await ensureDaemonControlEndpoint({ signal: options.signal });
|
|
11
|
+
assertDesktopBrowserHostEndpoint(endpoint);
|
|
12
|
+
try {
|
|
13
|
+
return await connectResidentDesktopBrowserHostVersion(options, endpoint, DESKTOP_BROWSER_HOST_PROTOCOL_VERSION);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
options.signal?.throwIfAborted();
|
|
17
|
+
if (!isLegacyProtocolRejection(error))
|
|
18
|
+
throw error;
|
|
19
|
+
// V1 daemons reject a V2 hello before issuing a lease. Reconnect once
|
|
20
|
+
// with their protocol so App and daemon can roll independently. Any
|
|
21
|
+
// failure after a lease is returned stays on that negotiated connection.
|
|
22
|
+
return connectResidentDesktopBrowserHostVersion(options, endpoint, DESKTOP_BROWSER_HOST_MINIMUM_PROTOCOL_VERSION).catch((legacyError) => {
|
|
23
|
+
throw new Error("Desktop Browser Host could not negotiate with the daemon", {
|
|
24
|
+
cause: new AggregateError([error, legacyError]),
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function connectResidentDesktopBrowserHostVersion(options, endpoint, protocolVersion) {
|
|
8
30
|
options.signal?.throwIfAborted();
|
|
9
31
|
const hello = parseDesktopBrowserHostClientFrame({
|
|
10
32
|
type: "desktop.browser.host.hello",
|
|
11
|
-
protocolVersion
|
|
33
|
+
protocolVersion,
|
|
12
34
|
hostInstanceId: options.hostInstanceId,
|
|
13
35
|
capabilities: {
|
|
14
36
|
semanticPageBinding: options.capabilities.semanticPageBinding,
|
|
@@ -19,9 +41,6 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
19
41
|
if (hello.type !== "desktop.browser.host.hello") {
|
|
20
42
|
throw new Error("Desktop Browser Host hello parser returned the wrong frame");
|
|
21
43
|
}
|
|
22
|
-
const endpoint = options.endpoint
|
|
23
|
-
?? await ensureDaemonControlEndpoint({ signal: options.signal });
|
|
24
|
-
assertDesktopBrowserHostEndpoint(endpoint);
|
|
25
44
|
const url = new URL(DESKTOP_BROWSER_HOST_PATH, `${endpoint.origin}/`);
|
|
26
45
|
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
27
46
|
const socket = new WebSocket(url, {
|
|
@@ -139,7 +158,7 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
139
158
|
socket.once("close", (code, reason) => {
|
|
140
159
|
options.signal?.removeEventListener("abort", onAbort);
|
|
141
160
|
if (!terminalError && !closing) {
|
|
142
|
-
terminalError = new
|
|
161
|
+
terminalError = new DesktopBrowserHostLeaseClosedError(code, `Desktop Browser Host lease closed (${code})${reason.length > 0 ? `: ${reason.toString("utf8")}` : ""}`);
|
|
143
162
|
}
|
|
144
163
|
if (!lease && terminalError)
|
|
145
164
|
leaseReject(terminalError);
|
|
@@ -224,7 +243,7 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
224
243
|
signal: controller.signal,
|
|
225
244
|
reply: (result) => settle(command, {
|
|
226
245
|
type: "desktop.browser.host.reply",
|
|
227
|
-
protocolVersion:
|
|
246
|
+
protocolVersion: activeLease.protocolVersion,
|
|
228
247
|
leaseId: activeLease.leaseId,
|
|
229
248
|
commandId: command.commandId,
|
|
230
249
|
ok: true,
|
|
@@ -232,7 +251,7 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
232
251
|
}),
|
|
233
252
|
reject: (failure) => settle(command, {
|
|
234
253
|
type: "desktop.browser.host.reply",
|
|
235
|
-
protocolVersion:
|
|
254
|
+
protocolVersion: activeLease.protocolVersion,
|
|
236
255
|
leaseId: activeLease.leaseId,
|
|
237
256
|
commandId: command.commandId,
|
|
238
257
|
ok: false,
|
|
@@ -250,7 +269,7 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
250
269
|
const nextSequence = eventSequence + 1;
|
|
251
270
|
const frame = parseDesktopBrowserHostClientFrame({
|
|
252
271
|
type: "desktop.browser.host.event",
|
|
253
|
-
protocolVersion:
|
|
272
|
+
protocolVersion: activeLease.protocolVersion,
|
|
254
273
|
leaseId: activeLease.leaseId,
|
|
255
274
|
eventSequence: nextSequence,
|
|
256
275
|
event,
|
|
@@ -268,7 +287,7 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
268
287
|
const encoded = encodeDesktopBrowserHostSurfaceFrame({
|
|
269
288
|
...frame,
|
|
270
289
|
leaseId: activeLease.leaseId,
|
|
271
|
-
});
|
|
290
|
+
}, activeLease.protocolVersion);
|
|
272
291
|
await sendBinary(socket, encoded);
|
|
273
292
|
}),
|
|
274
293
|
close: async () => {
|
|
@@ -285,6 +304,20 @@ export async function connectResidentDesktopBrowserHost(options) {
|
|
|
285
304
|
},
|
|
286
305
|
};
|
|
287
306
|
}
|
|
307
|
+
class DesktopBrowserHostLeaseClosedError extends Error {
|
|
308
|
+
closeCode;
|
|
309
|
+
constructor(closeCode, message) {
|
|
310
|
+
super(message);
|
|
311
|
+
this.closeCode = closeCode;
|
|
312
|
+
this.name = "DesktopBrowserHostLeaseClosedError";
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function isLegacyProtocolRejection(error) {
|
|
316
|
+
return error instanceof DesktopBrowserHostLeaseClosedError &&
|
|
317
|
+
// 4400 is the pre-V2 daemon's generic invalid-frame response. 1002 covers
|
|
318
|
+
// standards-based V1 peers; 4406 is the explicit unsupported-version code.
|
|
319
|
+
(error.closeCode === 1002 || error.closeCode === 4400 || error.closeCode === 4406);
|
|
320
|
+
}
|
|
288
321
|
async function sendBinary(socket, bytes) {
|
|
289
322
|
if (socket.readyState !== WebSocket.OPEN)
|
|
290
323
|
throw new Error("Desktop Browser Host lease is closed");
|
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.32",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@clack/prompts": "^1.6.0",
|
|
53
53
|
"ws": "^8.21.0",
|
|
54
|
-
"@rynx-ai/
|
|
55
|
-
"@rynx-ai/
|
|
56
|
-
"@rynx-ai/
|
|
57
|
-
"@rynx-ai/emulator": "0.1.11-beta.
|
|
58
|
-
"@rynx-ai/protocol": "0.1.11-beta.
|
|
59
|
-
"@rynx-ai/tmux": "0.1.11-beta.
|
|
54
|
+
"@rynx-ai/browser-cdp": "0.1.11-beta.32",
|
|
55
|
+
"@rynx-ai/core": "0.1.11-beta.32",
|
|
56
|
+
"@rynx-ai/daemon": "0.1.11-beta.32",
|
|
57
|
+
"@rynx-ai/emulator": "0.1.11-beta.32",
|
|
58
|
+
"@rynx-ai/protocol": "0.1.11-beta.32",
|
|
59
|
+
"@rynx-ai/tmux": "0.1.11-beta.32"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/ws": "^8.18.1"
|
package/skill-guides/browser.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browser
|
|
3
|
-
description: Use the bundled Rynx CLI to inspect and automate the Browser owned by the active Rynx Session
|
|
3
|
+
description: Use the bundled Rynx CLI to inspect and automate the Browser owned by the active Rynx Session, and to read or update the Session-wide Browser request-header policy, with safe failure handling.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rynx Browser
|
|
@@ -61,6 +61,46 @@ rynx browser screenshot --output /absolute/path/page.png --json
|
|
|
61
61
|
Do not request or connect to the raw CDP endpoint unless diagnosing Rynx itself.
|
|
62
62
|
Do not close a Browser generation the user still needs.
|
|
63
63
|
|
|
64
|
+
## Session-wide request headers
|
|
65
|
+
|
|
66
|
+
Inspect the current policy when needed. Header values are redacted in the
|
|
67
|
+
managed Session CLI so secrets do not enter the Agent transcript:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
rynx browser headers get --json
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Set headers for every current and future Browser page in this Rynx Session.
|
|
74
|
+
The policy covers document and subresource requests to every origin:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
rynx browser headers set \
|
|
78
|
+
--enable \
|
|
79
|
+
--header 'X-Environment: staging' \
|
|
80
|
+
--header 'X-Request-Source: rynx' \
|
|
81
|
+
--json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Changes take effect immediately. Existing pages use the new values on their
|
|
85
|
+
next request or refresh; requests already in flight are unchanged. Closing and
|
|
86
|
+
reopening the Session Browser keeps the policy. Deleting the Session or
|
|
87
|
+
restarting the resident daemon removes it, and a forked Session starts with no
|
|
88
|
+
header policy.
|
|
89
|
+
|
|
90
|
+
`set` reads the current policy internally, so `--disable` without `--header`
|
|
91
|
+
stops sending headers while retaining configured rows without exposing their
|
|
92
|
+
values. Use `--clear` to remove all rows. When an earlier `get` is the basis for
|
|
93
|
+
an edited update, pass its opaque revision with
|
|
94
|
+
`--expected-revision <revision>`; a conflict means another actor changed the
|
|
95
|
+
policy, so fetch it again instead of retrying stale values.
|
|
96
|
+
|
|
97
|
+
Header names are case-insensitively unique. Browser-controlled or unsafe names
|
|
98
|
+
such as `Authorization`, `Cookie`, `Host`, `Origin`, `Referer`, `User-Agent`,
|
|
99
|
+
`Content-Length`, `Proxy-*`, and `Sec-*` are rejected. Do not put header values
|
|
100
|
+
in logs, chat messages, or error reports. Both text and JSON CLI output use
|
|
101
|
+
`[redacted]`; use the Session settings UI when a human must inspect or edit a
|
|
102
|
+
sensitive value. Do not attempt to bypass this boundary from an active Session.
|
|
103
|
+
|
|
64
104
|
## Failure handling
|
|
65
105
|
|
|
66
106
|
- On endpoint access failure, follow **Sandbox boundary** and make no fallback
|
package/skills/rynx-cli/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rynx-cli
|
|
3
|
-
description: Use the bundled `rynx` CLI when a task needs to inspect or automate the Browser or Emulator owned by the active Rynx Session. Load the version-matched Browser or Emulator guide from the CLI before operating it. Prefer this over direct CDP, browser drivers, `serve-sim`, `simctl`, adb, guessed ports, or guessed App installations.
|
|
3
|
+
description: Use the bundled `rynx` CLI when a task needs to inspect or automate the Browser or Emulator owned by the active Rynx Session, or configure Session-wide Browser request headers. Load the version-matched Browser or Emulator guide from the CLI before operating it. Prefer this over direct CDP, browser drivers, Header-modifying extensions, `serve-sim`, `simctl`, adb, guessed ports, or guessed App installations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rynx CLI
|
|
@@ -21,9 +21,14 @@ rynx skills get browser
|
|
|
21
21
|
rynx skills get emulator
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Use `browser` for Browser inspection/automation and
|
|
25
|
-
inspection/control. Do not load an
|
|
26
|
-
|
|
24
|
+
Use `browser` for Browser inspection/automation and Session-wide Browser request
|
|
25
|
+
headers, and `emulator` for mobile device inspection/control. Do not load an
|
|
26
|
+
unrelated guide. These commands read guides shipped in the exact CLI npm
|
|
27
|
+
package and do not contact the daemon or network.
|
|
28
|
+
|
|
29
|
+
The managed Session CLI redacts configured Browser Header values. Do not try to
|
|
30
|
+
recover them through alternate endpoints or tools; a human can inspect or edit
|
|
31
|
+
sensitive values in Session settings.
|
|
27
32
|
|
|
28
33
|
If `rynx skills get <topic>` itself cannot execute, report its exact error and
|
|
29
34
|
stop. Do not scan `/Applications`, use `find` or `mdfind`, run `open`, `open -a`,
|