@rynx-ai/cli 0.1.11-beta.2 → 0.1.11-beta.4

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 interface CliDependencyVersions {
2
+ cliVersion: string;
3
+ daemonVersion: string;
4
+ }
5
+ /** Read the versions in the dependency closure owned by this CLI installation. */
6
+ export declare function resolveCliDependencyVersions(): CliDependencyVersions;
7
+ /** Resolve the CLI-owned process entry used exclusively by Rynx App. */
8
+ export declare function resolveCliAppRuntimeEntry(moduleUrl?: string): string;
9
+ /** Run the App-owned daemon in this dedicated CLI App Runtime process. */
10
+ export declare function runAppRuntimeProcess(): Promise<void>;
@@ -0,0 +1,34 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { installedDaemonVersion } from "@rynx-ai/daemon/entry-path";
4
+ import { installedVersion } from "./version.js";
5
+ /** Read the versions in the dependency closure owned by this CLI installation. */
6
+ export function resolveCliDependencyVersions() {
7
+ return {
8
+ cliVersion: installedVersion(),
9
+ daemonVersion: installedDaemonVersion(),
10
+ };
11
+ }
12
+ /** Resolve the CLI-owned process entry used exclusively by Rynx App. */
13
+ export function resolveCliAppRuntimeEntry(moduleUrl = import.meta.url) {
14
+ return fileURLToPath(moduleUrl);
15
+ }
16
+ /** Run the App-owned daemon in this dedicated CLI App Runtime process. */
17
+ export async function runAppRuntimeProcess() {
18
+ if (process.env.RYNX_DISTRIBUTION !== "app") {
19
+ throw new Error("CLI App Runtime requires RYNX_DISTRIBUTION=app");
20
+ }
21
+ if (process.env.RYNX_DAEMON_LIFECYCLE !== "follow-app" &&
22
+ process.env.RYNX_DAEMON_LIFECYCLE !== "resident") {
23
+ throw new Error("CLI App Runtime requires an App daemon lifecycle");
24
+ }
25
+ const { runDaemonProcess } = await import("@rynx-ai/daemon/internal/process");
26
+ await runDaemonProcess();
27
+ }
28
+ if (process.argv[1] &&
29
+ path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
30
+ runAppRuntimeProcess().catch((error) => {
31
+ console.error(error instanceof Error ? error.stack ?? error.message : String(error));
32
+ process.exitCode = 1;
33
+ });
34
+ }
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanResidentBrowserArtifacts, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, ensureResidentDaemon, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, installResidentBrowserArtifact, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, type InvokeResidentPluginCommandOptions, type ResidentDaemon, type ResidentDaemonIdentity, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostFailure, type ResidentPluginCommandResult, type ResidentPluginManagementItem, type ResidentPluginManagementState, type ResidentRemoteRuntimeClientGrant, type ResidentRuntimeCallErrorCode, type ResidentRuntimeCallOutcome, type ResidentRuntimeTargetTestResult, type ResidentRuntimeTargetView, } from "./control-client.js";
1
+ export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, type InvokeResidentPluginCommandOptions, type ResidentDaemonIdentity, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostFailure, type ResidentPluginCommandResult, type ResidentPluginManagementItem, type ResidentPluginManagementState, type ResidentRemoteRuntimeClientGrant, type ResidentRuntimeCallErrorCode, type ResidentRuntimeCallOutcome, type ResidentRuntimeTargetTestResult, type ResidentRuntimeTargetView, } from "./control-client.js";
2
2
  export type { DaemonChromeInspectionConfigureInput, DaemonChromeInspectionStatus, } from "@rynx-ai/protocol/control";
3
3
  export type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, } from "@rynx-ai/protocol/plugin-management";
4
4
  export { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, type DaemonControlEndpoint, type ResolveDaemonControlEndpointOptions, } from "./control-endpoint.js";
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanResidentBrowserArtifacts, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, ensureResidentDaemon, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, installResidentBrowserArtifact, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, } from "./control-client.js";
1
+ export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, } from "./control-client.js";
2
2
  export { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
@@ -1,6 +1,6 @@
1
1
  export declare function shouldUseAppDistributionCommand(command: string | undefined, env?: NodeJS.ProcessEnv): boolean;
2
2
  /**
3
- * Prevent the App launcher from falling through to the compatibility daemon
4
- * CLI and accidentally creating or inspecting a PM2-owned second lifecycle.
3
+ * Prevent the App launcher from falling through to standalone lifecycle
4
+ * commands and accidentally creating or inspecting a PM2-owned second owner.
5
5
  */
6
6
  export declare function runAppDistributionCommand(command: string, args: readonly string[]): Promise<number>;
@@ -4,7 +4,6 @@ import { rynxHome } from "@rynx-ai/core";
4
4
  import { resolveDaemonControlEndpoint } from "../control-endpoint.js";
5
5
  import { fail } from "./errors.js";
6
6
  const APP_ONLY_COMMANDS = new Set([
7
- "setup",
8
7
  "update",
9
8
  "start",
10
9
  "restart",
@@ -19,8 +18,8 @@ export function shouldUseAppDistributionCommand(command, env = process.env) {
19
18
  && env.RYNX_DISTRIBUTION === "app";
20
19
  }
21
20
  /**
22
- * Prevent the App launcher from falling through to the compatibility daemon
23
- * CLI and accidentally creating or inspecting a PM2-owned second lifecycle.
21
+ * Prevent the App launcher from falling through to standalone lifecycle
22
+ * commands and accidentally creating or inspecting a PM2-owned second owner.
24
23
  */
25
24
  export async function runAppDistributionCommand(command, args) {
26
25
  const json = args.includes("--json");
@@ -44,9 +43,6 @@ export async function runAppDistributionCommand(command, args) {
44
43
  printRecentDaemonLogs();
45
44
  return 0;
46
45
  }
47
- if (command === "setup") {
48
- fail("Rynx App setup is completed in its onboarding and Desktop Settings.");
49
- }
50
46
  if (command === "update") {
51
47
  fail("Rynx App updates are managed from Rynx > Check for Updates.");
52
48
  }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { connectBrowserAutomation, } from "@rynx-ai/browser-cdp";
4
4
  import WebSocket from "ws";
5
5
  import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
6
- import { callManagedRuntimeBrowser, callResidentRuntime, cleanResidentBrowserArtifacts, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, installResidentBrowserArtifact, readOptionalManagedRuntimeBrowserCredential, updateResidentBrowserArtifact, } from "../control-client.js";
6
+ import { callManagedRuntimeBrowser, callResidentRuntime, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
7
7
  import { fail } from "./errors.js";
8
8
  export async function runBrowserCommand(args) {
9
9
  let parsed;
@@ -18,9 +18,11 @@ export async function runBrowserCommand(args) {
18
18
  fail("browser install: choose either --version or --channel");
19
19
  }
20
20
  const channel = browserReleaseChannel(parsed.channel);
21
+ const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
22
+ const artifacts = createBrowserArtifactManagementService();
21
23
  const result = parsed.subcommand === "install"
22
- ? await installResidentBrowserArtifact(parsed.version ? { version: parsed.version } : { channel })
23
- : await updateResidentBrowserArtifact({ channel });
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
26
  if (parsed.json) {
25
27
  console.log(JSON.stringify({
26
28
  resolved: result.resolved,
@@ -35,7 +37,8 @@ export async function runBrowserCommand(args) {
35
37
  return 0;
36
38
  }
37
39
  if (parsed.subcommand === "version") {
38
- const { installed } = await getResidentBrowserArtifactVersion();
40
+ const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
41
+ const { installed } = createBrowserArtifactManagementService().version();
39
42
  if (parsed.json)
40
43
  console.log(JSON.stringify({ installed }, null, 2));
41
44
  else if (installed) {
@@ -47,7 +50,8 @@ export async function runBrowserCommand(args) {
47
50
  return 0;
48
51
  }
49
52
  if (parsed.subcommand === "clean") {
50
- const result = await cleanResidentBrowserArtifacts();
53
+ const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
54
+ const result = createBrowserArtifactManagementService().clean();
51
55
  if (parsed.json)
52
56
  console.log(JSON.stringify(result, null, 2));
53
57
  else if (result.removedBuilds.length === 0) {
@@ -0,0 +1 @@
1
+ export declare function runLifecycleCommand(command: string, args: readonly string[]): Promise<number>;
@@ -0,0 +1,21 @@
1
+ import { startStandaloneDaemon, statusStandaloneDaemon, stopStandaloneDaemon, streamStandaloneDaemonLogs, } from "../standalone.js";
2
+ import { fail } from "./errors.js";
3
+ export async function runLifecycleCommand(command, args) {
4
+ if (args.length > 0)
5
+ fail(`${command}: unexpected argument ${args[0]}`);
6
+ switch (command) {
7
+ case "start":
8
+ return startStandaloneDaemon();
9
+ case "restart":
10
+ return startStandaloneDaemon({ restart: true });
11
+ case "stop":
12
+ return stopStandaloneDaemon();
13
+ case "status":
14
+ return statusStandaloneDaemon();
15
+ case "logs":
16
+ await streamStandaloneDaemonLogs();
17
+ return 0;
18
+ default:
19
+ fail(`unknown lifecycle command: ${command}`);
20
+ }
21
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runSetupCommand(args: readonly string[]): Promise<number>;
2
+ export declare function runDoctorCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,314 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { chmodSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
+ import path from "node:path";
4
+ import * as prompts from "@clack/prompts";
5
+ import { AGENT_RUNTIME_IDS, getRuntimeProfile, loadConfig, rynxConfigFile, } from "@rynx-ai/core";
6
+ import { inspectDaemonDiagnostics, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
7
+ import { fail } from "./errors.js";
8
+ export async function runSetupCommand(args) {
9
+ const options = parseSetupOptions(args);
10
+ const interactive = !options.nonInteractive &&
11
+ !options.hasConfigurationArguments &&
12
+ Boolean(process.stdin.isTTY && process.stdout.isTTY);
13
+ const existing = readRawConfig();
14
+ const created = !existsSync(rynxConfigFile());
15
+ const defaults = loadConfig({});
16
+ const next = {
17
+ ...existing,
18
+ HOST: existing.HOST ?? defaults.HOST,
19
+ PORT: existing.PORT ?? defaults.PORT,
20
+ LOG_LEVEL: existing.LOG_LEVEL ?? defaults.LOG_LEVEL,
21
+ DEFAULT_RUNTIME: existing.DEFAULT_RUNTIME ?? defaults.DEFAULT_RUNTIME,
22
+ };
23
+ if (interactive) {
24
+ if (!await collectInteractiveSetup(next))
25
+ return 0;
26
+ }
27
+ else {
28
+ if (options.defaultRuntime !== undefined)
29
+ next.DEFAULT_RUNTIME = options.defaultRuntime;
30
+ if (options.host !== undefined)
31
+ next.HOST = options.host;
32
+ if (options.port !== undefined)
33
+ next.PORT = options.port;
34
+ if (options.logLevel !== undefined)
35
+ next.LOG_LEVEL = options.logLevel;
36
+ }
37
+ validateSetupConfig(next);
38
+ const updated = ["HOST", "PORT", "LOG_LEVEL", "DEFAULT_RUNTIME"].filter((key) => existing[key] !== next[key]);
39
+ writeConfigAtomically(next);
40
+ const result = {
41
+ configPath: rynxConfigFile(),
42
+ created,
43
+ updated,
44
+ browser: {
45
+ action: options.browser,
46
+ status: "skipped",
47
+ },
48
+ plugins: {
49
+ status: "ready",
50
+ },
51
+ };
52
+ let failed = false;
53
+ try {
54
+ const plugins = await prepareBundledPlugins();
55
+ result.plugins = {
56
+ status: plugins.status,
57
+ changed: [...plugins.installed, ...plugins.updated],
58
+ };
59
+ }
60
+ catch (error) {
61
+ failed = true;
62
+ result.plugins = {
63
+ status: "error",
64
+ detail: errorMessage(error),
65
+ };
66
+ }
67
+ const diagnostics = await inspectDaemonDiagnostics();
68
+ if (!diagnostics.browser.supported) {
69
+ result.browser = { action: options.browser, status: "unsupported" };
70
+ }
71
+ else if (diagnostics.browser.installedVersion) {
72
+ result.browser = {
73
+ action: options.browser,
74
+ status: "ready",
75
+ version: diagnostics.browser.installedVersion,
76
+ };
77
+ }
78
+ else if (options.browser !== "skip") {
79
+ try {
80
+ const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
81
+ const installed = await createBrowserArtifactManagementService().install({ channel: "stable" }, options.json
82
+ ? {}
83
+ : { onProgress: (message) => console.error(message) });
84
+ result.browser = {
85
+ action: options.browser,
86
+ status: "ready",
87
+ version: installed.installed.version,
88
+ };
89
+ }
90
+ catch (error) {
91
+ failed = true;
92
+ result.browser = {
93
+ action: options.browser,
94
+ status: "error",
95
+ detail: errorMessage(error),
96
+ };
97
+ }
98
+ }
99
+ if (options.json) {
100
+ console.log(JSON.stringify(result, null, 2));
101
+ }
102
+ else if (interactive) {
103
+ prompts.log.success(`已写入 ${result.configPath}`);
104
+ prompts.log.info(`HOST=${next.HOST} PORT=${next.PORT} DEFAULT_RUNTIME=${next.DEFAULT_RUNTIME}`);
105
+ if (result.browser.status === "ready") {
106
+ prompts.log.success(`Session Browser ${result.browser.version ?? "ready"}`);
107
+ }
108
+ else if (result.browser.status === "error") {
109
+ prompts.log.warn(`Session Browser: ${result.browser.detail}`);
110
+ }
111
+ prompts.outro(failed ? "初始化完成,但存在失败项" : "配置完成");
112
+ }
113
+ else {
114
+ console.log(`Initialized ${result.configPath}`);
115
+ if (result.browser.status === "ready") {
116
+ console.log(`Browser ready${result.browser.version ? `: ${result.browser.version}` : ""}`);
117
+ }
118
+ }
119
+ return failed ? 1 : 0;
120
+ }
121
+ export async function runDoctorCommand(args) {
122
+ const json = args.includes("--json");
123
+ if (args.some((arg) => arg !== "--json")) {
124
+ fail(`doctor: unexpected argument ${args.find((arg) => arg !== "--json")}`);
125
+ }
126
+ let config;
127
+ let configError;
128
+ try {
129
+ config = loadConfig();
130
+ }
131
+ catch (error) {
132
+ configError = errorMessage(error);
133
+ }
134
+ const runtimes = AGENT_RUNTIME_IDS.map((id) => {
135
+ const binary = getRuntimeProfile(id).defaultBinary;
136
+ const probe = spawnSync(binary, ["--version"], {
137
+ stdio: "ignore",
138
+ timeout: 8_000,
139
+ });
140
+ return { id, installed: !probe.error && probe.status === 0 };
141
+ });
142
+ const daemon = await inspectDaemonDiagnostics();
143
+ const result = {
144
+ ok: configError === undefined && runtimes.some((runtime) => runtime.installed),
145
+ configPath: rynxConfigFile(),
146
+ config,
147
+ configError,
148
+ runtimes,
149
+ daemon,
150
+ };
151
+ if (json)
152
+ console.log(JSON.stringify(result, null, 2));
153
+ else {
154
+ console.log(`config: ${result.configPath}`);
155
+ if (configError)
156
+ console.error(`config: ${configError}`);
157
+ else
158
+ console.log(`default runtime: ${config.DEFAULT_RUNTIME}`);
159
+ for (const runtime of runtimes) {
160
+ console.log(`${runtime.id}: ${runtime.installed ? "installed" : "not installed"}`);
161
+ }
162
+ if (daemon.browser.supported) {
163
+ console.log(`browser: ${daemon.browser.installedVersion ?? daemon.browser.error ?? "not installed"}`);
164
+ }
165
+ else {
166
+ console.log("browser: unsupported");
167
+ }
168
+ }
169
+ return result.ok ? 0 : 1;
170
+ }
171
+ function parseSetupOptions(args) {
172
+ const options = {
173
+ nonInteractive: false,
174
+ json: false,
175
+ browser: "auto",
176
+ hasConfigurationArguments: false,
177
+ };
178
+ for (let index = 0; index < args.length; index += 1) {
179
+ const arg = args[index];
180
+ if (arg === "--non-interactive") {
181
+ options.nonInteractive = true;
182
+ continue;
183
+ }
184
+ if (arg === "--json") {
185
+ options.json = true;
186
+ options.nonInteractive = true;
187
+ continue;
188
+ }
189
+ if (arg === "--install-browser" || arg === "--skip-browser") {
190
+ if (options.browser !== "auto")
191
+ fail("setup: choose only one Browser action");
192
+ options.browser = arg === "--install-browser" ? "install" : "skip";
193
+ options.hasConfigurationArguments = true;
194
+ continue;
195
+ }
196
+ const value = args[index + 1];
197
+ if (!value || value.startsWith("--"))
198
+ fail(`setup: ${arg} requires a value`);
199
+ index += 1;
200
+ options.hasConfigurationArguments = true;
201
+ if (arg === "--default-runtime") {
202
+ if (!AGENT_RUNTIME_IDS.includes(value)) {
203
+ fail("setup: --default-runtime must be codex, traex, or claude");
204
+ }
205
+ options.defaultRuntime = value;
206
+ }
207
+ else if (arg === "--host") {
208
+ if (!value.trim() || value.length > 255)
209
+ fail("setup: --host is invalid");
210
+ options.host = value;
211
+ }
212
+ else if (arg === "--port") {
213
+ const port = Number(value);
214
+ if (!Number.isInteger(port) || port < 1 || port > 65_535) {
215
+ fail("setup: --port must be an integer from 1 to 65535");
216
+ }
217
+ options.port = port;
218
+ }
219
+ else if (arg === "--log-level") {
220
+ if (!value.trim() || value.length > 32)
221
+ fail("setup: --log-level is invalid");
222
+ options.logLevel = value;
223
+ }
224
+ else {
225
+ fail(`setup: unknown option ${arg}`);
226
+ }
227
+ }
228
+ return options;
229
+ }
230
+ async function collectInteractiveSetup(config) {
231
+ prompts.intro("rynx setup");
232
+ const runtime = await prompts.select({
233
+ message: "Default runtime",
234
+ initialValue: config.DEFAULT_RUNTIME,
235
+ options: AGENT_RUNTIME_IDS.map((id) => ({ value: id, label: id })),
236
+ });
237
+ if (prompts.isCancel(runtime))
238
+ return cancelled();
239
+ config.DEFAULT_RUNTIME = runtime;
240
+ const port = await prompts.text({
241
+ message: "HTTP port",
242
+ initialValue: String(config.PORT),
243
+ validate: (value) => {
244
+ const parsed = Number(value);
245
+ return Number.isInteger(parsed) && parsed > 0 && parsed <= 65_535
246
+ ? undefined
247
+ : "Enter a port from 1 to 65535";
248
+ },
249
+ });
250
+ if (prompts.isCancel(port))
251
+ return cancelled();
252
+ config.PORT = Number(port);
253
+ const host = await prompts.text({
254
+ message: "Bind host",
255
+ initialValue: String(config.HOST),
256
+ });
257
+ if (prompts.isCancel(host))
258
+ return cancelled();
259
+ config.HOST = host;
260
+ const logLevel = await prompts.text({
261
+ message: "Log level",
262
+ initialValue: String(config.LOG_LEVEL),
263
+ });
264
+ if (prompts.isCancel(logLevel))
265
+ return cancelled();
266
+ config.LOG_LEVEL = logLevel;
267
+ return true;
268
+ }
269
+ function cancelled() {
270
+ prompts.cancel("Cancelled");
271
+ return false;
272
+ }
273
+ function validateSetupConfig(config) {
274
+ loadConfig({
275
+ HOST: String(config.HOST),
276
+ PORT: String(config.PORT),
277
+ LOG_LEVEL: String(config.LOG_LEVEL),
278
+ DEFAULT_RUNTIME: String(config.DEFAULT_RUNTIME),
279
+ });
280
+ }
281
+ function readRawConfig() {
282
+ try {
283
+ const parsed = JSON.parse(readFileSync(rynxConfigFile(), "utf8"));
284
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
285
+ ? parsed
286
+ : {};
287
+ }
288
+ catch {
289
+ return {};
290
+ }
291
+ }
292
+ function writeConfigAtomically(config) {
293
+ const file = rynxConfigFile();
294
+ const directory = path.dirname(file);
295
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
296
+ const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.tmp`);
297
+ let descriptor;
298
+ try {
299
+ descriptor = openSync(temporary, "wx", 0o600);
300
+ writeFileSync(descriptor, `${JSON.stringify(config, null, 2)}\n`, "utf8");
301
+ closeSync(descriptor);
302
+ descriptor = undefined;
303
+ renameSync(temporary, file);
304
+ chmodSync(file, 0o600);
305
+ }
306
+ finally {
307
+ if (descriptor !== undefined)
308
+ closeSync(descriptor);
309
+ rmSync(temporary, { force: true });
310
+ }
311
+ }
312
+ function errorMessage(error) {
313
+ return error instanceof Error ? error.message : String(error);
314
+ }
@@ -0,0 +1,20 @@
1
+ export interface UpdateOptions {
2
+ check?: boolean;
3
+ json?: boolean;
4
+ version?: string;
5
+ }
6
+ export type UpdateCheck = {
7
+ status: "up_to_date";
8
+ current: string;
9
+ } | {
10
+ status: "behind";
11
+ current: string;
12
+ latest: string;
13
+ } | {
14
+ status: "error";
15
+ detail: string;
16
+ };
17
+ export declare function isNewer(latest: string, current: string): boolean;
18
+ export declare function buildUpdateCheck(current: string, latest: string | null): UpdateCheck;
19
+ export declare function checkStatusLine(current: string, latest: string | null): string;
20
+ export declare function runUpdate(options: UpdateOptions): Promise<number>;
@@ -0,0 +1,234 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import { dirname, join, sep } from "node:path";
5
+ import { rynxHome } from "@rynx-ai/core";
6
+ import { stopStandaloneDaemon } from "../standalone.js";
7
+ function selfPackage() {
8
+ const require = createRequire(import.meta.url);
9
+ const manifest = require("../../package.json");
10
+ return {
11
+ name: manifest.name ?? "@rynx-ai/cli",
12
+ version: manifest.version ?? "0.0.0",
13
+ };
14
+ }
15
+ function packageRoot() {
16
+ const require = createRequire(import.meta.url);
17
+ return dirname(require.resolve("../../package.json"));
18
+ }
19
+ function isLocalDevInstall() {
20
+ return !packageRoot().split(sep).includes("node_modules");
21
+ }
22
+ function latestVersion(name, current) {
23
+ const tag = current.includes("-") ? "next" : "latest";
24
+ const result = spawnSync("npm", ["view", `${name}@${tag}`, "version"], {
25
+ encoding: "utf8",
26
+ timeout: 60_000,
27
+ });
28
+ if (result.error || result.status !== 0)
29
+ return null;
30
+ return result.stdout.trim() || null;
31
+ }
32
+ export function isNewer(latest, current) {
33
+ return compareSemver(latest, current) > 0;
34
+ }
35
+ export function buildUpdateCheck(current, latest) {
36
+ if (!latest)
37
+ return { status: "error", detail: "could not resolve latest version" };
38
+ return isNewer(latest, current)
39
+ ? { status: "behind", current, latest }
40
+ : { status: "up_to_date", current };
41
+ }
42
+ export function checkStatusLine(current, latest) {
43
+ const check = buildUpdateCheck(current, latest);
44
+ if (check.status === "behind")
45
+ return `behind ${check.current} ${check.latest}`;
46
+ if (check.status === "up_to_date")
47
+ return `up_to_date ${check.current}`;
48
+ return `error ${check.detail}`;
49
+ }
50
+ export async function runUpdate(options) {
51
+ const { name, version: current } = selfPackage();
52
+ const latest = latestVersion(name, current);
53
+ if (options.check) {
54
+ const result = buildUpdateCheck(current, latest);
55
+ console.log(options.json ? JSON.stringify(result) : checkStatusLine(current, latest));
56
+ return result.status === "error" ? 1 : 0;
57
+ }
58
+ if (isLocalDevInstall()) {
59
+ console.error(`update: ${name} is a local development install`);
60
+ return 1;
61
+ }
62
+ const target = options.version ?? latest;
63
+ if (!target) {
64
+ console.error(`update: could not resolve a target version for ${name}`);
65
+ return 1;
66
+ }
67
+ if (!options.version && !isNewer(target, current)) {
68
+ console.log(`Already on the latest ${name}@${current}.`);
69
+ return 0;
70
+ }
71
+ const releaseLock = acquireUpdateLock();
72
+ if (!releaseLock)
73
+ return 1;
74
+ try {
75
+ if (await stopStandaloneDaemon() !== 0) {
76
+ console.error("update: could not stop the standalone daemon");
77
+ return 1;
78
+ }
79
+ if (!npmInstallGlobal(name, target)) {
80
+ console.error("update: npm install failed");
81
+ startFreshCli();
82
+ return 1;
83
+ }
84
+ if (!freshRuntimeMatches(target) || !startFreshCli()) {
85
+ console.error(`update: ${name}@${target} failed verification; rolling back to ${current}`);
86
+ if (!npmInstallGlobal(name, current)) {
87
+ console.error(`update: rollback to ${name}@${current} failed`);
88
+ return 1;
89
+ }
90
+ if (!freshRuntimeMatches(current) || !startFreshCli()) {
91
+ console.error(`update: ${name}@${current} was restored but failed to start`);
92
+ }
93
+ return 1;
94
+ }
95
+ console.log(`Updated ${name} to ${target}.`);
96
+ return 0;
97
+ }
98
+ finally {
99
+ releaseLock();
100
+ }
101
+ }
102
+ function compareSemver(left, right) {
103
+ const parse = (value) => {
104
+ const [core, prerelease] = value.split("-", 2);
105
+ const parts = core.split(".").map((part) => Number(part));
106
+ return { parts, prerelease: prerelease?.split(".") ?? [] };
107
+ };
108
+ const a = parse(left);
109
+ const b = parse(right);
110
+ for (let index = 0; index < 3; index += 1) {
111
+ const difference = (a.parts[index] ?? 0) - (b.parts[index] ?? 0);
112
+ if (difference !== 0)
113
+ return Math.sign(difference);
114
+ }
115
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
116
+ return a.prerelease.length === b.prerelease.length
117
+ ? 0
118
+ : a.prerelease.length === 0 ? 1 : -1;
119
+ }
120
+ const length = Math.max(a.prerelease.length, b.prerelease.length);
121
+ for (let index = 0; index < length; index += 1) {
122
+ const leftPart = a.prerelease[index];
123
+ const rightPart = b.prerelease[index];
124
+ if (leftPart === undefined || rightPart === undefined) {
125
+ return leftPart === rightPart ? 0 : leftPart === undefined ? -1 : 1;
126
+ }
127
+ if (leftPart === rightPart)
128
+ continue;
129
+ const leftNumber = /^\d+$/.test(leftPart) ? Number(leftPart) : undefined;
130
+ const rightNumber = /^\d+$/.test(rightPart) ? Number(rightPart) : undefined;
131
+ if (leftNumber !== undefined && rightNumber !== undefined) {
132
+ return Math.sign(leftNumber - rightNumber);
133
+ }
134
+ if (leftNumber !== undefined)
135
+ return -1;
136
+ if (rightNumber !== undefined)
137
+ return 1;
138
+ return leftPart.localeCompare(rightPart);
139
+ }
140
+ return 0;
141
+ }
142
+ function npmInstallGlobal(name, version) {
143
+ const installed = spawnSync("npm", ["install", "-g", `${name}@${version}`, "--no-audit", "--no-fund"], { stdio: "inherit", timeout: 600_000 });
144
+ return !installed.error && installed.status === 0;
145
+ }
146
+ function freshRuntimeMatches(version) {
147
+ const probe = spawnSync("rynx", ["version", "--json"], {
148
+ encoding: "utf8",
149
+ timeout: 60_000,
150
+ });
151
+ if (probe.error || probe.status !== 0)
152
+ return false;
153
+ try {
154
+ const runtime = JSON.parse(probe.stdout);
155
+ return runtime.cliVersion === version && runtime.daemonVersion === version;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ /**
162
+ * Run lifecycle through the newly installed bin. Calling an imported lifecycle
163
+ * module after npm replaces the package graph would execute stale code.
164
+ */
165
+ function startFreshCli() {
166
+ const restarted = spawnSync("rynx", ["start"], {
167
+ stdio: "inherit",
168
+ timeout: 60_000,
169
+ });
170
+ return !restarted.error && restarted.status === 0;
171
+ }
172
+ function acquireUpdateLock() {
173
+ const directory = join(rynxHome(), "state");
174
+ const file = join(directory, "update.lock");
175
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
176
+ let descriptor;
177
+ for (let attempt = 0; attempt < 2; attempt += 1) {
178
+ try {
179
+ descriptor = openSync(file, "wx", 0o600);
180
+ writeFileSync(descriptor, `${process.pid}\n`, "utf8");
181
+ break;
182
+ }
183
+ catch (error) {
184
+ if (error.code !== "EEXIST")
185
+ throw error;
186
+ const owner = readLockOwner(file);
187
+ if (owner !== undefined && processIsAlive(owner)) {
188
+ console.error(`update: another update is running (pid ${owner})`);
189
+ return null;
190
+ }
191
+ try {
192
+ unlinkSync(file);
193
+ }
194
+ catch (unlinkError) {
195
+ if (unlinkError.code !== "ENOENT") {
196
+ console.error("update: could not clear a stale update lock");
197
+ return null;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ if (descriptor === undefined) {
203
+ console.error("update: could not acquire the update lock");
204
+ return null;
205
+ }
206
+ return () => {
207
+ closeSync(descriptor);
208
+ try {
209
+ if (readLockOwner(file) === process.pid)
210
+ unlinkSync(file);
211
+ }
212
+ catch {
213
+ // The update already completed; a stale lock is recoverable next run.
214
+ }
215
+ };
216
+ }
217
+ function readLockOwner(file) {
218
+ try {
219
+ const value = Number(readFileSync(file, "utf8").trim());
220
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined;
221
+ }
222
+ catch {
223
+ return undefined;
224
+ }
225
+ }
226
+ function processIsAlive(pid) {
227
+ try {
228
+ process.kill(pid, 0);
229
+ return true;
230
+ }
231
+ catch (error) {
232
+ return error.code === "EPERM";
233
+ }
234
+ }
@@ -1,13 +1,10 @@
1
1
  import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
2
- import { type DaemonBrowserArtifactCleanResult, type DaemonBrowserArtifactInstallInput, type DaemonBrowserArtifactInstallResult, type DaemonBrowserArtifactUpdateInput, type DaemonBrowserArtifactVersionResult, type DaemonCleanupSessionsInput, type DaemonCleanupSessionsResult, type DaemonChromeInspectionConfigureInput, type DaemonChromeInspectionStatus, type DaemonShutdownIfIdleResult } from "@rynx-ai/protocol/control";
2
+ import { type DaemonCleanupSessionsInput, type DaemonCleanupSessionsResult, type DaemonChromeInspectionConfigureInput, type DaemonChromeInspectionStatus, type DaemonShutdownIfIdleResult } from "@rynx-ai/protocol/control";
3
3
  import { type RemoteRuntimeRpcMethod, type RemoteRuntimeRpcParams, type RemoteRuntimeRpcResultFor } from "@rynx-ai/protocol/remote-runtime-rpc";
4
4
  import { type PairingOffer } from "@rynx-ai/protocol/direct-runtime";
5
5
  import { type RuntimeBrowserBootstrapCredential, type RuntimeBrowserEndpointDescriptor } from "@rynx-ai/protocol/runtime-browser-bootstrap";
6
6
  import { type PluginInstallCommitInput, type PluginInstallCommitResult, type PluginInstallPreparation, type PluginInstallPrepareInput, type PluginManagementItem, type PluginManagementState, type PluginMarketplaceAddInput, type PluginMarketplaceItem } from "@rynx-ai/protocol/plugin-management";
7
7
  export { connectResidentDesktopBrowserHost, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostFailure, } from "./desktop-browser-host-client.js";
8
- export interface ResidentDaemon {
9
- origin: string;
10
- }
11
8
  export interface ResidentDaemonIdentity {
12
9
  installationId: string;
13
10
  algorithm: "Ed25519";
@@ -59,8 +56,6 @@ export declare class ResidentRuntimeCallError extends Error {
59
56
  outcome?: ResidentRuntimeCallOutcome;
60
57
  });
61
58
  }
62
- /** Connect to the resident daemon already owned by the App or standalone supervisor. */
63
- export declare function ensureResidentDaemon(): Promise<ResidentDaemon>;
64
59
  /** Read the stable public identity of the single resident daemon. */
65
60
  export declare function getResidentDaemonIdentity(): Promise<ResidentDaemonIdentity>;
66
61
  /** Read the transport-independent status of the resident daemon process. */
@@ -73,10 +68,6 @@ export declare function getResidentDaemonRuntimeStatus(): Promise<DaemonStatus>;
73
68
  export declare function shutdownResidentDaemonIfIdle(): Promise<DaemonShutdownIfIdleResult>;
74
69
  export declare function getResidentChromeInspectionStatus(): Promise<DaemonChromeInspectionStatus>;
75
70
  export declare function configureResidentChromeInspection(input: DaemonChromeInspectionConfigureInput): Promise<DaemonChromeInspectionStatus>;
76
- export declare function installResidentBrowserArtifact(input: DaemonBrowserArtifactInstallInput): Promise<DaemonBrowserArtifactInstallResult>;
77
- export declare function updateResidentBrowserArtifact(input: DaemonBrowserArtifactUpdateInput): Promise<DaemonBrowserArtifactInstallResult>;
78
- export declare function getResidentBrowserArtifactVersion(): Promise<DaemonBrowserArtifactVersionResult>;
79
- export declare function cleanResidentBrowserArtifacts(): Promise<DaemonBrowserArtifactCleanResult>;
80
71
  export declare function cleanupResidentSessions(input?: DaemonCleanupSessionsInput): Promise<DaemonCleanupSessionsResult>;
81
72
  /** Create one pairing offer on the resident daemon for a selected reachable route. */
82
73
  export declare function createResidentRemoteRuntimePairingOffer(input?: {
@@ -1,7 +1,7 @@
1
1
  import { open } from "node:fs/promises";
2
2
  import { isAbsolute } from "node:path";
3
3
  import { isDaemonCoreCompatible, parseDaemonStatus, } from "@rynx-ai/protocol/remote-runtime";
4
- import { DAEMON_BROWSER_ARTIFACT_CLEAN_PATH, DAEMON_BROWSER_ARTIFACT_INSTALL_PATH, DAEMON_BROWSER_ARTIFACT_UPDATE_PATH, DAEMON_BROWSER_ARTIFACT_VERSION_PATH, DAEMON_CHROME_INSPECTION_PATH, DAEMON_CLEANUP_SESSIONS_PATH, DAEMON_SHUTDOWN_IF_IDLE_PATH, parseDaemonBrowserArtifactCleanResult, parseDaemonBrowserArtifactInstallResult, parseDaemonBrowserArtifactVersionResult, parseDaemonCleanupSessionsResult, parseDaemonChromeInspectionConfigureInput, parseDaemonChromeInspectionStatus, parseDaemonShutdownIfIdleResult, } from "@rynx-ai/protocol/control";
4
+ import { DAEMON_CHROME_INSPECTION_PATH, DAEMON_CLEANUP_SESSIONS_PATH, DAEMON_SHUTDOWN_IF_IDLE_PATH, parseDaemonCleanupSessionsResult, parseDaemonChromeInspectionConfigureInput, parseDaemonChromeInspectionStatus, parseDaemonShutdownIfIdleResult, } from "@rynx-ai/protocol/control";
5
5
  import { parseRemoteRuntimeRpcRequest, parseRemoteRuntimeRpcResponseForMethod, REMOTE_RUNTIME_RPC_MAX_FRAME_BYTES, REMOTE_RUNTIME_RPC_METHOD_METADATA, } from "@rynx-ai/protocol/remote-runtime-rpc";
6
6
  import { parsePairingOffer, } from "@rynx-ai/protocol/direct-runtime";
7
7
  import { RUNTIME_BROWSER_BOOTSTRAP_PATH, RUNTIME_BROWSER_CAPABILITY_ENV, RUNTIME_BROWSER_CAPABILITY_HEADER, RUNTIME_BROWSER_CONTEXT_FILE_ENV, RUNTIME_BROWSER_MANAGEMENT_PATH_PREFIX, RUNTIME_BROWSER_RPC_PATH, RUNTIME_BROWSER_SESSION_ID_ENV, RUNTIME_BROWSER_SESSION_ID_HEADER, parseRuntimeBrowserBootstrapCredential, parseRuntimeBrowserEndpointDescriptor, } from "@rynx-ai/protocol/runtime-browser-bootstrap";
@@ -35,8 +35,6 @@ const PLUGIN_CLI_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
35
35
  const PLUGIN_MANAGEMENT_TIMEOUT_MS = 30_000;
36
36
  const PLUGIN_INSTALL_OPERATION_TIMEOUT_MS = 15 * 60_000;
37
37
  const PLUGIN_MANAGEMENT_RESPONSE_MAX_BYTES = 256 * 1024;
38
- const BROWSER_ARTIFACT_OPERATION_TIMEOUT_MS = 15 * 60_000;
39
- const BROWSER_ARTIFACT_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
40
38
  const MAINTENANCE_TIMEOUT_MS = 30_000;
41
39
  const MAINTENANCE_RESPONSE_MAX_BYTES = 64 * 1024;
42
40
  const CHROME_INSPECTION_TIMEOUT_MS = 30_000;
@@ -56,11 +54,6 @@ export class ResidentRuntimeCallError extends Error {
56
54
  this.outcome = options?.outcome;
57
55
  }
58
56
  }
59
- /** Connect to the resident daemon already owned by the App or standalone supervisor. */
60
- export async function ensureResidentDaemon() {
61
- const endpoint = await ensureDaemonControlEndpoint();
62
- return { origin: endpoint.origin };
63
- }
64
57
  /** Read the stable public identity of the single resident daemon. */
65
58
  export async function getResidentDaemonIdentity() {
66
59
  const endpoint = await ensureDaemonControlEndpoint();
@@ -160,44 +153,6 @@ export async function configureResidentChromeInspection(input) {
160
153
  });
161
154
  return parseDaemonChromeInspectionStatus(result);
162
155
  }
163
- export async function installResidentBrowserArtifact(input) {
164
- const result = await localManagementJsonRequest(DAEMON_BROWSER_ARTIFACT_INSTALL_PATH, {
165
- method: "POST",
166
- body: input,
167
- timeoutMs: BROWSER_ARTIFACT_OPERATION_TIMEOUT_MS,
168
- maxResponseBytes: BROWSER_ARTIFACT_RESPONSE_MAX_BYTES,
169
- operation: "Browser artifact install",
170
- });
171
- return parseDaemonBrowserArtifactInstallResult(result);
172
- }
173
- export async function updateResidentBrowserArtifact(input) {
174
- const result = await localManagementJsonRequest(DAEMON_BROWSER_ARTIFACT_UPDATE_PATH, {
175
- method: "POST",
176
- body: input,
177
- timeoutMs: BROWSER_ARTIFACT_OPERATION_TIMEOUT_MS,
178
- maxResponseBytes: BROWSER_ARTIFACT_RESPONSE_MAX_BYTES,
179
- operation: "Browser artifact update",
180
- });
181
- return parseDaemonBrowserArtifactInstallResult(result);
182
- }
183
- export async function getResidentBrowserArtifactVersion() {
184
- const result = await localManagementJsonRequest(DAEMON_BROWSER_ARTIFACT_VERSION_PATH, {
185
- method: "GET",
186
- timeoutMs: MAINTENANCE_TIMEOUT_MS,
187
- maxResponseBytes: BROWSER_ARTIFACT_RESPONSE_MAX_BYTES,
188
- operation: "Browser artifact version",
189
- });
190
- return parseDaemonBrowserArtifactVersionResult(result);
191
- }
192
- export async function cleanResidentBrowserArtifacts() {
193
- const result = await localManagementJsonRequest(DAEMON_BROWSER_ARTIFACT_CLEAN_PATH, {
194
- method: "POST",
195
- timeoutMs: MAINTENANCE_TIMEOUT_MS,
196
- maxResponseBytes: BROWSER_ARTIFACT_RESPONSE_MAX_BYTES,
197
- operation: "Browser artifact clean",
198
- });
199
- return parseDaemonBrowserArtifactCleanResult(result);
200
- }
201
156
  export async function cleanupResidentSessions(input = {}) {
202
157
  const result = await localManagementJsonRequest(DAEMON_CLEANUP_SESSIONS_PATH, {
203
158
  method: "POST",
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { runCli } from "./run-cli.js";
2
2
  export { USAGE } from "./usage.js";
3
+ export { installedVersion } from "./version.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { runCli } from "./run-cli.js";
2
2
  export { USAGE } from "./usage.js";
3
+ export { installedVersion } from "./version.js";
package/dist/run-cli.js CHANGED
@@ -9,13 +9,9 @@ import { runPluginCommand, } from "./commands/plugin.js";
9
9
  import { runRuntimeCommand } from "./commands/runtime.js";
10
10
  import { runSessionCommand } from "./commands/session.js";
11
11
  import { runSkillsCommand } from "./commands/skills.js";
12
- import { runLegacyDaemonCli } from "./legacy-adapter.js";
13
12
  import { USAGE } from "./usage.js";
14
13
  import { installedVersion } from "./version.js";
15
- const LEGACY_TOP_LEVEL_COMMANDS = new Set([
16
- "setup",
17
- "doctor",
18
- "update",
14
+ const LIFECYCLE_COMMANDS = new Set([
19
15
  "start",
20
16
  "restart",
21
17
  "stop",
@@ -27,12 +23,25 @@ export async function runCli(argv) {
27
23
  if (shouldUseAppDistributionCommand(command)) {
28
24
  return runAppDistributionCommand(command, argv.slice(1));
29
25
  }
30
- if (command && LEGACY_TOP_LEVEL_COMMANDS.has(command)) {
31
- return runLegacyDaemonCli(argv);
26
+ if (command && LIFECYCLE_COMMANDS.has(command)) {
27
+ const { runLifecycleCommand } = await import("./commands/lifecycle.js");
28
+ return runLifecycleCommand(command, argv.slice(1));
32
29
  }
33
30
  switch (command) {
34
- case "version":
31
+ case "version": {
32
+ if (argv.length === 2 && argv[1] === "--json") {
33
+ const { resolveCliDependencyVersions } = await import("./app-runtime.js");
34
+ console.log(JSON.stringify(resolveCliDependencyVersions()));
35
+ return 0;
36
+ }
37
+ if (argv.length > 1)
38
+ fail(`version: unexpected argument ${argv[1]}`);
39
+ console.log(installedVersion());
40
+ return 0;
41
+ }
35
42
  case "--version":
43
+ if (argv.length > 1)
44
+ fail(`--version: unexpected argument ${argv[1]}`);
36
45
  console.log(installedVersion());
37
46
  return 0;
38
47
  case "plugin":
@@ -53,6 +62,23 @@ export async function runCli(argv) {
53
62
  return runBrowserCommand(argv.slice(1));
54
63
  case "cleanup":
55
64
  return runCleanupCommand(argv.slice(1));
65
+ case "setup": {
66
+ const { runSetupCommand } = await import("./commands/setup.js");
67
+ return runSetupCommand(argv.slice(1));
68
+ }
69
+ case "doctor": {
70
+ const { runDoctorCommand } = await import("./commands/setup.js");
71
+ return runDoctorCommand(argv.slice(1));
72
+ }
73
+ case "update": {
74
+ const { runUpdate } = await import("./commands/update.js");
75
+ const rest = argv.slice(1);
76
+ return runUpdate({
77
+ check: rest.includes("--check"),
78
+ json: rest.includes("--json"),
79
+ version: rest.find((argument) => !argument.startsWith("--")),
80
+ });
81
+ }
56
82
  case undefined:
57
83
  case "help":
58
84
  case "-h":
@@ -0,0 +1,13 @@
1
+ /** Resolve the canonical Builtin Skill directory shipped by this exact CLI. */
2
+ export declare function standaloneBuiltinSkillsDirectory(moduleUrl?: string): string;
3
+ export declare function startStandaloneDaemon(options?: {
4
+ restart?: boolean;
5
+ quiet?: boolean;
6
+ }): Promise<number>;
7
+ /** Reuse any healthy owner; otherwise explicitly start the standalone owner. */
8
+ export declare function ensureStandaloneDaemon(): Promise<{
9
+ origin: string;
10
+ }>;
11
+ export declare function stopStandaloneDaemon(): Promise<number>;
12
+ export declare function statusStandaloneDaemon(): Promise<number>;
13
+ export declare function streamStandaloneDaemonLogs(): Promise<void>;
@@ -0,0 +1,54 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { installedVersion } from "./version.js";
4
+ import { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
5
+ /** Resolve the canonical Builtin Skill directory shipped by this exact CLI. */
6
+ export function standaloneBuiltinSkillsDirectory(moduleUrl = import.meta.url) {
7
+ return path.resolve(fileURLToPath(new URL("../skills/", moduleUrl)));
8
+ }
9
+ export async function startStandaloneDaemon(options = {}) {
10
+ const cliVersion = installedVersion();
11
+ const { startDaemon } = await import("@rynx-ai/daemon/lifecycle");
12
+ return startDaemon({
13
+ force: options.restart ?? false,
14
+ quiet: options.quiet,
15
+ context: {
16
+ builtinSkillsDir: standaloneBuiltinSkillsDirectory(),
17
+ cliVersion,
18
+ productVersion: cliVersion,
19
+ },
20
+ });
21
+ }
22
+ /** Reuse any healthy owner; otherwise explicitly start the standalone owner. */
23
+ export async function ensureStandaloneDaemon() {
24
+ const current = await resolveDaemonControlEndpoint();
25
+ if (current)
26
+ return { origin: current.origin };
27
+ const status = await startStandaloneDaemon({ quiet: true });
28
+ if (status !== 0)
29
+ throw new Error("failed to start the standalone daemon");
30
+ const deadline = Date.now() + 20_000;
31
+ while (Date.now() < deadline) {
32
+ try {
33
+ const endpoint = await ensureDaemonControlEndpoint();
34
+ return { origin: endpoint.origin };
35
+ }
36
+ catch {
37
+ await new Promise((resolve) => setTimeout(resolve, 100));
38
+ }
39
+ }
40
+ const endpoint = await ensureDaemonControlEndpoint();
41
+ return { origin: endpoint.origin };
42
+ }
43
+ export async function stopStandaloneDaemon() {
44
+ const { stopDaemon } = await import("@rynx-ai/daemon/lifecycle");
45
+ return stopDaemon();
46
+ }
47
+ export async function statusStandaloneDaemon() {
48
+ const { statusDaemon } = await import("@rynx-ai/daemon/lifecycle");
49
+ return statusDaemon();
50
+ }
51
+ export async function streamStandaloneDaemonLogs() {
52
+ const { streamLogs } = await import("@rynx-ai/daemon/lifecycle");
53
+ streamLogs();
54
+ }
package/dist/usage.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const USAGE = "Usage: rynx <command>\n\nGeneral:\n version | --version print the installed Rynx version\n\nSetup:\n setup interactive configuration wizard\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]\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
@@ -1,10 +1,14 @@
1
1
  export const USAGE = `Usage: rynx <command>
2
2
 
3
3
  General:
4
- version | --version print the installed Rynx version
4
+ version [--json] | --version
5
+ print the installed Rynx version
5
6
 
6
7
  Setup:
7
- setup interactive configuration wizard
8
+ setup [--non-interactive] [--default-runtime <codex|traex|claude>]
9
+ [--host <host>] [--port <port>] [--log-level <level>]
10
+ [--install-browser|--skip-browser] [--json]
11
+ initialize configuration and local dependencies
8
12
  doctor read-only health check
9
13
 
10
14
  Lifecycle:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/cli",
3
- "version": "0.1.11-beta.2",
3
+ "version": "0.1.11-beta.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -9,6 +9,9 @@
9
9
  "description": "Rynx command-line client and local control-plane management surface.",
10
10
  "license": "MIT",
11
11
  "type": "module",
12
+ "engines": {
13
+ "node": ">=22"
14
+ },
12
15
  "publishConfig": {
13
16
  "registry": "https://registry.npmjs.org/",
14
17
  "access": "public"
@@ -35,14 +38,24 @@
35
38
  "./commands": {
36
39
  "types": "./dist/commands/index.d.ts",
37
40
  "default": "./dist/commands/index.js"
41
+ },
42
+ "./standalone": {
43
+ "types": "./dist/standalone.d.ts",
44
+ "default": "./dist/standalone.js"
45
+ },
46
+ "./app-runtime": {
47
+ "types": "./dist/app-runtime.d.ts",
48
+ "default": "./dist/app-runtime.js"
38
49
  }
39
50
  },
40
51
  "dependencies": {
52
+ "@clack/prompts": "^1.6.0",
41
53
  "ws": "^8.21.0",
42
- "@rynx-ai/browser-cdp": "0.1.11-beta.2",
43
- "@rynx-ai/core": "0.1.11-beta.2",
44
- "@rynx-ai/emulator": "0.1.11-beta.2",
45
- "@rynx-ai/protocol": "0.1.11-beta.2"
54
+ "@rynx-ai/browser-cdp": "0.1.11-beta.4",
55
+ "@rynx-ai/core": "0.1.11-beta.4",
56
+ "@rynx-ai/daemon": "0.1.11-beta.4",
57
+ "@rynx-ai/emulator": "0.1.11-beta.4",
58
+ "@rynx-ai/protocol": "0.1.11-beta.4"
46
59
  },
47
60
  "devDependencies": {
48
61
  "@types/ws": "^8.18.1"
@@ -1,7 +0,0 @@
1
- /**
2
- * Temporary bridge for commands whose daemon-owned mutation has no management
3
- * RPC yet. It crosses the package boundary as a subprocess, never by importing
4
- * daemon implementation modules into the CLI process.
5
- */
6
- export declare function runLegacyDaemonCli(args: readonly string[]): Promise<number>;
7
- export declare function resolveLegacyDaemonCli(env?: NodeJS.ProcessEnv, moduleUrl?: string): string | null;
@@ -1,63 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync, realpathSync, statSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import path from "node:path";
5
- const LEGACY_CLI_ENV = "RYNX_LEGACY_CLI_PATH";
6
- /**
7
- * Temporary bridge for commands whose daemon-owned mutation has no management
8
- * RPC yet. It crosses the package boundary as a subprocess, never by importing
9
- * daemon implementation modules into the CLI process.
10
- */
11
- export async function runLegacyDaemonCli(args) {
12
- const cliPath = resolveLegacyDaemonCli();
13
- if (!cliPath) {
14
- throw new Error("This command still requires the legacy daemon CLI, but it is not installed. " +
15
- `Set ${LEGACY_CLI_ENV} to its absolute cli.js path or use the bundled Rynx App.`);
16
- }
17
- return new Promise((resolve, reject) => {
18
- const child = spawn(process.execPath, [cliPath, ...args], {
19
- stdio: "inherit",
20
- env: process.env,
21
- });
22
- child.once("error", (error) => {
23
- reject(new Error(`failed to launch legacy daemon CLI: ${error.message}`));
24
- });
25
- child.once("exit", (code, signal) => {
26
- if (signal) {
27
- reject(new Error(`legacy daemon CLI exited with signal ${signal}`));
28
- return;
29
- }
30
- resolve(code ?? 1);
31
- });
32
- });
33
- }
34
- export function resolveLegacyDaemonCli(env = process.env, moduleUrl = import.meta.url) {
35
- const configured = env[LEGACY_CLI_ENV];
36
- if (configured !== undefined) {
37
- if (!path.isAbsolute(configured)) {
38
- throw new Error(`${LEGACY_CLI_ENV} must be an absolute path`);
39
- }
40
- return validateCliFile(configured);
41
- }
42
- // Works both in the workspace and when @rynx-ai/cli and @rynx-ai/daemon are
43
- // siblings under node_modules/@rynx-ai.
44
- const sibling = fileURLToPath(new URL("../../daemon/dist/cli.js", moduleUrl));
45
- return validateCliFile(sibling);
46
- }
47
- function validateCliFile(candidate) {
48
- try {
49
- if (!existsSync(candidate) || !statSync(candidate).isFile())
50
- return null;
51
- const resolved = realpathSync(candidate);
52
- const current = process.argv[1];
53
- if (current &&
54
- existsSync(current) &&
55
- realpathSync(current) === resolved) {
56
- return null;
57
- }
58
- return resolved;
59
- }
60
- catch {
61
- return null;
62
- }
63
- }