@rynx-ai/cli 0.1.11-beta.5 → 0.1.11-beta.51
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/autostart-environment.d.ts +5 -0
- package/dist/autostart-environment.js +64 -0
- package/dist/autostart-launcher.d.ts +1 -0
- package/dist/autostart-launcher.js +48 -0
- package/dist/autostart.d.ts +29 -0
- package/dist/autostart.js +266 -0
- package/dist/browser-cli-args.d.ts +9 -9
- package/dist/browser-cli-args.js +57 -75
- package/dist/commands/autostart.d.ts +1 -0
- package/dist/commands/autostart.js +58 -0
- package/dist/commands/browser.d.ts +4 -0
- package/dist/commands/browser.js +88 -119
- package/dist/commands/lifecycle.d.ts +3 -1
- package/dist/commands/lifecycle.js +85 -9
- package/dist/commands/plugin.js +52 -26
- package/dist/commands/setup.js +107 -17
- package/dist/commands/skills.js +2 -2
- package/dist/commands/update.js +2 -2
- package/dist/control-client.d.ts +23 -2
- package/dist/control-client.js +109 -11
- package/dist/desktop-browser-host-client.js +43 -10
- package/dist/legacy-maintenance.d.ts +8 -0
- package/dist/legacy-maintenance.js +13 -0
- package/dist/progress-display.d.ts +7 -0
- package/dist/progress-display.js +78 -0
- package/dist/run-cli.js +4 -0
- package/dist/standalone.d.ts +3 -1
- package/dist/standalone.js +20 -1
- package/dist/systemd-service.d.ts +53 -0
- package/dist/systemd-service.js +509 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +4 -7
- package/package.json +7 -7
- package/skill-guides/browser.md +99 -20
- package/skills/rynx-cli/SKILL.md +9 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { autostartPlatformLabel, disableAutostart, enableAutostart, inspectAutostart, } from "../autostart.js";
|
|
2
|
+
import { fail } from "./errors.js";
|
|
3
|
+
export async function runAutostartCommand(args) {
|
|
4
|
+
if (process.env.RYNX_DISTRIBUTION === "app") {
|
|
5
|
+
fail("Rynx App manages startup. Use the App's launch-at-login setting.");
|
|
6
|
+
}
|
|
7
|
+
const [action, ...rest] = args;
|
|
8
|
+
if (rest.length > 0)
|
|
9
|
+
fail(`autostart: unexpected argument ${rest[0]}`);
|
|
10
|
+
if (action === "enable") {
|
|
11
|
+
const state = enableAutostart();
|
|
12
|
+
console.log(`Rynx autostart enabled with ${autostartPlatformLabel() ?? state.manager}.`);
|
|
13
|
+
if (state.registrationPath)
|
|
14
|
+
console.log(`Registration: ${state.registrationPath}`);
|
|
15
|
+
console.log("Rynx will start at the next login. Start it now with `rynx start`.");
|
|
16
|
+
printLingerWarning(state);
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
if (action === "disable") {
|
|
20
|
+
const state = disableAutostart();
|
|
21
|
+
console.log("Rynx autostart disabled.");
|
|
22
|
+
console.log("The running daemon was not stopped; use `rynx stop` if needed.");
|
|
23
|
+
if (state.registered) {
|
|
24
|
+
console.warn("An autostart registration is still present; run `rynx autostart status`.");
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (action === "status") {
|
|
30
|
+
printAutostartState(inspectAutostart());
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
fail("Usage: rynx autostart <enable|disable|status>");
|
|
34
|
+
}
|
|
35
|
+
function printAutostartState(state) {
|
|
36
|
+
console.log(`manager: ${state.manager}`);
|
|
37
|
+
console.log(`supported: ${state.supported ? "yes" : "no"}`);
|
|
38
|
+
console.log(`registered: ${state.registered ? "yes" : "no"}`);
|
|
39
|
+
if (state.active !== undefined) {
|
|
40
|
+
console.log(`active: ${state.active ? "yes" : "no"}`);
|
|
41
|
+
}
|
|
42
|
+
if (state.lingerEnabled !== undefined) {
|
|
43
|
+
console.log(`linger: ${state.lingerEnabled ? "yes" : "no"}`);
|
|
44
|
+
}
|
|
45
|
+
if (state.registrationPath) {
|
|
46
|
+
console.log(`registration: ${state.registrationPath}`);
|
|
47
|
+
}
|
|
48
|
+
if (state.detail)
|
|
49
|
+
console.log(`detail: ${state.detail}`);
|
|
50
|
+
}
|
|
51
|
+
function printLingerWarning(state) {
|
|
52
|
+
if (state.manager !== "systemd-user" || state.lingerEnabled !== false)
|
|
53
|
+
return;
|
|
54
|
+
console.warn("Linger is disabled; Rynx stops when this user logs out.");
|
|
55
|
+
if (state.lingerEnableCommand) {
|
|
56
|
+
console.warn(`To keep it running across logout and start it before login, run manually: ${state.lingerEnableCommand}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1 +1,5 @@
|
|
|
1
|
+
import type { RuntimeBrowserRequestHeadersPolicy } from "@rynx-ai/protocol/runtime-browser";
|
|
1
2
|
export declare function runBrowserCommand(args: readonly string[]): Promise<number>;
|
|
3
|
+
export declare function projectRequestHeadersPolicyForOutput(policy: RuntimeBrowserRequestHeadersPolicy, showValues: boolean): RuntimeBrowserRequestHeadersPolicy & {
|
|
4
|
+
valuesRedacted: boolean;
|
|
5
|
+
};
|
package/dist/commands/browser.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { connectBrowserAutomation, } from "@rynx-ai/browser-cdp";
|
|
4
|
-
import WebSocket from "ws";
|
|
5
1
|
import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
|
|
6
|
-
import { callManagedRuntimeBrowser, callResidentRuntime,
|
|
2
|
+
import { callManagedRuntimeBrowser, callResidentRuntime, executeResidentBrowserCommand, ResidentBrowserCommandError, getResidentRuntimeLocalBrowserEndpoint, readOptionalManagedRuntimeBrowserCredential, } from "../control-client.js";
|
|
3
|
+
import { createProgressDisplay } from "../progress-display.js";
|
|
7
4
|
import { fail } from "./errors.js";
|
|
8
5
|
export async function runBrowserCommand(args) {
|
|
9
6
|
let parsed;
|
|
@@ -20,9 +17,18 @@ export async function runBrowserCommand(args) {
|
|
|
20
17
|
const channel = browserReleaseChannel(parsed.channel);
|
|
21
18
|
const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
|
|
22
19
|
const artifacts = createBrowserArtifactManagementService();
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
const progress = parsed.json ? undefined : createProgressDisplay();
|
|
21
|
+
progress?.start(parsed.subcommand === "install" ? "正在安装 Rynx Browser" : "正在更新 Rynx Browser");
|
|
22
|
+
let result;
|
|
23
|
+
try {
|
|
24
|
+
result = parsed.subcommand === "install"
|
|
25
|
+
? await artifacts.install(parsed.version ? { version: parsed.version } : { channel }, progress ? { onProgress: (message) => progress.update(message) } : {})
|
|
26
|
+
: await artifacts.update({ channel }, progress ? { onProgress: (message) => progress.update(message) } : {});
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
progress?.clear();
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
26
32
|
if (parsed.json) {
|
|
27
33
|
console.log(JSON.stringify({
|
|
28
34
|
resolved: result.resolved,
|
|
@@ -30,9 +36,7 @@ export async function runBrowserCommand(args) {
|
|
|
30
36
|
}, null, 2));
|
|
31
37
|
}
|
|
32
38
|
else {
|
|
33
|
-
|
|
34
|
-
console.log(message);
|
|
35
|
-
console.log(`Active Browser engine: Chrome for Testing ${result.installed.version}.`);
|
|
39
|
+
progress.succeed(`Rynx Browser 已就绪:Chrome for Testing ${result.installed.version}`);
|
|
36
40
|
}
|
|
37
41
|
return 0;
|
|
38
42
|
}
|
|
@@ -75,8 +79,47 @@ export async function runBrowserCommand(args) {
|
|
|
75
79
|
return 0;
|
|
76
80
|
}
|
|
77
81
|
const target = await resolveBrowserTarget(parsed);
|
|
78
|
-
if (
|
|
79
|
-
|
|
82
|
+
if (parsed.subcommand === "headers") {
|
|
83
|
+
const current = await callBrowserRuntime(target, "browser.request-headers.get", {
|
|
84
|
+
sessionId: target.sessionId,
|
|
85
|
+
});
|
|
86
|
+
if (parsed.headersAction === "get") {
|
|
87
|
+
if (parsed.showHeaderValues && target.credential) {
|
|
88
|
+
fail("browser headers get: --show-values is unavailable inside a managed Session; " +
|
|
89
|
+
"use the Session settings UI");
|
|
90
|
+
}
|
|
91
|
+
printRequestHeadersPolicy(current, parsed.json, parsed.showHeaderValues === true);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const headers = parsed.clearHeaders
|
|
95
|
+
? []
|
|
96
|
+
: parsed.headers?.map(parseCliRequestHeader) ?? current.headers;
|
|
97
|
+
const updated = await callBrowserRuntime(target, "browser.request-headers.set", {
|
|
98
|
+
sessionId: target.sessionId,
|
|
99
|
+
expectedRevision: parsed.expectedRevision ?? current.revision,
|
|
100
|
+
enabled: parsed.headersEnabled ?? current.enabled,
|
|
101
|
+
headers,
|
|
102
|
+
});
|
|
103
|
+
printRequestHeadersPolicy(updated, parsed.json, false);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (parsed.subcommand === "exec") {
|
|
107
|
+
try {
|
|
108
|
+
const result = await executeResidentBrowserCommand({ argv: parsed.argv, ...(parsed.page ? { pageId: parsed.page } : {}) }, { sessionId: target.sessionId, credential: target.credential });
|
|
109
|
+
if (!parsed.json && typeof result.data?.help === "string")
|
|
110
|
+
console.log(result.data.help);
|
|
111
|
+
else if (!parsed.json && typeof result.data.snapshot === "string")
|
|
112
|
+
console.log(result.data.snapshot);
|
|
113
|
+
else
|
|
114
|
+
console.log(JSON.stringify(result, null, 2));
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (!(error instanceof ResidentBrowserCommandError))
|
|
119
|
+
throw error;
|
|
120
|
+
console.error(parsed.json ? JSON.stringify({ ...error.details, status: error.status }) : error.message);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
80
123
|
}
|
|
81
124
|
const sessionId = target.sessionId;
|
|
82
125
|
if (parsed.subcommand === "open") {
|
|
@@ -125,86 +168,6 @@ function browserReleaseChannel(value) {
|
|
|
125
168
|
}
|
|
126
169
|
return normalized;
|
|
127
170
|
}
|
|
128
|
-
async function runBrowserAutomation(parsed, target) {
|
|
129
|
-
if (target.runtimeSelector !== "local") {
|
|
130
|
-
fail(`browser ${parsed.subcommand}: automation is available only on the local Runtime`);
|
|
131
|
-
}
|
|
132
|
-
const automationAccess = await getResidentRuntimeLocalBrowserAutomationAccess({
|
|
133
|
-
...(target.credential ? {} : { sessionId: target.sessionId }),
|
|
134
|
-
});
|
|
135
|
-
const descriptor = automationAccess.descriptor;
|
|
136
|
-
let client;
|
|
137
|
-
try {
|
|
138
|
-
client = await connectBrowserAutomation({
|
|
139
|
-
endpoint: descriptor.endpoint,
|
|
140
|
-
...(descriptor.pageTargetId ? { pageTargetId: descriptor.pageTargetId } : {}),
|
|
141
|
-
referenceKey: automationAccess.referenceKey,
|
|
142
|
-
createWebSocket: (endpoint) => new WebSocket(endpoint),
|
|
143
|
-
});
|
|
144
|
-
if (parsed.subcommand === "snapshot") {
|
|
145
|
-
const snapshot = await client.snapshot();
|
|
146
|
-
if (parsed.json) {
|
|
147
|
-
console.log(JSON.stringify({
|
|
148
|
-
sessionId: descriptor.sessionId,
|
|
149
|
-
browserGeneration: descriptor.browserGeneration,
|
|
150
|
-
snapshot,
|
|
151
|
-
}, null, 2));
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
printAutomationSnapshot(snapshot.nodes);
|
|
155
|
-
}
|
|
156
|
-
return 0;
|
|
157
|
-
}
|
|
158
|
-
if (parsed.subcommand === "navigate") {
|
|
159
|
-
const navigation = await client.navigate(parsed.url);
|
|
160
|
-
printAutomationResult(parsed, descriptor.browserGeneration, navigation);
|
|
161
|
-
return 0;
|
|
162
|
-
}
|
|
163
|
-
if (parsed.subcommand === "click") {
|
|
164
|
-
const clickTarget = parsed.ref !== undefined
|
|
165
|
-
? { ref: parsed.ref }
|
|
166
|
-
: parsed.selector !== undefined
|
|
167
|
-
? { selector: parsed.selector }
|
|
168
|
-
: { x: parsed.x, y: parsed.y };
|
|
169
|
-
await client.click(clickTarget);
|
|
170
|
-
printAutomationResult(parsed, descriptor.browserGeneration);
|
|
171
|
-
return 0;
|
|
172
|
-
}
|
|
173
|
-
if (parsed.subcommand === "type") {
|
|
174
|
-
const typeTarget = parsed.ref !== undefined
|
|
175
|
-
? { ref: parsed.ref }
|
|
176
|
-
: { selector: parsed.selector };
|
|
177
|
-
await client.type(typeTarget, parsed.text);
|
|
178
|
-
printAutomationResult(parsed, descriptor.browserGeneration);
|
|
179
|
-
return 0;
|
|
180
|
-
}
|
|
181
|
-
const screenshot = await client.screenshot({
|
|
182
|
-
...(parsed.format ? { format: parsed.format } : {}),
|
|
183
|
-
...(parsed.quality !== undefined ? { quality: parsed.quality } : {}),
|
|
184
|
-
});
|
|
185
|
-
const output = path.resolve(parsed.output);
|
|
186
|
-
if (!path.isAbsolute(parsed.output)) {
|
|
187
|
-
fail("browser screenshot: --output must be an absolute path");
|
|
188
|
-
}
|
|
189
|
-
await writeFile(output, Buffer.from(screenshot.data, "base64"), { mode: 0o600 });
|
|
190
|
-
if (parsed.json) {
|
|
191
|
-
console.log(JSON.stringify({
|
|
192
|
-
completed: true,
|
|
193
|
-
browserGeneration: descriptor.browserGeneration,
|
|
194
|
-
output,
|
|
195
|
-
format: screenshot.format,
|
|
196
|
-
mimeType: screenshot.mimeType,
|
|
197
|
-
}, null, 2));
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
console.log(output);
|
|
201
|
-
}
|
|
202
|
-
return 0;
|
|
203
|
-
}
|
|
204
|
-
finally {
|
|
205
|
-
client?.close();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
171
|
async function resolveBrowserTarget(args) {
|
|
209
172
|
const credential = await readOptionalManagedRuntimeBrowserCredential();
|
|
210
173
|
try {
|
|
@@ -244,34 +207,40 @@ function printBrowserPages(state) {
|
|
|
244
207
|
console.log(`${marker} ${page.pageId} ${page.url}${title}`);
|
|
245
208
|
}
|
|
246
209
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
210
|
+
function parseCliRequestHeader(value) {
|
|
211
|
+
const separator = value.indexOf(":");
|
|
212
|
+
if (separator <= 0) {
|
|
213
|
+
fail("browser headers set: --header must use 'Name: value'");
|
|
214
|
+
}
|
|
215
|
+
const name = value.slice(0, separator).trim();
|
|
216
|
+
const rawValue = value.slice(separator + 1);
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
value: rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue,
|
|
220
|
+
};
|
|
253
221
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
browserGeneration,
|
|
259
|
-
...result,
|
|
260
|
-
}, null, 2));
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
console.log(`Browser ${parsed.subcommand} completed.`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function printAutomationSnapshot(nodes) {
|
|
267
|
-
if (nodes.length === 0) {
|
|
268
|
-
console.log("(empty accessibility tree)");
|
|
222
|
+
function printRequestHeadersPolicy(policy, json, showValues) {
|
|
223
|
+
const output = projectRequestHeadersPolicyForOutput(policy, showValues);
|
|
224
|
+
if (json) {
|
|
225
|
+
console.log(JSON.stringify(output, null, 2));
|
|
269
226
|
return;
|
|
270
227
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
console.log(`${node.role}${name}${value}${ref}`);
|
|
228
|
+
console.log(`Browser request headers ${output.enabled ? "enabled" : "disabled"} · revision ${output.revision}`);
|
|
229
|
+
if (output.headers.length === 0) {
|
|
230
|
+
console.log(" (no configured headers)");
|
|
231
|
+
return;
|
|
276
232
|
}
|
|
233
|
+
for (const header of output.headers)
|
|
234
|
+
console.log(` ${header.name}: ${header.value}`);
|
|
235
|
+
}
|
|
236
|
+
export function projectRequestHeadersPolicyForOutput(policy, showValues) {
|
|
237
|
+
return {
|
|
238
|
+
enabled: policy.enabled,
|
|
239
|
+
revision: policy.revision,
|
|
240
|
+
valuesRedacted: !showValues,
|
|
241
|
+
headers: policy.headers.map((header) => ({
|
|
242
|
+
name: header.name,
|
|
243
|
+
value: showValues ? header.value : "[redacted]",
|
|
244
|
+
})),
|
|
245
|
+
};
|
|
277
246
|
}
|
|
@@ -1,17 +1,77 @@
|
|
|
1
1
|
import { startStandaloneDaemon, statusStandaloneDaemon, stopStandaloneDaemon, streamStandaloneDaemonLogs, } from "../standalone.js";
|
|
2
|
+
import { shutdownResidentDaemonIfIdle, waitForResidentDaemonExit, } from "../control-client.js";
|
|
3
|
+
import { resolveDaemonControlEndpoint } from "../control-endpoint.js";
|
|
4
|
+
import { assertMacLaunchdInvocation, refreshAutostart } from "../autostart.js";
|
|
5
|
+
import { assertLinuxSystemdInvocation } from "../systemd-service.js";
|
|
6
|
+
import { clearLegacyMaintenanceState } from "../legacy-maintenance.js";
|
|
2
7
|
import { fail } from "./errors.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
const SYSTEMD_SERVICE_FLAG = "--systemd-service";
|
|
9
|
+
const LAUNCHD_SERVICE_FLAG = "--launchd-service";
|
|
10
|
+
const IF_IDLE_FLAG = "--if-idle";
|
|
11
|
+
export async function runLifecycleCommand(command, args, options = {}) {
|
|
12
|
+
if (args.length === 1 && args[0] === SYSTEMD_SERVICE_FLAG) {
|
|
13
|
+
assertLinuxSystemdInvocation();
|
|
14
|
+
if (command === "start")
|
|
8
15
|
return startStandaloneDaemon();
|
|
9
|
-
|
|
10
|
-
return startStandaloneDaemon({ restart: true });
|
|
11
|
-
case "stop":
|
|
16
|
+
if (command === "stop")
|
|
12
17
|
return stopStandaloneDaemon();
|
|
18
|
+
fail(`${SYSTEMD_SERVICE_FLAG} is only valid for start and stop`);
|
|
19
|
+
}
|
|
20
|
+
if (args.length === 1 && args[0] === LAUNCHD_SERVICE_FLAG) {
|
|
21
|
+
assertMacLaunchdInvocation();
|
|
22
|
+
if (command === "start")
|
|
23
|
+
return startStandaloneDaemon();
|
|
24
|
+
fail(`${LAUNCHD_SERVICE_FLAG} is only valid for start`);
|
|
25
|
+
}
|
|
26
|
+
const stopIfIdle = command === "stop" && args.length === 1 && args[0] === IF_IDLE_FLAG;
|
|
27
|
+
const statusJson = command === "status" && args.length === 1 && args[0] === "--json";
|
|
28
|
+
if (args.length > 0 && !stopIfIdle && !statusJson) {
|
|
29
|
+
fail(`${command}: unexpected argument ${args[0]}`);
|
|
30
|
+
}
|
|
31
|
+
switch (command) {
|
|
32
|
+
case "start": {
|
|
33
|
+
refreshAutostart();
|
|
34
|
+
return startStandaloneDaemon(options.quiet ? { quiet: true } : undefined);
|
|
35
|
+
}
|
|
36
|
+
case "restart": {
|
|
37
|
+
refreshAutostart();
|
|
38
|
+
return startStandaloneDaemon({ restart: true, quiet: options.quiet });
|
|
39
|
+
}
|
|
40
|
+
case "stop": {
|
|
41
|
+
const endpoint = await resolveDaemonControlEndpoint();
|
|
42
|
+
if (endpoint &&
|
|
43
|
+
(endpoint.distribution !== "standalone" || endpoint.daemonLifecycle !== "standalone")) {
|
|
44
|
+
throw new Error("rynx stop supports only the standalone daemon; use the owning App lifecycle");
|
|
45
|
+
}
|
|
46
|
+
if (stopIfIdle && !endpoint) {
|
|
47
|
+
console.error("cannot verify whole-host idle because the standalone daemon is unavailable");
|
|
48
|
+
return 3;
|
|
49
|
+
}
|
|
50
|
+
if (stopIfIdle) {
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = await shutdownResidentDaemonIfIdle(endpoint);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.error("cannot complete whole-host idle check: " +
|
|
57
|
+
(error instanceof Error ? error.message : String(error)));
|
|
58
|
+
return 3;
|
|
59
|
+
}
|
|
60
|
+
if (result.outcome === "busy") {
|
|
61
|
+
console.error("rynx is busy: " +
|
|
62
|
+
`runningTurns=${result.activity.runningTurns} ` +
|
|
63
|
+
`pendingInteractions=${result.activity.pendingInteractions}`);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
if (!await waitForResidentDaemonExit(endpoint.pid)) {
|
|
67
|
+
console.error("rynx accepted whole-host idle shutdown but the daemon did not exit within 120 seconds; supervisor was left untouched");
|
|
68
|
+
return 5;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return stopStandaloneAndThenClearLegacyMaintenance();
|
|
72
|
+
}
|
|
13
73
|
case "status":
|
|
14
|
-
return statusStandaloneDaemon();
|
|
74
|
+
return statusStandaloneDaemon(statusJson ? { json: true } : undefined);
|
|
15
75
|
case "logs":
|
|
16
76
|
await streamStandaloneDaemonLogs();
|
|
17
77
|
return 0;
|
|
@@ -19,3 +79,19 @@ export async function runLifecycleCommand(command, args) {
|
|
|
19
79
|
fail(`unknown lifecycle command: ${command}`);
|
|
20
80
|
}
|
|
21
81
|
}
|
|
82
|
+
async function stopStandaloneAndThenClearLegacyMaintenance() {
|
|
83
|
+
const status = await stopStandaloneDaemon();
|
|
84
|
+
if (status !== 0)
|
|
85
|
+
return 1;
|
|
86
|
+
clearLegacyMaintenanceAfterStop();
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
function clearLegacyMaintenanceAfterStop() {
|
|
90
|
+
try {
|
|
91
|
+
clearLegacyMaintenanceState();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.warn("rynx lifecycle stopped but retired maintenance state could not be removed: " +
|
|
95
|
+
(error instanceof Error ? error.message : String(error)));
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/commands/plugin.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { cancelResidentPluginInstallation, commitResidentPluginInstallation,
|
|
2
|
+
import { cancelResidentPluginInstallation, commitResidentPluginInstallation, prepareResidentPluginInstallation, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
|
|
3
3
|
import { fail } from "./errors.js";
|
|
4
|
-
const PLUGIN_STDIN_MAX_BYTES = 256 * 1024;
|
|
5
4
|
const PLUGIN_DIGEST_PATTERN = /^sha256-[A-Za-z0-9+/]{43}={0,2}$/;
|
|
6
5
|
const CANONICAL_PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}@[a-z0-9][a-z0-9-]{0,62}(?:\/[a-z0-9][a-z0-9._-]{0,99})?$/;
|
|
6
|
+
const PLUGIN_USAGE = `Usage: rynx plugin <command|plugin-id>
|
|
7
|
+
|
|
8
|
+
Management:
|
|
9
|
+
list
|
|
10
|
+
install <source|plugin@market> [--force] [--expect-digest <sha256-...>]
|
|
11
|
+
update <plugin@market> [--expect-digest <sha256-...>]
|
|
12
|
+
enable|disable|uninstall <plugin@market>
|
|
13
|
+
|
|
14
|
+
Plugin commands:
|
|
15
|
+
<plugin-id> <command> [args...]
|
|
16
|
+
<plugin-id> --help`;
|
|
7
17
|
export async function runPluginManageCommand(args, options = {}) {
|
|
8
18
|
const [subcommand, pluginId] = args;
|
|
9
19
|
switch (subcommand) {
|
|
10
20
|
case "list": {
|
|
11
|
-
const
|
|
21
|
+
const { listInstalledPlugins } = await import("@rynx-ai/daemon/plugin-cli");
|
|
22
|
+
const plugins = listInstalledPlugins();
|
|
12
23
|
console.log("Plugins");
|
|
13
24
|
if (plugins.length === 0) {
|
|
14
25
|
console.log(" (none)");
|
|
@@ -126,23 +137,50 @@ export async function runPluginCommand(args) {
|
|
|
126
137
|
const [pluginId, command] = args;
|
|
127
138
|
if (!pluginId)
|
|
128
139
|
fail("plugin: missing plugin id");
|
|
140
|
+
if (pluginId === "--help" || pluginId === "-h" || pluginId === "help") {
|
|
141
|
+
console.log(PLUGIN_USAGE);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
129
144
|
if (["list", "install", "update", "uninstall", "enable", "disable"].includes(pluginId)) {
|
|
130
145
|
return runPluginManageCommand(args);
|
|
131
146
|
}
|
|
132
147
|
if (!command)
|
|
133
148
|
fail(`plugin ${pluginId}: missing command`);
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
const pluginCli = await import("@rynx-ai/daemon/plugin-cli");
|
|
150
|
+
if (command === "--help" || command === "-h") {
|
|
151
|
+
console.log(pluginCli.installedPluginCommandUsage(pluginId));
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
return runPluginCliWithForwardedSignals((signal) => pluginCli.runPluginCliCommand(pluginId, args.slice(1), {
|
|
155
|
+
stdio: "inherit",
|
|
156
|
+
signal,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
async function runPluginCliWithForwardedSignals(run) {
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
let interruptedBy;
|
|
162
|
+
const interrupt = (signal) => {
|
|
163
|
+
interruptedBy ??= signal;
|
|
164
|
+
if (!controller.signal.aborted) {
|
|
165
|
+
controller.abort(new Error(`plugin command interrupted by ${signal}`));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const onSigint = () => interrupt("SIGINT");
|
|
169
|
+
const onSigterm = () => interrupt("SIGTERM");
|
|
170
|
+
process.on("SIGINT", onSigint);
|
|
171
|
+
process.on("SIGTERM", onSigterm);
|
|
172
|
+
try {
|
|
173
|
+
return (await run(controller.signal)).code;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (interruptedBy)
|
|
177
|
+
return interruptedBy === "SIGINT" ? 130 : 143;
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
process.off("SIGINT", onSigint);
|
|
182
|
+
process.off("SIGTERM", onSigterm);
|
|
144
183
|
}
|
|
145
|
-
return result.code;
|
|
146
184
|
}
|
|
147
185
|
function parseInstallationArgs(operation, args) {
|
|
148
186
|
const target = args[0];
|
|
@@ -219,15 +257,3 @@ async function confirmInTerminal(message) {
|
|
|
219
257
|
prompt.close();
|
|
220
258
|
}
|
|
221
259
|
}
|
|
222
|
-
async function readStdinBounded(maxBytes) {
|
|
223
|
-
const chunks = [];
|
|
224
|
-
let bytes = 0;
|
|
225
|
-
for await (const chunk of process.stdin) {
|
|
226
|
-
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
227
|
-
bytes += value.byteLength;
|
|
228
|
-
if (bytes > maxBytes)
|
|
229
|
-
fail(`plugin stdin exceeds ${maxBytes} bytes`);
|
|
230
|
-
chunks.push(value);
|
|
231
|
-
}
|
|
232
|
-
return Buffer.concat(chunks, bytes).toString("utf8");
|
|
233
|
-
}
|