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

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.
@@ -0,0 +1,10 @@
1
+ export declare const BUNDLED_TMUX_UNAVAILABLE_MESSAGE = "Rynx bundled tmux is unavailable; reinstall @rynx-ai/cli without omitting optional dependencies";
2
+ export interface BundledTmuxResolutionOptions {
3
+ platform?: string;
4
+ arch?: string;
5
+ resolve?: (specifier: string) => string;
6
+ }
7
+ /** Resolve the executable shipped for this CLI's exact OS/architecture. */
8
+ export declare function resolveBundledTmux(options?: BundledTmuxResolutionOptions): string | undefined;
9
+ /** An explicit operator override wins; otherwise prefer the CLI-bundled binary. */
10
+ export declare function resolveTmuxExecutable(env?: NodeJS.ProcessEnv, options?: BundledTmuxResolutionOptions): string | undefined;
@@ -0,0 +1,32 @@
1
+ import { accessSync, constants } from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ const require = createRequire(import.meta.url);
4
+ export const BUNDLED_TMUX_UNAVAILABLE_MESSAGE = "Rynx bundled tmux is unavailable; reinstall @rynx-ai/cli without omitting optional dependencies";
5
+ const BUNDLED_TMUX_PACKAGES = {
6
+ "darwin-arm64": "@rynx-ai/tmux-darwin-arm64/bin/tmux",
7
+ "darwin-x64": "@rynx-ai/tmux-darwin-x64/bin/tmux",
8
+ "linux-arm64": "@rynx-ai/tmux-linux-arm64/bin/tmux",
9
+ "linux-x64": "@rynx-ai/tmux-linux-x64/bin/tmux",
10
+ };
11
+ /** Resolve the executable shipped for this CLI's exact OS/architecture. */
12
+ export function resolveBundledTmux(options = {}) {
13
+ const platform = options.platform ?? process.platform;
14
+ const arch = options.arch ?? process.arch;
15
+ const specifier = BUNDLED_TMUX_PACKAGES[`${platform}-${arch}`];
16
+ if (!specifier)
17
+ return undefined;
18
+ let executable;
19
+ try {
20
+ executable = (options.resolve ?? require.resolve)(specifier);
21
+ accessSync(executable, constants.X_OK);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ return executable;
27
+ }
28
+ /** An explicit operator override wins; otherwise prefer the CLI-bundled binary. */
29
+ export function resolveTmuxExecutable(env = process.env, options = {}) {
30
+ const override = env.RYNX_TMUX_BIN?.trim();
31
+ return override || resolveBundledTmux(options);
32
+ }
@@ -1,14 +1,25 @@
1
1
  import { createInterface } from "node:readline/promises";
2
- import { cancelResidentPluginInstallation, commitResidentPluginInstallation, invokeResidentPluginCommand, listResidentPlugins, prepareResidentPluginInstallation, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
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 plugins = await listResidentPlugins();
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 stdin = process.stdin.isTTY
135
- ? undefined
136
- : await readStdinBounded(PLUGIN_STDIN_MAX_BYTES);
137
- const result = await invokeResidentPluginCommand(pluginId, args.slice(1), stdin === undefined ? {} : { stdin });
138
- if (result.stdout)
139
- process.stdout.write(result.stdout);
140
- if (result.stderr)
141
- process.stderr.write(result.stderr);
142
- if (result.truncated) {
143
- process.stderr.write("rynx: plugin command output was truncated by the daemon\n");
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
- }
@@ -3,8 +3,9 @@ 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 { ensureSystemDependencies, inspectDaemonDiagnostics, inspectSystemDependencies, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
6
+ import { inspectDaemonDiagnostics, inspectSystemDependencies, prepareBundledPlugins, verifySystemDependencies, } from "@rynx-ai/daemon/setup-service";
7
7
  import { createProgressDisplay } from "../progress-display.js";
8
+ import { BUNDLED_TMUX_UNAVAILABLE_MESSAGE, resolveTmuxExecutable, } from "../bundled-tmux.js";
8
9
  import { fail } from "./errors.js";
9
10
  export async function runSetupCommand(args) {
10
11
  const options = parseSetupOptions(args);
@@ -76,9 +77,11 @@ export async function runSetupCommand(args) {
76
77
  }
77
78
  progress?.start("正在检查 tmux");
78
79
  try {
79
- result.systemDependencies = await ensureSystemDependencies({
80
- ...(progress ? { onProgress: (message) => progress.update(message) } : {}),
81
- });
80
+ const tmuxBin = resolveTmuxExecutable();
81
+ if (!tmuxBin) {
82
+ throw new Error(BUNDLED_TMUX_UNAVAILABLE_MESSAGE);
83
+ }
84
+ result.systemDependencies = await verifySystemDependencies({ tmuxBin });
82
85
  progress?.succeed("tmux 已就绪");
83
86
  }
84
87
  catch (error) {
@@ -182,7 +185,15 @@ export async function runDoctorCommand(args) {
182
185
  return { id, installed: !probe.error && probe.status === 0 };
183
186
  });
184
187
  const daemon = await inspectDaemonDiagnostics();
185
- const systemDependencies = await inspectSystemDependencies();
188
+ const tmuxBin = resolveTmuxExecutable();
189
+ const systemDependencies = tmuxBin
190
+ ? await inspectSystemDependencies({ tmuxBin })
191
+ : {
192
+ tmux: {
193
+ installed: false,
194
+ detail: BUNDLED_TMUX_UNAVAILABLE_MESSAGE,
195
+ },
196
+ };
186
197
  const result = {
187
198
  ok: configError === undefined &&
188
199
  runtimes.some((runtime) => runtime.installed) &&
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { installedVersion } from "./version.js";
4
+ import { BUNDLED_TMUX_UNAVAILABLE_MESSAGE, resolveTmuxExecutable, } from "./bundled-tmux.js";
4
5
  import { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
5
6
  /** Resolve the canonical Builtin Skill directory shipped by this exact CLI. */
6
7
  export function standaloneBuiltinSkillsDirectory(moduleUrl = import.meta.url) {
@@ -8,6 +9,9 @@ export function standaloneBuiltinSkillsDirectory(moduleUrl = import.meta.url) {
8
9
  }
9
10
  export async function startStandaloneDaemon(options = {}) {
10
11
  const cliVersion = installedVersion();
12
+ const tmuxBin = resolveTmuxExecutable();
13
+ if (!tmuxBin)
14
+ throw new Error(BUNDLED_TMUX_UNAVAILABLE_MESSAGE);
11
15
  const { startDaemon } = await import("@rynx-ai/daemon/lifecycle");
12
16
  return startDaemon({
13
17
  force: options.restart ?? false,
@@ -16,6 +20,7 @@ export async function startStandaloneDaemon(options = {}) {
16
20
  builtinSkillsDir: standaloneBuiltinSkillsDirectory(),
17
21
  cliVersion,
18
22
  productVersion: cliVersion,
23
+ tmuxBin,
19
24
  },
20
25
  });
21
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/cli",
3
- "version": "0.1.11-beta.14",
3
+ "version": "0.1.11-beta.16",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,11 +51,17 @@
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.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"
54
+ "@rynx-ai/browser-cdp": "0.1.11-beta.16",
55
+ "@rynx-ai/core": "0.1.11-beta.16",
56
+ "@rynx-ai/daemon": "0.1.11-beta.16",
57
+ "@rynx-ai/emulator": "0.1.11-beta.16",
58
+ "@rynx-ai/protocol": "0.1.11-beta.16"
59
+ },
60
+ "optionalDependencies": {
61
+ "@rynx-ai/tmux-darwin-arm64": "0.1.11-beta.16",
62
+ "@rynx-ai/tmux-linux-arm64": "0.1.11-beta.16",
63
+ "@rynx-ai/tmux-darwin-x64": "0.1.11-beta.16",
64
+ "@rynx-ai/tmux-linux-x64": "0.1.11-beta.16"
59
65
  },
60
66
  "devDependencies": {
61
67
  "@types/ws": "^8.18.1"