@rynx-ai/cli 0.1.10

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.
Files changed (47) hide show
  1. package/dist/agent-file.d.ts +13 -0
  2. package/dist/agent-file.js +61 -0
  3. package/dist/browser-cli-args.d.ts +28 -0
  4. package/dist/browser-cli-args.js +181 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +11 -0
  7. package/dist/client.d.ts +4 -0
  8. package/dist/client.js +2 -0
  9. package/dist/commands/agent.d.ts +1 -0
  10. package/dist/commands/agent.js +55 -0
  11. package/dist/commands/app-distribution.d.ts +6 -0
  12. package/dist/commands/app-distribution.js +98 -0
  13. package/dist/commands/browser.d.ts +1 -0
  14. package/dist/commands/browser.js +273 -0
  15. package/dist/commands/cleanup.d.ts +1 -0
  16. package/dist/commands/cleanup.js +24 -0
  17. package/dist/commands/emulator.d.ts +1 -0
  18. package/dist/commands/emulator.js +23 -0
  19. package/dist/commands/errors.d.ts +5 -0
  20. package/dist/commands/errors.js +10 -0
  21. package/dist/commands/index.d.ts +9 -0
  22. package/dist/commands/index.js +9 -0
  23. package/dist/commands/plugin.d.ts +6 -0
  24. package/dist/commands/plugin.js +289 -0
  25. package/dist/commands/runtime.d.ts +1 -0
  26. package/dist/commands/runtime.js +136 -0
  27. package/dist/commands/skills.d.ts +10 -0
  28. package/dist/commands/skills.js +80 -0
  29. package/dist/control-client.d.ts +163 -0
  30. package/dist/control-client.js +1028 -0
  31. package/dist/control-endpoint.d.ts +29 -0
  32. package/dist/control-endpoint.js +121 -0
  33. package/dist/desktop-browser-host-client.d.ts +44 -0
  34. package/dist/desktop-browser-host-client.js +430 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +2 -0
  37. package/dist/legacy-adapter.d.ts +7 -0
  38. package/dist/legacy-adapter.js +63 -0
  39. package/dist/run-cli.d.ts +1 -0
  40. package/dist/run-cli.js +62 -0
  41. package/dist/usage.d.ts +1 -0
  42. package/dist/usage.js +56 -0
  43. package/dist/version.d.ts +1 -0
  44. package/dist/version.js +8 -0
  45. package/package.json +52 -0
  46. package/skills/rynx-cli/SKILL.md +99 -0
  47. package/skills/rynx-cli/agents/openai.yaml +4 -0
@@ -0,0 +1,273 @@
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
+ import { parseBrowserCliArgs, resolveBrowserCliTarget, } from "../browser-cli-args.js";
6
+ import { callManagedRuntimeBrowser, callResidentRuntime, cleanResidentBrowserArtifacts, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserAutomationAccess, getResidentRuntimeLocalBrowserEndpoint, installResidentBrowserArtifact, readOptionalManagedRuntimeBrowserCredential, updateResidentBrowserArtifact, } from "../control-client.js";
7
+ import { fail } from "./errors.js";
8
+ export async function runBrowserCommand(args) {
9
+ let parsed;
10
+ try {
11
+ parsed = parseBrowserCliArgs(args);
12
+ }
13
+ catch (error) {
14
+ fail(error instanceof Error ? error.message : String(error));
15
+ }
16
+ if (parsed.subcommand === "install" || parsed.subcommand === "update") {
17
+ if (parsed.version && parsed.channel) {
18
+ fail("browser install: choose either --version or --channel");
19
+ }
20
+ const channel = browserReleaseChannel(parsed.channel);
21
+ const result = parsed.subcommand === "install"
22
+ ? await installResidentBrowserArtifact(parsed.version ? { version: parsed.version } : { channel })
23
+ : await updateResidentBrowserArtifact({ channel });
24
+ if (parsed.json) {
25
+ console.log(JSON.stringify({
26
+ resolved: result.resolved,
27
+ installed: result.installed,
28
+ }, null, 2));
29
+ }
30
+ else {
31
+ for (const message of result.messages)
32
+ console.log(message);
33
+ console.log(`Active Browser engine: Chrome for Testing ${result.installed.version}.`);
34
+ }
35
+ return 0;
36
+ }
37
+ if (parsed.subcommand === "version") {
38
+ const { installed } = await getResidentBrowserArtifactVersion();
39
+ if (parsed.json)
40
+ console.log(JSON.stringify({ installed }, null, 2));
41
+ else if (installed) {
42
+ console.log(`Chrome for Testing ${installed.version} · ${installed.hostPlatform} · ${installed.sha256}`);
43
+ }
44
+ else {
45
+ console.log("No managed Chrome for Testing build is installed.");
46
+ }
47
+ return 0;
48
+ }
49
+ if (parsed.subcommand === "clean") {
50
+ const result = await cleanResidentBrowserArtifacts();
51
+ if (parsed.json)
52
+ console.log(JSON.stringify(result, null, 2));
53
+ else if (result.removedBuilds.length === 0) {
54
+ console.log("No inactive Chrome for Testing builds were eligible for removal.");
55
+ }
56
+ else {
57
+ console.log(`Removed ${result.removedBuilds.length} inactive Chrome for Testing build(s).`);
58
+ }
59
+ return 0;
60
+ }
61
+ const json = parsed.json;
62
+ if (parsed.subcommand === "endpoint") {
63
+ const target = await resolveBrowserTarget(parsed);
64
+ if (parsed.ensure) {
65
+ await callBrowserRuntime(target, "browser.open", { sessionId: target.sessionId });
66
+ }
67
+ const descriptor = await getResidentRuntimeLocalBrowserEndpoint({
68
+ ...(target.credential ? {} : { sessionId: target.sessionId }),
69
+ });
70
+ console.log(json ? JSON.stringify(descriptor, null, 2) : descriptor.endpoint);
71
+ return 0;
72
+ }
73
+ const target = await resolveBrowserTarget(parsed);
74
+ if (isAutomationCommand(parsed.subcommand)) {
75
+ return runBrowserAutomation(parsed, target);
76
+ }
77
+ const sessionId = target.sessionId;
78
+ if (parsed.subcommand === "open") {
79
+ const state = await callBrowserRuntime(target, "browser.open", {
80
+ sessionId,
81
+ ...(parsed.url ? { url: parsed.url } : {}),
82
+ });
83
+ printBrowserState(state, json);
84
+ return 0;
85
+ }
86
+ const state = await callBrowserRuntime(target, "browser.state.get", { sessionId });
87
+ if (parsed.subcommand === "status") {
88
+ printBrowserState(state, json);
89
+ return 0;
90
+ }
91
+ if (parsed.subcommand === "pages") {
92
+ if (json)
93
+ console.log(JSON.stringify({ pages: state?.pages ?? [] }, null, 2));
94
+ else
95
+ printBrowserPages(state);
96
+ return 0;
97
+ }
98
+ if (!state) {
99
+ if (json)
100
+ console.log(JSON.stringify({ closed: false, browser: null }, null, 2));
101
+ else
102
+ console.log(`No Browser is open for Session ${sessionId}.`);
103
+ return 0;
104
+ }
105
+ const result = await callBrowserRuntime(target, "browser.close", {
106
+ sessionId,
107
+ browserGeneration: state.browserGeneration,
108
+ });
109
+ console.log(json
110
+ ? JSON.stringify(result, null, 2)
111
+ : `Closed Browser generation ${result.browserGeneration} for Session ${sessionId}.`);
112
+ return 0;
113
+ }
114
+ function browserReleaseChannel(value) {
115
+ const normalized = value?.toLowerCase() ?? "stable";
116
+ if (normalized !== "stable" &&
117
+ normalized !== "beta" &&
118
+ normalized !== "dev" &&
119
+ normalized !== "canary") {
120
+ fail("browser: --channel must be stable, beta, dev, or canary");
121
+ }
122
+ return normalized;
123
+ }
124
+ async function runBrowserAutomation(parsed, target) {
125
+ if (target.runtimeSelector !== "local") {
126
+ fail(`browser ${parsed.subcommand}: automation is available only on the local Runtime`);
127
+ }
128
+ const automationAccess = await getResidentRuntimeLocalBrowserAutomationAccess({
129
+ ...(target.credential ? {} : { sessionId: target.sessionId }),
130
+ });
131
+ const descriptor = automationAccess.descriptor;
132
+ let client;
133
+ try {
134
+ client = await connectBrowserAutomation({
135
+ endpoint: descriptor.endpoint,
136
+ ...(descriptor.pageTargetId ? { pageTargetId: descriptor.pageTargetId } : {}),
137
+ referenceKey: automationAccess.referenceKey,
138
+ createWebSocket: (endpoint) => new WebSocket(endpoint),
139
+ });
140
+ if (parsed.subcommand === "snapshot") {
141
+ const snapshot = await client.snapshot();
142
+ if (parsed.json) {
143
+ console.log(JSON.stringify({
144
+ sessionId: descriptor.sessionId,
145
+ browserGeneration: descriptor.browserGeneration,
146
+ snapshot,
147
+ }, null, 2));
148
+ }
149
+ else {
150
+ printAutomationSnapshot(snapshot.nodes);
151
+ }
152
+ return 0;
153
+ }
154
+ if (parsed.subcommand === "navigate") {
155
+ const navigation = await client.navigate(parsed.url);
156
+ printAutomationResult(parsed, descriptor.browserGeneration, navigation);
157
+ return 0;
158
+ }
159
+ if (parsed.subcommand === "click") {
160
+ const clickTarget = parsed.ref !== undefined
161
+ ? { ref: parsed.ref }
162
+ : parsed.selector !== undefined
163
+ ? { selector: parsed.selector }
164
+ : { x: parsed.x, y: parsed.y };
165
+ await client.click(clickTarget);
166
+ printAutomationResult(parsed, descriptor.browserGeneration);
167
+ return 0;
168
+ }
169
+ if (parsed.subcommand === "type") {
170
+ const typeTarget = parsed.ref !== undefined
171
+ ? { ref: parsed.ref }
172
+ : { selector: parsed.selector };
173
+ await client.type(typeTarget, parsed.text);
174
+ printAutomationResult(parsed, descriptor.browserGeneration);
175
+ return 0;
176
+ }
177
+ const screenshot = await client.screenshot({
178
+ ...(parsed.format ? { format: parsed.format } : {}),
179
+ ...(parsed.quality !== undefined ? { quality: parsed.quality } : {}),
180
+ });
181
+ const output = path.resolve(parsed.output);
182
+ if (!path.isAbsolute(parsed.output)) {
183
+ fail("browser screenshot: --output must be an absolute path");
184
+ }
185
+ await writeFile(output, Buffer.from(screenshot.data, "base64"), { mode: 0o600 });
186
+ if (parsed.json) {
187
+ console.log(JSON.stringify({
188
+ completed: true,
189
+ browserGeneration: descriptor.browserGeneration,
190
+ output,
191
+ format: screenshot.format,
192
+ mimeType: screenshot.mimeType,
193
+ }, null, 2));
194
+ }
195
+ else {
196
+ console.log(output);
197
+ }
198
+ return 0;
199
+ }
200
+ finally {
201
+ client?.close();
202
+ }
203
+ }
204
+ async function resolveBrowserTarget(args) {
205
+ const credential = await readOptionalManagedRuntimeBrowserCredential();
206
+ try {
207
+ return resolveBrowserCliTarget(args, credential);
208
+ }
209
+ catch (error) {
210
+ fail(error instanceof Error ? error.message : String(error));
211
+ }
212
+ }
213
+ async function callBrowserRuntime(target, method, params) {
214
+ if (target.credential) {
215
+ return callManagedRuntimeBrowser(target.credential, method, params);
216
+ }
217
+ return callResidentRuntime(target.runtimeSelector, method, params);
218
+ }
219
+ function printBrowserState(state, json) {
220
+ if (json) {
221
+ console.log(JSON.stringify({ browser: state }, null, 2));
222
+ return;
223
+ }
224
+ if (!state) {
225
+ console.log("No Browser is open for this Session.");
226
+ return;
227
+ }
228
+ console.log(`Browser ${state.status} · ${state.executionBackend} · ` +
229
+ `generation ${state.browserGeneration}`);
230
+ printBrowserPages(state);
231
+ }
232
+ function printBrowserPages(state) {
233
+ if (!state || state.pages.length === 0) {
234
+ console.log(" (no Pages)");
235
+ return;
236
+ }
237
+ for (const page of state.pages) {
238
+ const marker = page.pageId === state.activePageId ? "*" : " ";
239
+ const title = page.title ? ` ${page.title}` : "";
240
+ console.log(`${marker} ${page.pageId} ${page.url}${title}`);
241
+ }
242
+ }
243
+ function isAutomationCommand(subcommand) {
244
+ return subcommand === "snapshot"
245
+ || subcommand === "navigate"
246
+ || subcommand === "click"
247
+ || subcommand === "type"
248
+ || subcommand === "screenshot";
249
+ }
250
+ function printAutomationResult(parsed, browserGeneration, result = {}) {
251
+ if (parsed.json) {
252
+ console.log(JSON.stringify({
253
+ completed: true,
254
+ browserGeneration,
255
+ ...result,
256
+ }, null, 2));
257
+ }
258
+ else {
259
+ console.log(`Browser ${parsed.subcommand} completed.`);
260
+ }
261
+ }
262
+ function printAutomationSnapshot(nodes) {
263
+ if (nodes.length === 0) {
264
+ console.log("(empty accessibility tree)");
265
+ return;
266
+ }
267
+ for (const node of nodes) {
268
+ const name = node.name ? ` ${JSON.stringify(node.name)}` : "";
269
+ const value = node.value ? ` value=${JSON.stringify(node.value)}` : "";
270
+ const ref = node.ref ? ` ref=${node.ref}` : "";
271
+ console.log(`${node.role}${name}${value}${ref}`);
272
+ }
273
+ }
@@ -0,0 +1 @@
1
+ export declare function runCleanupCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,24 @@
1
+ import { cleanupResidentSessions } from "../control-client.js";
2
+ import { fail } from "./errors.js";
3
+ export async function runCleanupCommand(args) {
4
+ if (args[0] !== "sessions") {
5
+ fail("cleanup: only `cleanup sessions` is supported");
6
+ }
7
+ let dryRun = false;
8
+ for (const arg of args.slice(1)) {
9
+ if (arg !== "--dry-run") {
10
+ fail(`cleanup sessions: unknown option ${arg}`);
11
+ }
12
+ if (dryRun)
13
+ fail("cleanup sessions: duplicate option --dry-run");
14
+ dryRun = true;
15
+ }
16
+ const result = await cleanupResidentSessions({ dryRun });
17
+ const summary = `${result.deletedSessions} session(s), ` +
18
+ `${result.deletedLogRows} log row(s), ` +
19
+ `${result.prunedStoreEntries} store entr(ies)`;
20
+ console.log(result.dryRun
21
+ ? `Dry run: would delete ${summary}.`
22
+ : `Deleted ${summary}.`);
23
+ return 0;
24
+ }
@@ -0,0 +1 @@
1
+ export declare function runEmulatorCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,23 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ export async function runEmulatorCommand(args) {
5
+ const require = createRequire(import.meta.url);
6
+ const cliPath = path.join(path.dirname(require.resolve("@rynx-ai/emulator")), "cli.js");
7
+ return runNodeCli(cliPath, args);
8
+ }
9
+ function runNodeCli(cliPath, args) {
10
+ return new Promise((resolve, reject) => {
11
+ const child = spawn(process.execPath, [cliPath, ...args], { stdio: "inherit" });
12
+ child.once("error", (error) => {
13
+ reject(new Error(`rynx emulator: failed to launch the bundled CLI: ${error.message}`));
14
+ });
15
+ child.once("exit", (code, signal) => {
16
+ if (signal) {
17
+ reject(new Error(`rynx emulator: bundled CLI exited with signal ${signal}`));
18
+ return;
19
+ }
20
+ resolve(code ?? 1);
21
+ });
22
+ });
23
+ }
@@ -0,0 +1,5 @@
1
+ export declare class CliUsageError extends Error {
2
+ readonly exitCode = 2;
3
+ constructor(message: string);
4
+ }
5
+ export declare function fail(message: string): never;
@@ -0,0 +1,10 @@
1
+ export class CliUsageError extends Error {
2
+ exitCode = 2;
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "CliUsageError";
6
+ }
7
+ }
8
+ export function fail(message) {
9
+ throw new CliUsageError(message);
10
+ }
@@ -0,0 +1,9 @@
1
+ export { runAgentCommand } from "./agent.js";
2
+ export { runAppDistributionCommand, shouldUseAppDistributionCommand, } from "./app-distribution.js";
3
+ export { runBrowserCommand } from "./browser.js";
4
+ export { runCleanupCommand } from "./cleanup.js";
5
+ export { runEmulatorCommand } from "./emulator.js";
6
+ export { CliUsageError } from "./errors.js";
7
+ export { type PluginManageCommandOptions, runPluginCommand, runPluginManageCommand, } from "./plugin.js";
8
+ export { runRuntimeCommand } from "./runtime.js";
9
+ export { builtinSkillsDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
@@ -0,0 +1,9 @@
1
+ export { runAgentCommand } from "./agent.js";
2
+ export { runAppDistributionCommand, shouldUseAppDistributionCommand, } from "./app-distribution.js";
3
+ export { runBrowserCommand } from "./browser.js";
4
+ export { runCleanupCommand } from "./cleanup.js";
5
+ export { runEmulatorCommand } from "./emulator.js";
6
+ export { CliUsageError } from "./errors.js";
7
+ export { runPluginCommand, runPluginManageCommand, } from "./plugin.js";
8
+ export { runRuntimeCommand } from "./runtime.js";
9
+ export { builtinSkillsDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
@@ -0,0 +1,6 @@
1
+ export interface PluginManageCommandOptions {
2
+ interactive?: boolean;
3
+ confirm?: (message: string) => Promise<boolean>;
4
+ }
5
+ export declare function runPluginManageCommand(args: readonly string[], options?: PluginManageCommandOptions): Promise<number>;
6
+ export declare function runPluginCommand(args: readonly string[]): Promise<number>;
@@ -0,0 +1,289 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { cancelResidentPluginInstallation, commitResidentPluginInstallation, invokeResidentPluginCommand, listResidentPlugins, prepareResidentPluginInstallation, reloadResidentPluginRuntime, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
3
+ import { fail } from "./errors.js";
4
+ const PLUGIN_STDIN_MAX_BYTES = 256 * 1024;
5
+ const PLUGIN_DIGEST_PATTERN = /^sha256-[A-Za-z0-9+/]{43}={0,2}$/;
6
+ export async function runPluginManageCommand(args, options = {}) {
7
+ const [subcommand, pluginId] = args;
8
+ switch (subcommand) {
9
+ case "list": {
10
+ const plugins = await listResidentPlugins();
11
+ console.log("Plugins");
12
+ if (plugins.length === 0) {
13
+ console.log(" (none)");
14
+ return 0;
15
+ }
16
+ for (const plugin of plugins) {
17
+ console.log(` ${plugin.id.padEnd(16)} ${(plugin.version ?? "-").padEnd(10)} ` +
18
+ `${plugin.source.padEnd(6)} ${plugin.state}`);
19
+ }
20
+ return 0;
21
+ }
22
+ case "enable":
23
+ case "disable": {
24
+ if (!pluginId)
25
+ fail(`plugin manage ${subcommand}: missing plugin id`);
26
+ await setResidentPluginEnabled(pluginId, subcommand === "enable");
27
+ console.log(`Plugin "${pluginId}" ${subcommand}d. Run \`rynx restart\` to apply.`);
28
+ return 0;
29
+ }
30
+ case "uninstall": {
31
+ if (!pluginId)
32
+ fail("plugin manage uninstall: missing plugin id");
33
+ await uninstallResidentPlugin(pluginId);
34
+ console.log(`Plugin "${pluginId}" uninstalled. Run \`rynx restart\` to apply.`);
35
+ return 0;
36
+ }
37
+ case "install":
38
+ case "update": {
39
+ const parsed = parseInstallationArgs(subcommand, args.slice(1));
40
+ const preparation = await prepareResidentPluginInstallation({
41
+ operation: subcommand,
42
+ ...(subcommand === "install"
43
+ ? { spec: parsed.target }
44
+ : { pluginId: parsed.target }),
45
+ allowScripts: parsed.allowScripts,
46
+ });
47
+ let committed = false;
48
+ try {
49
+ if (!parsed.json)
50
+ printPreparation(preparation);
51
+ const interactive = options.interactive ??
52
+ Boolean(process.stdin.isTTY && process.stdout.isTTY);
53
+ const confirm = options.confirm ?? confirmInTerminal;
54
+ if (parsed.expectedDigest !== undefined &&
55
+ parsed.expectedDigest !== preparation.artifact.digest) {
56
+ fail(`prepared plugin digest ${preparation.artifact.digest} does not match ` +
57
+ `--expect-digest ${parsed.expectedDigest}`);
58
+ }
59
+ if ((!interactive || parsed.json) &&
60
+ (parsed.force || parsed.grantAll || parsed.allowScripts) &&
61
+ parsed.expectedDigest === undefined) {
62
+ fail("non-interactive --force/--grant-all/--allow-scripts approval must be bound to the " +
63
+ `prepared artifact; re-run with --expect-digest ${preparation.artifact.digest}`);
64
+ }
65
+ if (preparation.artifact.requiresBuildScripts && !parsed.allowScripts) {
66
+ fail(`plugin "${preparation.artifact.id}" requires package build scripts; ` +
67
+ `review digest ${preparation.artifact.digest} and re-run with ` +
68
+ `--allow-scripts --expect-digest ${preparation.artifact.digest}`);
69
+ }
70
+ if (preparation.artifact.requiresBuildScripts &&
71
+ parsed.allowScripts &&
72
+ parsed.expectedDigest === undefined) {
73
+ const approved = await confirm(`Run declared build scripts from digest ${preparation.artifact.digest}?`);
74
+ if (!approved) {
75
+ console.log("Plugin installation cancelled; build scripts were not run.");
76
+ return 0;
77
+ }
78
+ }
79
+ let overwrite = subcommand === "update";
80
+ if (subcommand === "install" && preparation.existing) {
81
+ overwrite = parsed.force;
82
+ if (!overwrite) {
83
+ if (!interactive || parsed.json) {
84
+ fail(`plugin "${preparation.artifact.id}" already exists; ` +
85
+ `inspect digest ${preparation.artifact.digest} and re-run with ` +
86
+ `--force --expect-digest ${preparation.artifact.digest}`);
87
+ }
88
+ overwrite = await confirm(`Overwrite installed plugin "${preparation.artifact.id}" with this artifact?`);
89
+ if (!overwrite) {
90
+ console.log("Plugin installation cancelled; the existing plugin was not changed.");
91
+ return 0;
92
+ }
93
+ }
94
+ }
95
+ let grants = [...preparation.capabilityDiff.retained];
96
+ if (preparation.capabilityDiff.added.length > 0) {
97
+ if (parsed.grantAll) {
98
+ grants = [...preparation.artifact.requestedCapabilities];
99
+ }
100
+ else {
101
+ if (!interactive || parsed.json) {
102
+ fail(`plugin "${preparation.artifact.id}" requests new capabilities ` +
103
+ `(${preparation.capabilityDiff.added.join(", ")}); inspect digest ` +
104
+ `${preparation.artifact.digest} and re-run with ` +
105
+ `--grant-all --expect-digest ${preparation.artifact.digest}`);
106
+ }
107
+ const approved = await confirm(`Grant ${preparation.capabilityDiff.added.length} new capability(s) ` +
108
+ `to "${preparation.artifact.id}"?`);
109
+ if (!approved) {
110
+ console.log("Plugin installation cancelled; no changes were made.");
111
+ return 0;
112
+ }
113
+ grants = [...preparation.artifact.requestedCapabilities];
114
+ }
115
+ }
116
+ const result = await commitResidentPluginInstallation(preparation.token, {
117
+ grants,
118
+ overwrite,
119
+ expectedDigest: preparation.artifact.digest,
120
+ });
121
+ committed = true;
122
+ let runtimeReloaded = false;
123
+ if (parsed.restart) {
124
+ try {
125
+ await reloadResidentPluginRuntime(result.plugin.id);
126
+ runtimeReloaded = true;
127
+ }
128
+ catch (error) {
129
+ throw new Error(`Plugin "${result.plugin.id}" was committed, but its runtime did not reload: ` +
130
+ `${messageOf(error)}`, { cause: error });
131
+ }
132
+ }
133
+ if (parsed.json) {
134
+ console.log(JSON.stringify({
135
+ preparation: publicPreparation(preparation),
136
+ plugin: result.plugin,
137
+ runtimeReloaded,
138
+ }, null, 2));
139
+ }
140
+ else {
141
+ const action = subcommand === "install" ? "installed" : "updated";
142
+ console.log(`Plugin "${result.plugin.id}"${result.plugin.version
143
+ ? ` @${result.plugin.version}`
144
+ : ""} ${action}.`);
145
+ console.log(parsed.restart
146
+ ? `Plugin "${result.plugin.id}" runtime is ready.`
147
+ : "Use --restart to reload the plugin runtime now.");
148
+ }
149
+ return 0;
150
+ }
151
+ finally {
152
+ if (!committed) {
153
+ await cancelResidentPluginInstallation(preparation.token).catch(() => false);
154
+ }
155
+ }
156
+ }
157
+ default:
158
+ fail("plugin manage: expected list, install, update, uninstall, enable, or disable");
159
+ }
160
+ }
161
+ export async function runPluginCommand(args) {
162
+ const [pluginId, command] = args;
163
+ if (!pluginId)
164
+ fail("plugin: missing plugin id");
165
+ if (pluginId === "manage") {
166
+ return runPluginManageCommand(args.slice(1));
167
+ }
168
+ if (!command)
169
+ fail(`plugin ${pluginId}: missing command`);
170
+ const stdin = process.stdin.isTTY
171
+ ? undefined
172
+ : await readStdinBounded(PLUGIN_STDIN_MAX_BYTES);
173
+ const result = await invokeResidentPluginCommand(pluginId, args.slice(1), stdin === undefined ? {} : { stdin });
174
+ if (result.stdout)
175
+ process.stdout.write(result.stdout);
176
+ if (result.stderr)
177
+ process.stderr.write(result.stderr);
178
+ if (result.truncated) {
179
+ process.stderr.write("rynx: plugin command output was truncated by the daemon\n");
180
+ }
181
+ return result.code;
182
+ }
183
+ function parseInstallationArgs(operation, args) {
184
+ const target = args[0];
185
+ if (!target || target.startsWith("--")) {
186
+ fail(operation === "install"
187
+ ? "plugin manage install: missing npm/path/git/url source"
188
+ : "plugin manage update: missing plugin id");
189
+ }
190
+ const flags = new Set();
191
+ const allowedFlags = new Set([
192
+ "--force",
193
+ "--grant-all",
194
+ "--allow-scripts",
195
+ "--restart",
196
+ "--json",
197
+ ]);
198
+ let expectedDigest;
199
+ for (let index = 1; index < args.length; index += 1) {
200
+ const arg = args[index];
201
+ if (arg === "--expect-digest" || arg === "--expected-digest") {
202
+ if (expectedDigest !== undefined) {
203
+ fail(`plugin manage ${operation}: duplicate digest expectation`);
204
+ }
205
+ const value = args[index + 1];
206
+ if (!value || value.startsWith("--")) {
207
+ fail(`plugin manage ${operation}: ${arg} requires a value`);
208
+ }
209
+ if (!PLUGIN_DIGEST_PATTERN.test(value)) {
210
+ fail(`plugin manage ${operation}: ${arg} must be a sha256 digest`);
211
+ }
212
+ expectedDigest = value;
213
+ index += 1;
214
+ continue;
215
+ }
216
+ if (!arg.startsWith("--")) {
217
+ fail(`plugin manage ${operation}: unexpected argument ${arg}`);
218
+ }
219
+ if (!allowedFlags.has(arg)) {
220
+ fail(`plugin manage ${operation}: unknown option ${arg}`);
221
+ }
222
+ if (flags.has(arg)) {
223
+ fail(`plugin manage ${operation}: duplicate option ${arg}`);
224
+ }
225
+ flags.add(arg);
226
+ }
227
+ return {
228
+ target,
229
+ force: flags.has("--force"),
230
+ grantAll: flags.has("--grant-all"),
231
+ allowScripts: flags.has("--allow-scripts"),
232
+ restart: flags.has("--restart"),
233
+ json: flags.has("--json"),
234
+ ...(expectedDigest ? { expectedDigest } : {}),
235
+ };
236
+ }
237
+ function printPreparation(preparation) {
238
+ const version = preparation.artifact.version
239
+ ? ` @${preparation.artifact.version}`
240
+ : "";
241
+ console.log(`Prepared plugin "${preparation.artifact.id}"${version}.`);
242
+ console.log(`Artifact digest: ${preparation.artifact.digest}`);
243
+ if (preparation.artifact.requiresBuildScripts) {
244
+ console.log("Package build scripts: required and explicitly enabled.");
245
+ }
246
+ printCapabilities("New capabilities", preparation.capabilityDiff.added);
247
+ printCapabilities("Retained capabilities", preparation.capabilityDiff.retained);
248
+ printCapabilities("Removed capabilities", preparation.capabilityDiff.removed);
249
+ }
250
+ function printCapabilities(label, capabilities) {
251
+ if (capabilities.length === 0)
252
+ return;
253
+ console.log(`${label}:`);
254
+ for (const capability of capabilities)
255
+ console.log(` - ${capability}`);
256
+ }
257
+ function publicPreparation(preparation) {
258
+ const { token: _token, ...publicFields } = preparation;
259
+ return publicFields;
260
+ }
261
+ async function confirmInTerminal(message) {
262
+ const prompt = createInterface({
263
+ input: process.stdin,
264
+ output: process.stdout,
265
+ });
266
+ try {
267
+ const answer = await prompt.question(`${message} [y/N] `);
268
+ return answer.trim().toLowerCase() === "y" ||
269
+ answer.trim().toLowerCase() === "yes";
270
+ }
271
+ finally {
272
+ prompt.close();
273
+ }
274
+ }
275
+ function messageOf(error) {
276
+ return error instanceof Error ? error.message : String(error);
277
+ }
278
+ async function readStdinBounded(maxBytes) {
279
+ const chunks = [];
280
+ let bytes = 0;
281
+ for await (const chunk of process.stdin) {
282
+ const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
283
+ bytes += value.byteLength;
284
+ if (bytes > maxBytes)
285
+ fail(`plugin stdin exceeds ${maxBytes} bytes`);
286
+ chunks.push(value);
287
+ }
288
+ return Buffer.concat(chunks, bytes).toString("utf8");
289
+ }
@@ -0,0 +1 @@
1
+ export declare function runRuntimeCommand(args: readonly string[]): Promise<number>;