@rynx-ai/cli 0.1.11-beta.12 → 0.1.11-beta.14

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.
@@ -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 result = parsed.subcommand === "install"
24
- ? await artifacts.install(parsed.version ? { version: parsed.version } : { channel }, parsed.json ? {} : { onProgress: (message) => console.error(message) })
25
- : await artifacts.update({ channel }, parsed.json ? {} : { onProgress: (message) => console.error(message) });
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
- for (const message of result.messages)
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
  }
@@ -4,9 +4,11 @@ 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
6
  import { ensureSystemDependencies, inspectDaemonDiagnostics, inspectSystemDependencies, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
7
+ import { createProgressDisplay } from "../progress-display.js";
7
8
  import { fail } from "./errors.js";
8
9
  export async function runSetupCommand(args) {
9
10
  const options = parseSetupOptions(args);
11
+ const progress = options.json ? undefined : createProgressDisplay();
10
12
  const interactive = !options.nonInteractive &&
11
13
  !options.hasConfigurationArguments &&
12
14
  Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -53,36 +55,46 @@ export async function runSetupCommand(args) {
53
55
  },
54
56
  };
55
57
  let failed = false;
58
+ progress?.start("正在准备 Rynx 内置插件");
56
59
  try {
57
60
  const plugins = await prepareBundledPlugins();
58
61
  result.plugins = {
59
62
  status: plugins.status,
60
63
  changed: [...plugins.installed, ...plugins.updated],
61
64
  };
65
+ progress?.succeed("Rynx 内置插件已就绪");
62
66
  }
63
67
  catch (error) {
64
68
  failed = true;
69
+ progress?.clear();
70
+ const detail = errorMessage(error);
65
71
  result.plugins = {
66
72
  status: "error",
67
- detail: errorMessage(error),
73
+ detail,
68
74
  };
75
+ console.error(`Rynx 内置插件准备失败:${detail}`);
69
76
  }
77
+ progress?.start("正在检查 tmux");
70
78
  try {
71
79
  result.systemDependencies = await ensureSystemDependencies({
72
- onProgress: (message) => console.error(message),
80
+ ...(progress ? { onProgress: (message) => progress.update(message) } : {}),
73
81
  });
82
+ progress?.succeed("tmux 已就绪");
74
83
  }
75
84
  catch (error) {
76
85
  failed = true;
86
+ progress?.clear();
77
87
  const detail = errorMessage(error);
78
88
  result.systemDependencies = {
79
89
  tmux: { installed: false, detail },
80
90
  };
81
- console.error(`tmux: ${detail}`);
91
+ console.error(`tmux 准备失败:${detail}`);
82
92
  }
93
+ progress?.start("正在检查 Rynx Browser");
83
94
  const diagnostics = await inspectDaemonDiagnostics();
84
95
  if (!diagnostics.browser.supported) {
85
96
  result.browser = { action: options.browser, status: "unsupported" };
97
+ progress?.succeed("当前平台不支持 Rynx Browser");
86
98
  }
87
99
  else if (diagnostics.browser.installedVersion) {
88
100
  result.browser = {
@@ -90,29 +102,39 @@ export async function runSetupCommand(args) {
90
102
  status: "ready",
91
103
  version: diagnostics.browser.installedVersion,
92
104
  };
105
+ progress?.succeed(`Rynx Browser 已就绪:${diagnostics.browser.installedVersion}`);
93
106
  }
94
107
  else if (options.browser !== "skip") {
108
+ progress?.update("正在安装 Rynx Browser");
95
109
  try {
96
110
  const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
97
- const installed = await createBrowserArtifactManagementService().install({ channel: "stable" }, options.json
98
- ? {}
99
- : { onProgress: (message) => console.error(message) });
111
+ const installed = await createBrowserArtifactManagementService().install({ channel: "stable" }, progress ? { onProgress: (message) => progress.update(message) } : {});
100
112
  result.browser = {
101
113
  action: options.browser,
102
114
  status: "ready",
103
115
  version: installed.installed.version,
104
116
  };
117
+ progress?.succeed(`Rynx Browser 已就绪:${installed.installed.version}`);
105
118
  }
106
119
  catch (error) {
107
120
  failed = true;
121
+ progress?.clear();
122
+ const detail = errorMessage(error);
108
123
  result.browser = {
109
124
  action: options.browser,
110
125
  status: "error",
111
- detail: errorMessage(error),
126
+ detail,
112
127
  };
128
+ console.error(`Rynx Browser 安装失败:${detail}`);
113
129
  }
114
130
  }
115
- if (options.json) {
131
+ else {
132
+ progress?.succeed("已跳过 Rynx Browser 安装");
133
+ }
134
+ if (options.resultFile) {
135
+ writeJsonAtomically(options.resultFile, result);
136
+ }
137
+ else if (options.json) {
116
138
  console.log(JSON.stringify(result, null, 2));
117
139
  }
118
140
  else if (interactive) {
@@ -211,6 +233,17 @@ function parseSetupOptions(args) {
211
233
  options.nonInteractive = true;
212
234
  continue;
213
235
  }
236
+ if (arg === "--result-file") {
237
+ if (options.resultFile !== undefined)
238
+ fail("setup: duplicate option --result-file");
239
+ const value = args[index + 1];
240
+ if (!value || value.startsWith("--"))
241
+ fail("setup: --result-file requires a value");
242
+ options.resultFile = path.resolve(value);
243
+ options.nonInteractive = true;
244
+ index += 1;
245
+ continue;
246
+ }
214
247
  if (arg === "--install-browser" || arg === "--skip-browser") {
215
248
  if (options.browser !== "auto")
216
249
  fail("setup: choose only one Browser action");
@@ -250,6 +283,9 @@ function parseSetupOptions(args) {
250
283
  fail(`setup: unknown option ${arg}`);
251
284
  }
252
285
  }
286
+ if (options.json && options.resultFile) {
287
+ fail("setup: choose either --json or --result-file");
288
+ }
253
289
  return options;
254
290
  }
255
291
  async function collectInteractiveSetup(config) {
@@ -315,14 +351,16 @@ function readRawConfig() {
315
351
  }
316
352
  }
317
353
  function writeConfigAtomically(config) {
318
- const file = rynxConfigFile();
354
+ writeJsonAtomically(rynxConfigFile(), config);
355
+ }
356
+ function writeJsonAtomically(file, value) {
319
357
  const directory = path.dirname(file);
320
358
  mkdirSync(directory, { recursive: true, mode: 0o700 });
321
359
  const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.tmp`);
322
360
  let descriptor;
323
361
  try {
324
362
  descriptor = openSync(temporary, "wx", 0o600);
325
- writeFileSync(descriptor, `${JSON.stringify(config, null, 2)}\n`, "utf8");
363
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}\n`, "utf8");
326
364
  closeSync(descriptor);
327
365
  descriptor = undefined;
328
366
  renameSync(temporary, file);
@@ -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 (!SKILL_NAME_PATTERN.test(name))
58
+ if (!isSkillPathComponent(name))
59
59
  return null;
60
60
  if (!BUILTIN_SKILL_NAMES.includes(name))
61
61
  return null;
@@ -0,0 +1,7 @@
1
+ export interface ProgressDisplay {
2
+ start(message: string): void;
3
+ update(message: string): void;
4
+ succeed(message: string): void;
5
+ clear(): void;
6
+ }
7
+ export declare function createProgressDisplay(): ProgressDisplay;
@@ -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/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.12",
3
+ "version": "0.1.11-beta.14",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,11 +51,11 @@
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.6.0",
53
53
  "ws": "^8.21.0",
54
- "@rynx-ai/browser-cdp": "0.1.11-beta.12",
55
- "@rynx-ai/core": "0.1.11-beta.12",
56
- "@rynx-ai/daemon": "0.1.11-beta.12",
57
- "@rynx-ai/emulator": "0.1.11-beta.12",
58
- "@rynx-ai/protocol": "0.1.11-beta.12"
54
+ "@rynx-ai/browser-cdp": "0.1.11-beta.14",
55
+ "@rynx-ai/daemon": "0.1.11-beta.14",
56
+ "@rynx-ai/core": "0.1.11-beta.14",
57
+ "@rynx-ai/emulator": "0.1.11-beta.14",
58
+ "@rynx-ai/protocol": "0.1.11-beta.14"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/ws": "^8.18.1"