@rynx-ai/cli 0.1.11-beta.3 → 0.1.11-beta.31
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 +75 -6
- package/dist/commands/plugin.js +52 -26
- package/dist/commands/setup.js +59 -10
- package/dist/commands/skills.js +2 -2
- package/dist/control-client.js +2 -0
- package/dist/desktop-browser-host-client.js +43 -10
- package/dist/progress-display.d.ts +7 -0
- package/dist/progress-display.js +78 -0
- package/dist/standalone.js +3 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +1 -1
- package/package.json +8 -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
|
@@ -4,6 +4,7 @@ import { connectBrowserAutomation, } from "@rynx-ai/browser-cdp";
|
|
|
4
4
|
import WebSocket from "ws";
|
|
5
5
|
import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
|
|
6
6
|
import { callManagedRuntimeBrowser, callResidentRuntime, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
|
|
7
|
+
import { createProgressDisplay } from "../progress-display.js";
|
|
7
8
|
import { fail } from "./errors.js";
|
|
8
9
|
export async function runBrowserCommand(args) {
|
|
9
10
|
let parsed;
|
|
@@ -20,9 +21,18 @@ export async function runBrowserCommand(args) {
|
|
|
20
21
|
const channel = browserReleaseChannel(parsed.channel);
|
|
21
22
|
const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
|
|
22
23
|
const artifacts = createBrowserArtifactManagementService();
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const progress = parsed.json ? undefined : createProgressDisplay();
|
|
25
|
+
progress?.start(parsed.subcommand === "install" ? "正在安装 Rynx Browser" : "正在更新 Rynx Browser");
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = parsed.subcommand === "install"
|
|
29
|
+
? await artifacts.install(parsed.version ? { version: parsed.version } : { channel }, progress ? { onProgress: (message) => progress.update(message) } : {})
|
|
30
|
+
: await artifacts.update({ channel }, progress ? { onProgress: (message) => progress.update(message) } : {});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
progress?.clear();
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
26
36
|
if (parsed.json) {
|
|
27
37
|
console.log(JSON.stringify({
|
|
28
38
|
resolved: result.resolved,
|
|
@@ -30,9 +40,7 @@ export async function runBrowserCommand(args) {
|
|
|
30
40
|
}, null, 2));
|
|
31
41
|
}
|
|
32
42
|
else {
|
|
33
|
-
|
|
34
|
-
console.log(message);
|
|
35
|
-
console.log(`Active Browser engine: Chrome for Testing ${result.installed.version}.`);
|
|
43
|
+
progress.succeed(`Rynx Browser 已就绪:Chrome for Testing ${result.installed.version}`);
|
|
36
44
|
}
|
|
37
45
|
return 0;
|
|
38
46
|
}
|
|
@@ -75,6 +83,30 @@ export async function runBrowserCommand(args) {
|
|
|
75
83
|
return 0;
|
|
76
84
|
}
|
|
77
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
|
+
}
|
|
78
110
|
if (isAutomationCommand(parsed.subcommand)) {
|
|
79
111
|
return runBrowserAutomation(parsed, target);
|
|
80
112
|
}
|
|
@@ -244,6 +276,43 @@ function printBrowserPages(state) {
|
|
|
244
276
|
console.log(`${marker} ${page.pageId} ${page.url}${title}`);
|
|
245
277
|
}
|
|
246
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
|
+
}
|
|
247
316
|
function isAutomationCommand(subcommand) {
|
|
248
317
|
return subcommand === "snapshot"
|
|
249
318
|
|| subcommand === "navigate"
|
package/dist/commands/plugin.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { cancelResidentPluginInstallation, commitResidentPluginInstallation,
|
|
2
|
+
import { cancelResidentPluginInstallation, commitResidentPluginInstallation, prepareResidentPluginInstallation, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
|
|
3
3
|
import { fail } from "./errors.js";
|
|
4
|
-
const PLUGIN_STDIN_MAX_BYTES = 256 * 1024;
|
|
5
4
|
const PLUGIN_DIGEST_PATTERN = /^sha256-[A-Za-z0-9+/]{43}={0,2}$/;
|
|
6
5
|
const CANONICAL_PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}@[a-z0-9][a-z0-9-]{0,62}(?:\/[a-z0-9][a-z0-9._-]{0,99})?$/;
|
|
6
|
+
const PLUGIN_USAGE = `Usage: rynx plugin <command|plugin-id>
|
|
7
|
+
|
|
8
|
+
Management:
|
|
9
|
+
list
|
|
10
|
+
install <source|plugin@market> [--force] [--expect-digest <sha256-...>]
|
|
11
|
+
update <plugin@market> [--expect-digest <sha256-...>]
|
|
12
|
+
enable|disable|uninstall <plugin@market>
|
|
13
|
+
|
|
14
|
+
Plugin commands:
|
|
15
|
+
<plugin-id> <command> [args...]
|
|
16
|
+
<plugin-id> --help`;
|
|
7
17
|
export async function runPluginManageCommand(args, options = {}) {
|
|
8
18
|
const [subcommand, pluginId] = args;
|
|
9
19
|
switch (subcommand) {
|
|
10
20
|
case "list": {
|
|
11
|
-
const
|
|
21
|
+
const { listInstalledPlugins } = await import("@rynx-ai/daemon/plugin-cli");
|
|
22
|
+
const plugins = listInstalledPlugins();
|
|
12
23
|
console.log("Plugins");
|
|
13
24
|
if (plugins.length === 0) {
|
|
14
25
|
console.log(" (none)");
|
|
@@ -126,23 +137,50 @@ export async function runPluginCommand(args) {
|
|
|
126
137
|
const [pluginId, command] = args;
|
|
127
138
|
if (!pluginId)
|
|
128
139
|
fail("plugin: missing plugin id");
|
|
140
|
+
if (pluginId === "--help" || pluginId === "-h" || pluginId === "help") {
|
|
141
|
+
console.log(PLUGIN_USAGE);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
129
144
|
if (["list", "install", "update", "uninstall", "enable", "disable"].includes(pluginId)) {
|
|
130
145
|
return runPluginManageCommand(args);
|
|
131
146
|
}
|
|
132
147
|
if (!command)
|
|
133
148
|
fail(`plugin ${pluginId}: missing command`);
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const pluginCli = await import("@rynx-ai/daemon/plugin-cli");
|
|
150
|
+
if (command === "--help" || command === "-h") {
|
|
151
|
+
console.log(pluginCli.installedPluginCommandUsage(pluginId));
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
return runPluginCliWithForwardedSignals((signal) => pluginCli.runPluginCliCommand(pluginId, args.slice(1), {
|
|
155
|
+
stdio: "inherit",
|
|
156
|
+
signal,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
async function runPluginCliWithForwardedSignals(run) {
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
let interruptedBy;
|
|
162
|
+
const interrupt = (signal) => {
|
|
163
|
+
interruptedBy ??= signal;
|
|
164
|
+
if (!controller.signal.aborted) {
|
|
165
|
+
controller.abort(new Error(`plugin command interrupted by ${signal}`));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const onSigint = () => interrupt("SIGINT");
|
|
169
|
+
const onSigterm = () => interrupt("SIGTERM");
|
|
170
|
+
process.on("SIGINT", onSigint);
|
|
171
|
+
process.on("SIGTERM", onSigterm);
|
|
172
|
+
try {
|
|
173
|
+
return (await run(controller.signal)).code;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (interruptedBy)
|
|
177
|
+
return interruptedBy === "SIGINT" ? 130 : 143;
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
process.off("SIGINT", onSigint);
|
|
182
|
+
process.off("SIGTERM", onSigterm);
|
|
144
183
|
}
|
|
145
|
-
return result.code;
|
|
146
184
|
}
|
|
147
185
|
function parseInstallationArgs(operation, args) {
|
|
148
186
|
const target = args[0];
|
|
@@ -219,15 +257,3 @@ async function confirmInTerminal(message) {
|
|
|
219
257
|
prompt.close();
|
|
220
258
|
}
|
|
221
259
|
}
|
|
222
|
-
async function readStdinBounded(maxBytes) {
|
|
223
|
-
const chunks = [];
|
|
224
|
-
let bytes = 0;
|
|
225
|
-
for await (const chunk of process.stdin) {
|
|
226
|
-
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
227
|
-
bytes += value.byteLength;
|
|
228
|
-
if (bytes > maxBytes)
|
|
229
|
-
fail(`plugin stdin exceeds ${maxBytes} bytes`);
|
|
230
|
-
chunks.push(value);
|
|
231
|
-
}
|
|
232
|
-
return Buffer.concat(chunks, bytes).toString("utf8");
|
|
233
|
-
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -3,10 +3,13 @@ import { chmodSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, re
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import * as prompts from "@clack/prompts";
|
|
5
5
|
import { AGENT_RUNTIME_IDS, getRuntimeProfile, loadConfig, rynxConfigFile, } from "@rynx-ai/core";
|
|
6
|
-
import { inspectDaemonDiagnostics, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
|
|
6
|
+
import { inspectDaemonDiagnostics, inspectSystemDependencies, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
|
|
7
|
+
import { BUNDLED_TMUX_UNAVAILABLE_MESSAGE, resolveBundledTmux, } from "@rynx-ai/tmux";
|
|
8
|
+
import { createProgressDisplay } from "../progress-display.js";
|
|
7
9
|
import { fail } from "./errors.js";
|
|
8
10
|
export async function runSetupCommand(args) {
|
|
9
11
|
const options = parseSetupOptions(args);
|
|
12
|
+
const progress = options.json ? undefined : createProgressDisplay();
|
|
10
13
|
const interactive = !options.nonInteractive &&
|
|
11
14
|
!options.hasConfigurationArguments &&
|
|
12
15
|
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
@@ -50,23 +53,30 @@ export async function runSetupCommand(args) {
|
|
|
50
53
|
},
|
|
51
54
|
};
|
|
52
55
|
let failed = false;
|
|
56
|
+
progress?.start("正在准备 Rynx 内置插件");
|
|
53
57
|
try {
|
|
54
58
|
const plugins = await prepareBundledPlugins();
|
|
55
59
|
result.plugins = {
|
|
56
60
|
status: plugins.status,
|
|
57
61
|
changed: [...plugins.installed, ...plugins.updated],
|
|
58
62
|
};
|
|
63
|
+
progress?.succeed("Rynx 内置插件已就绪");
|
|
59
64
|
}
|
|
60
65
|
catch (error) {
|
|
61
66
|
failed = true;
|
|
67
|
+
progress?.clear();
|
|
68
|
+
const detail = errorMessage(error);
|
|
62
69
|
result.plugins = {
|
|
63
70
|
status: "error",
|
|
64
|
-
detail
|
|
71
|
+
detail,
|
|
65
72
|
};
|
|
73
|
+
console.error(`Rynx 内置插件准备失败:${detail}`);
|
|
66
74
|
}
|
|
75
|
+
progress?.start("正在检查 Rynx Browser");
|
|
67
76
|
const diagnostics = await inspectDaemonDiagnostics();
|
|
68
77
|
if (!diagnostics.browser.supported) {
|
|
69
78
|
result.browser = { action: options.browser, status: "unsupported" };
|
|
79
|
+
progress?.succeed("当前平台不支持 Rynx Browser");
|
|
70
80
|
}
|
|
71
81
|
else if (diagnostics.browser.installedVersion) {
|
|
72
82
|
result.browser = {
|
|
@@ -74,29 +84,39 @@ export async function runSetupCommand(args) {
|
|
|
74
84
|
status: "ready",
|
|
75
85
|
version: diagnostics.browser.installedVersion,
|
|
76
86
|
};
|
|
87
|
+
progress?.succeed(`Rynx Browser 已就绪:${diagnostics.browser.installedVersion}`);
|
|
77
88
|
}
|
|
78
89
|
else if (options.browser !== "skip") {
|
|
90
|
+
progress?.update("正在安装 Rynx Browser");
|
|
79
91
|
try {
|
|
80
92
|
const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
|
|
81
|
-
const installed = await createBrowserArtifactManagementService().install({ channel: "stable" },
|
|
82
|
-
? {}
|
|
83
|
-
: { onProgress: (message) => console.error(message) });
|
|
93
|
+
const installed = await createBrowserArtifactManagementService().install({ channel: "stable" }, progress ? { onProgress: (message) => progress.update(message) } : {});
|
|
84
94
|
result.browser = {
|
|
85
95
|
action: options.browser,
|
|
86
96
|
status: "ready",
|
|
87
97
|
version: installed.installed.version,
|
|
88
98
|
};
|
|
99
|
+
progress?.succeed(`Rynx Browser 已就绪:${installed.installed.version}`);
|
|
89
100
|
}
|
|
90
101
|
catch (error) {
|
|
91
102
|
failed = true;
|
|
103
|
+
progress?.clear();
|
|
104
|
+
const detail = errorMessage(error);
|
|
92
105
|
result.browser = {
|
|
93
106
|
action: options.browser,
|
|
94
107
|
status: "error",
|
|
95
|
-
detail
|
|
108
|
+
detail,
|
|
96
109
|
};
|
|
110
|
+
console.error(`Rynx Browser 安装失败:${detail}`);
|
|
97
111
|
}
|
|
98
112
|
}
|
|
99
|
-
|
|
113
|
+
else {
|
|
114
|
+
progress?.succeed("已跳过 Rynx Browser 安装");
|
|
115
|
+
}
|
|
116
|
+
if (options.resultFile) {
|
|
117
|
+
writeJsonAtomically(options.resultFile, result);
|
|
118
|
+
}
|
|
119
|
+
else if (options.json) {
|
|
100
120
|
console.log(JSON.stringify(result, null, 2));
|
|
101
121
|
}
|
|
102
122
|
else if (interactive) {
|
|
@@ -140,13 +160,25 @@ export async function runDoctorCommand(args) {
|
|
|
140
160
|
return { id, installed: !probe.error && probe.status === 0 };
|
|
141
161
|
});
|
|
142
162
|
const daemon = await inspectDaemonDiagnostics();
|
|
163
|
+
const tmuxBin = resolveBundledTmux();
|
|
164
|
+
const systemDependencies = tmuxBin
|
|
165
|
+
? await inspectSystemDependencies({ tmuxBin })
|
|
166
|
+
: {
|
|
167
|
+
tmux: {
|
|
168
|
+
installed: false,
|
|
169
|
+
detail: BUNDLED_TMUX_UNAVAILABLE_MESSAGE,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
143
172
|
const result = {
|
|
144
|
-
ok: configError === undefined &&
|
|
173
|
+
ok: configError === undefined &&
|
|
174
|
+
runtimes.some((runtime) => runtime.installed) &&
|
|
175
|
+
systemDependencies.tmux.installed,
|
|
145
176
|
configPath: rynxConfigFile(),
|
|
146
177
|
config,
|
|
147
178
|
configError,
|
|
148
179
|
runtimes,
|
|
149
180
|
daemon,
|
|
181
|
+
systemDependencies,
|
|
150
182
|
};
|
|
151
183
|
if (json)
|
|
152
184
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -159,6 +191,7 @@ export async function runDoctorCommand(args) {
|
|
|
159
191
|
for (const runtime of runtimes) {
|
|
160
192
|
console.log(`${runtime.id}: ${runtime.installed ? "installed" : "not installed"}`);
|
|
161
193
|
}
|
|
194
|
+
console.log(`tmux: ${systemDependencies.tmux.installed ? "installed" : "not installed"}`);
|
|
162
195
|
if (daemon.browser.supported) {
|
|
163
196
|
console.log(`browser: ${daemon.browser.installedVersion ?? daemon.browser.error ?? "not installed"}`);
|
|
164
197
|
}
|
|
@@ -186,6 +219,17 @@ function parseSetupOptions(args) {
|
|
|
186
219
|
options.nonInteractive = true;
|
|
187
220
|
continue;
|
|
188
221
|
}
|
|
222
|
+
if (arg === "--result-file") {
|
|
223
|
+
if (options.resultFile !== undefined)
|
|
224
|
+
fail("setup: duplicate option --result-file");
|
|
225
|
+
const value = args[index + 1];
|
|
226
|
+
if (!value || value.startsWith("--"))
|
|
227
|
+
fail("setup: --result-file requires a value");
|
|
228
|
+
options.resultFile = path.resolve(value);
|
|
229
|
+
options.nonInteractive = true;
|
|
230
|
+
index += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
189
233
|
if (arg === "--install-browser" || arg === "--skip-browser") {
|
|
190
234
|
if (options.browser !== "auto")
|
|
191
235
|
fail("setup: choose only one Browser action");
|
|
@@ -225,6 +269,9 @@ function parseSetupOptions(args) {
|
|
|
225
269
|
fail(`setup: unknown option ${arg}`);
|
|
226
270
|
}
|
|
227
271
|
}
|
|
272
|
+
if (options.json && options.resultFile) {
|
|
273
|
+
fail("setup: choose either --json or --result-file");
|
|
274
|
+
}
|
|
228
275
|
return options;
|
|
229
276
|
}
|
|
230
277
|
async function collectInteractiveSetup(config) {
|
|
@@ -290,14 +337,16 @@ function readRawConfig() {
|
|
|
290
337
|
}
|
|
291
338
|
}
|
|
292
339
|
function writeConfigAtomically(config) {
|
|
293
|
-
|
|
340
|
+
writeJsonAtomically(rynxConfigFile(), config);
|
|
341
|
+
}
|
|
342
|
+
function writeJsonAtomically(file, value) {
|
|
294
343
|
const directory = path.dirname(file);
|
|
295
344
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
296
345
|
const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.tmp`);
|
|
297
346
|
let descriptor;
|
|
298
347
|
try {
|
|
299
348
|
descriptor = openSync(temporary, "wx", 0o600);
|
|
300
|
-
writeFileSync(descriptor, `${JSON.stringify(
|
|
349
|
+
writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
301
350
|
closeSync(descriptor);
|
|
302
351
|
descriptor = undefined;
|
|
303
352
|
renameSync(temporary, file);
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isSkillPathComponent } from "@rynx-ai/core";
|
|
4
5
|
import { fail } from "./errors.js";
|
|
5
|
-
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
6
6
|
const MAX_SKILL_BYTES = 256 * 1024;
|
|
7
7
|
const BUILTIN_SKILL_NAMES = ["browser", "emulator"];
|
|
8
8
|
export async function runSkillsCommand(args) {
|
|
@@ -55,7 +55,7 @@ export async function listBuiltinSkills() {
|
|
|
55
55
|
return guides.filter((guide) => guide !== null);
|
|
56
56
|
}
|
|
57
57
|
export async function readBuiltinSkill(name, full = false) {
|
|
58
|
-
if (!
|
|
58
|
+
if (!isSkillPathComponent(name))
|
|
59
59
|
return null;
|
|
60
60
|
if (!BUILTIN_SKILL_NAMES.includes(name))
|
|
61
61
|
return null;
|
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");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as prompts from "@clack/prompts";
|
|
2
|
+
const DOWNLOAD_PERCENT_PATTERN = /下载进度:(\d{1,3})%$/u;
|
|
3
|
+
export function createProgressDisplay() {
|
|
4
|
+
if (!prompts.isTTY(process.stdout) || prompts.isCI()) {
|
|
5
|
+
return {
|
|
6
|
+
start: (message) => console.log(message),
|
|
7
|
+
update: (message) => console.log(message),
|
|
8
|
+
succeed: (message) => console.log(message),
|
|
9
|
+
clear: () => undefined,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const spinner = prompts.spinner({ output: process.stdout });
|
|
13
|
+
const progress = prompts.progress({
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
max: 100,
|
|
16
|
+
size: 28,
|
|
17
|
+
style: "block",
|
|
18
|
+
});
|
|
19
|
+
let spinnerActive = false;
|
|
20
|
+
let progressActive = false;
|
|
21
|
+
let completedPercent = 0;
|
|
22
|
+
const clear = () => {
|
|
23
|
+
if (progressActive)
|
|
24
|
+
progress.clear();
|
|
25
|
+
if (spinnerActive)
|
|
26
|
+
spinner.clear();
|
|
27
|
+
progressActive = false;
|
|
28
|
+
spinnerActive = false;
|
|
29
|
+
completedPercent = 0;
|
|
30
|
+
};
|
|
31
|
+
const finishDownload = () => {
|
|
32
|
+
if (!progressActive)
|
|
33
|
+
return;
|
|
34
|
+
progress.stop("Chrome for Testing 下载完成");
|
|
35
|
+
progressActive = false;
|
|
36
|
+
completedPercent = 0;
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
start(message) {
|
|
40
|
+
clear();
|
|
41
|
+
spinner.start(message);
|
|
42
|
+
spinnerActive = true;
|
|
43
|
+
},
|
|
44
|
+
update(message) {
|
|
45
|
+
const match = DOWNLOAD_PERCENT_PATTERN.exec(message);
|
|
46
|
+
if (match) {
|
|
47
|
+
const percent = Math.min(100, Number(match[1]));
|
|
48
|
+
if (spinnerActive) {
|
|
49
|
+
spinner.clear();
|
|
50
|
+
spinnerActive = false;
|
|
51
|
+
}
|
|
52
|
+
if (!progressActive) {
|
|
53
|
+
progress.start(message);
|
|
54
|
+
progressActive = true;
|
|
55
|
+
}
|
|
56
|
+
progress.advance(percent - completedPercent, message);
|
|
57
|
+
completedPercent = percent;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
finishDownload();
|
|
61
|
+
if (spinnerActive)
|
|
62
|
+
spinner.message(message);
|
|
63
|
+
else {
|
|
64
|
+
spinner.start(message);
|
|
65
|
+
spinnerActive = true;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
succeed(message) {
|
|
69
|
+
finishDownload();
|
|
70
|
+
if (spinnerActive)
|
|
71
|
+
spinner.stop(message);
|
|
72
|
+
else
|
|
73
|
+
prompts.log.success(message);
|
|
74
|
+
spinnerActive = false;
|
|
75
|
+
},
|
|
76
|
+
clear,
|
|
77
|
+
};
|
|
78
|
+
}
|
package/dist/standalone.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolveBundledTmux } from "@rynx-ai/tmux";
|
|
3
4
|
import { installedVersion } from "./version.js";
|
|
4
5
|
import { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
|
|
5
6
|
/** Resolve the canonical Builtin Skill directory shipped by this exact CLI. */
|
|
@@ -8,6 +9,7 @@ export function standaloneBuiltinSkillsDirectory(moduleUrl = import.meta.url) {
|
|
|
8
9
|
}
|
|
9
10
|
export async function startStandaloneDaemon(options = {}) {
|
|
10
11
|
const cliVersion = installedVersion();
|
|
12
|
+
const tmuxBin = resolveBundledTmux();
|
|
11
13
|
const { startDaemon } = await import("@rynx-ai/daemon/lifecycle");
|
|
12
14
|
return startDaemon({
|
|
13
15
|
force: options.restart ?? false,
|
|
@@ -16,6 +18,7 @@ export async function startStandaloneDaemon(options = {}) {
|
|
|
16
18
|
builtinSkillsDir: standaloneBuiltinSkillsDirectory(),
|
|
17
19
|
cliVersion,
|
|
18
20
|
productVersion: cliVersion,
|
|
21
|
+
...(tmuxBin ? { tmuxBin } : {}),
|
|
19
22
|
},
|
|
20
23
|
});
|
|
21
24
|
}
|
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]\n initialize configuration and local dependencies\n doctor read-only health check\n\nLifecycle:\n start | restart | stop | status | logs\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 | status | logs\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";
|
package/dist/usage.js
CHANGED
|
@@ -7,7 +7,7 @@ General:
|
|
|
7
7
|
Setup:
|
|
8
8
|
setup [--non-interactive] [--default-runtime <codex|traex|claude>]
|
|
9
9
|
[--host <host>] [--port <port>] [--log-level <level>]
|
|
10
|
-
[--install-browser|--skip-browser] [--json]
|
|
10
|
+
[--install-browser|--skip-browser] [--json|--result-file <path>]
|
|
11
11
|
initialize configuration and local dependencies
|
|
12
12
|
doctor read-only health check
|
|
13
13
|
|
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.31",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": ">=22"
|
|
13
|
+
"node": ">=22.16"
|
|
14
14
|
},
|
|
15
15
|
"publishConfig": {
|
|
16
16
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -51,11 +51,12 @@
|
|
|
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/
|
|
56
|
-
"@rynx-ai/
|
|
57
|
-
"@rynx-ai/
|
|
58
|
-
"@rynx-ai/protocol": "0.1.11-beta.
|
|
54
|
+
"@rynx-ai/core": "0.1.11-beta.31",
|
|
55
|
+
"@rynx-ai/daemon": "0.1.11-beta.31",
|
|
56
|
+
"@rynx-ai/browser-cdp": "0.1.11-beta.31",
|
|
57
|
+
"@rynx-ai/emulator": "0.1.11-beta.31",
|
|
58
|
+
"@rynx-ai/protocol": "0.1.11-beta.31",
|
|
59
|
+
"@rynx-ai/tmux": "0.1.11-beta.31"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
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`,
|