@rynx-ai/cli 0.1.11-beta.2 → 0.1.11-beta.21
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/app-runtime.d.ts +10 -0
- package/dist/app-runtime.js +34 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/commands/app-distribution.d.ts +2 -2
- package/dist/commands/app-distribution.js +2 -6
- package/dist/commands/browser.js +21 -9
- package/dist/commands/lifecycle.d.ts +1 -0
- package/dist/commands/lifecycle.js +21 -0
- package/dist/commands/plugin.js +52 -26
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +363 -0
- package/dist/commands/skills.js +2 -2
- package/dist/commands/update.d.ts +20 -0
- package/dist/commands/update.js +234 -0
- package/dist/control-client.d.ts +1 -10
- package/dist/control-client.js +1 -46
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/progress-display.d.ts +7 -0
- package/dist/progress-display.js +78 -0
- package/dist/run-cli.js +34 -8
- package/dist/standalone.d.ts +13 -0
- package/dist/standalone.js +57 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +6 -2
- package/package.json +19 -5
- package/dist/legacy-adapter.d.ts +0 -7
- package/dist/legacy-adapter.js +0 -63
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;
|
|
@@ -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
|
+
}
|
package/dist/control-client.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
|
|
2
|
-
import { type
|
|
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?: {
|
package/dist/control-client.js
CHANGED
|
@@ -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 {
|
|
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
package/dist/index.js
CHANGED
|
@@ -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/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
|
|
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 &&
|
|
31
|
-
|
|
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,57 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolveBundledTmux } from "@rynx-ai/tmux";
|
|
4
|
+
import { installedVersion } from "./version.js";
|
|
5
|
+
import { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
|
|
6
|
+
/** Resolve the canonical Builtin Skill directory shipped by this exact CLI. */
|
|
7
|
+
export function standaloneBuiltinSkillsDirectory(moduleUrl = import.meta.url) {
|
|
8
|
+
return path.resolve(fileURLToPath(new URL("../skills/", moduleUrl)));
|
|
9
|
+
}
|
|
10
|
+
export async function startStandaloneDaemon(options = {}) {
|
|
11
|
+
const cliVersion = installedVersion();
|
|
12
|
+
const tmuxBin = resolveBundledTmux();
|
|
13
|
+
const { startDaemon } = await import("@rynx-ai/daemon/lifecycle");
|
|
14
|
+
return startDaemon({
|
|
15
|
+
force: options.restart ?? false,
|
|
16
|
+
quiet: options.quiet,
|
|
17
|
+
context: {
|
|
18
|
+
builtinSkillsDir: standaloneBuiltinSkillsDirectory(),
|
|
19
|
+
cliVersion,
|
|
20
|
+
productVersion: cliVersion,
|
|
21
|
+
...(tmuxBin ? { tmuxBin } : {}),
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Reuse any healthy owner; otherwise explicitly start the standalone owner. */
|
|
26
|
+
export async function ensureStandaloneDaemon() {
|
|
27
|
+
const current = await resolveDaemonControlEndpoint();
|
|
28
|
+
if (current)
|
|
29
|
+
return { origin: current.origin };
|
|
30
|
+
const status = await startStandaloneDaemon({ quiet: true });
|
|
31
|
+
if (status !== 0)
|
|
32
|
+
throw new Error("failed to start the standalone daemon");
|
|
33
|
+
const deadline = Date.now() + 20_000;
|
|
34
|
+
while (Date.now() < deadline) {
|
|
35
|
+
try {
|
|
36
|
+
const endpoint = await ensureDaemonControlEndpoint();
|
|
37
|
+
return { origin: endpoint.origin };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const endpoint = await ensureDaemonControlEndpoint();
|
|
44
|
+
return { origin: endpoint.origin };
|
|
45
|
+
}
|
|
46
|
+
export async function stopStandaloneDaemon() {
|
|
47
|
+
const { stopDaemon } = await import("@rynx-ai/daemon/lifecycle");
|
|
48
|
+
return stopDaemon();
|
|
49
|
+
}
|
|
50
|
+
export async function statusStandaloneDaemon() {
|
|
51
|
+
const { statusDaemon } = await import("@rynx-ai/daemon/lifecycle");
|
|
52
|
+
return statusDaemon();
|
|
53
|
+
}
|
|
54
|
+
export async function streamStandaloneDaemonLogs() {
|
|
55
|
+
const { streamLogs } = await import("@rynx-ai/daemon/lifecycle");
|
|
56
|
+
streamLogs();
|
|
57
|
+
}
|
package/dist/usage.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const USAGE = "Usage: rynx <command>\n\nGeneral:\n version | --version
|
|
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
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export const USAGE = `Usage: rynx <command>
|
|
2
2
|
|
|
3
3
|
General:
|
|
4
|
-
version | --version
|
|
4
|
+
version [--json] | --version
|
|
5
|
+
print the installed Rynx version
|
|
5
6
|
|
|
6
7
|
Setup:
|
|
7
|
-
setup
|
|
8
|
+
setup [--non-interactive] [--default-runtime <codex|traex|claude>]
|
|
9
|
+
[--host <host>] [--port <port>] [--log-level <level>]
|
|
10
|
+
[--install-browser|--skip-browser] [--json|--result-file <path>]
|
|
11
|
+
initialize configuration and local dependencies
|
|
8
12
|
doctor read-only health check
|
|
9
13
|
|
|
10
14
|
Lifecycle:
|