@rynx-ai/cli 0.1.10 → 0.1.11-beta.2
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/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.js +3 -1
- package/dist/commands/market.d.ts +1 -0
- package/dist/commands/market.js +71 -0
- package/dist/commands/plugin.js +37 -93
- package/dist/commands/session.d.ts +1 -0
- package/dist/commands/session.js +38 -0
- package/dist/commands/skills.d.ts +6 -4
- package/dist/commands/skills.js +41 -28
- package/dist/control-client.d.ts +6 -4
- package/dist/control-client.js +31 -8
- package/dist/run-cli.js +7 -4
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +21 -14
- package/package.json +7 -6
- package/skill-guides/browser.md +72 -0
- package/skill-guides/emulator.md +79 -0
- package/skills/rynx-cli/SKILL.md +19 -87
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,
|
|
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";
|
|
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,
|
|
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";
|
|
2
2
|
export { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export { runCleanupCommand } from "./cleanup.js";
|
|
|
5
5
|
export { runEmulatorCommand } from "./emulator.js";
|
|
6
6
|
export { CliUsageError } from "./errors.js";
|
|
7
7
|
export { type PluginManageCommandOptions, runPluginCommand, runPluginManageCommand, } from "./plugin.js";
|
|
8
|
+
export { runMarketCommand } from "./market.js";
|
|
8
9
|
export { runRuntimeCommand } from "./runtime.js";
|
|
9
|
-
export {
|
|
10
|
+
export { runSessionCommand } from "./session.js";
|
|
11
|
+
export { builtinSkillGuidesDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
|
package/dist/commands/index.js
CHANGED
|
@@ -5,5 +5,7 @@ export { runCleanupCommand } from "./cleanup.js";
|
|
|
5
5
|
export { runEmulatorCommand } from "./emulator.js";
|
|
6
6
|
export { CliUsageError } from "./errors.js";
|
|
7
7
|
export { runPluginCommand, runPluginManageCommand, } from "./plugin.js";
|
|
8
|
+
export { runMarketCommand } from "./market.js";
|
|
8
9
|
export { runRuntimeCommand } from "./runtime.js";
|
|
9
|
-
export {
|
|
10
|
+
export { runSessionCommand } from "./session.js";
|
|
11
|
+
export { builtinSkillGuidesDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runMarketCommand(args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { addResidentPluginMarketplace, listResidentPluginMarketplaces, refreshResidentPluginMarketplace, removeResidentPluginMarketplace, } from "../control-client.js";
|
|
2
|
+
import { fail } from "./errors.js";
|
|
3
|
+
export async function runMarketCommand(args) {
|
|
4
|
+
const [command, target, ...rest] = args;
|
|
5
|
+
switch (command) {
|
|
6
|
+
case "list": {
|
|
7
|
+
rejectExtra(rest, "market list");
|
|
8
|
+
const markets = await listResidentPluginMarketplaces();
|
|
9
|
+
console.log("Markets");
|
|
10
|
+
if (markets.length === 0) {
|
|
11
|
+
console.log(" (none)");
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
for (const market of markets) {
|
|
15
|
+
const source = market.builtIn ? "built-in" : market.source;
|
|
16
|
+
const revision = market.builtIn ? "bundled with Rynx" : market.resolvedRevision;
|
|
17
|
+
console.log(` ${market.id.padEnd(24)} ${source.padEnd(8)} ` +
|
|
18
|
+
`${String(market.plugins.length).padStart(3)} plugins ${revision}`);
|
|
19
|
+
}
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
case "add": {
|
|
23
|
+
if (!target || target.startsWith("--"))
|
|
24
|
+
fail("market add: missing Git/local source");
|
|
25
|
+
let alias;
|
|
26
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
27
|
+
const arg = rest[index];
|
|
28
|
+
if (arg !== "--alias" || alias !== undefined)
|
|
29
|
+
fail(`market add: unexpected option ${arg}`);
|
|
30
|
+
const value = rest[index + 1];
|
|
31
|
+
if (!value || value.startsWith("--"))
|
|
32
|
+
fail("market add: --alias requires a value");
|
|
33
|
+
alias = value;
|
|
34
|
+
index += 1;
|
|
35
|
+
}
|
|
36
|
+
const market = await addResidentPluginMarketplace({
|
|
37
|
+
source: target,
|
|
38
|
+
...(alias ? { alias } : {}),
|
|
39
|
+
});
|
|
40
|
+
console.log(`Market "${market.id}" added with ${market.plugins.length} plugin(s).`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
case "refresh": {
|
|
44
|
+
rejectExtra(rest, "market refresh");
|
|
45
|
+
const ids = target
|
|
46
|
+
? [target]
|
|
47
|
+
: (await listResidentPluginMarketplaces()).map((market) => market.id);
|
|
48
|
+
for (const id of ids) {
|
|
49
|
+
const market = await refreshResidentPluginMarketplace(id);
|
|
50
|
+
console.log(`Market "${market.id}" refreshed at ${market.resolvedRevision}.`);
|
|
51
|
+
}
|
|
52
|
+
if (ids.length === 0)
|
|
53
|
+
console.log("No Markets to refresh.");
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
case "remove": {
|
|
57
|
+
if (!target)
|
|
58
|
+
fail("market remove: missing market id");
|
|
59
|
+
rejectExtra(rest, "market remove");
|
|
60
|
+
await removeResidentPluginMarketplace(target);
|
|
61
|
+
console.log(`Market "${target}" removed.`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
fail("market: expected list, add, refresh, or remove");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function rejectExtra(args, command) {
|
|
69
|
+
if (args.length > 0)
|
|
70
|
+
fail(`${command}: unexpected argument ${args[0]}`);
|
|
71
|
+
}
|
package/dist/commands/plugin.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import { cancelResidentPluginInstallation, commitResidentPluginInstallation, invokeResidentPluginCommand, listResidentPlugins, prepareResidentPluginInstallation,
|
|
2
|
+
import { cancelResidentPluginInstallation, commitResidentPluginInstallation, invokeResidentPluginCommand, listResidentPlugins, prepareResidentPluginInstallation, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
|
|
3
3
|
import { fail } from "./errors.js";
|
|
4
4
|
const PLUGIN_STDIN_MAX_BYTES = 256 * 1024;
|
|
5
5
|
const PLUGIN_DIGEST_PATTERN = /^sha256-[A-Za-z0-9+/]{43}={0,2}$/;
|
|
6
|
+
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
7
|
export async function runPluginManageCommand(args, options = {}) {
|
|
7
8
|
const [subcommand, pluginId] = args;
|
|
8
9
|
switch (subcommand) {
|
|
@@ -22,16 +23,16 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
22
23
|
case "enable":
|
|
23
24
|
case "disable": {
|
|
24
25
|
if (!pluginId)
|
|
25
|
-
fail(`plugin
|
|
26
|
+
fail(`plugin ${subcommand}: missing canonical plugin id`);
|
|
26
27
|
await setResidentPluginEnabled(pluginId, subcommand === "enable");
|
|
27
|
-
console.log(`Plugin "${pluginId}" ${subcommand}d
|
|
28
|
+
console.log(`Plugin "${pluginId}" ${subcommand}d.`);
|
|
28
29
|
return 0;
|
|
29
30
|
}
|
|
30
31
|
case "uninstall": {
|
|
31
32
|
if (!pluginId)
|
|
32
|
-
fail("plugin
|
|
33
|
+
fail("plugin uninstall: missing canonical plugin id");
|
|
33
34
|
await uninstallResidentPlugin(pluginId);
|
|
34
|
-
console.log(`Plugin "${pluginId}" uninstalled
|
|
35
|
+
console.log(`Plugin "${pluginId}" uninstalled.`);
|
|
35
36
|
return 0;
|
|
36
37
|
}
|
|
37
38
|
case "install":
|
|
@@ -40,9 +41,10 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
40
41
|
const preparation = await prepareResidentPluginInstallation({
|
|
41
42
|
operation: subcommand,
|
|
42
43
|
...(subcommand === "install"
|
|
43
|
-
?
|
|
44
|
+
? CANONICAL_PLUGIN_ID_PATTERN.test(parsed.target)
|
|
45
|
+
? { marketplacePlugin: parsed.target }
|
|
46
|
+
: { spec: parsed.target }
|
|
44
47
|
: { pluginId: parsed.target }),
|
|
45
|
-
allowScripts: parsed.allowScripts,
|
|
46
48
|
});
|
|
47
49
|
let committed = false;
|
|
48
50
|
try {
|
|
@@ -56,26 +58,24 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
56
58
|
fail(`prepared plugin digest ${preparation.artifact.digest} does not match ` +
|
|
57
59
|
`--expect-digest ${parsed.expectedDigest}`);
|
|
58
60
|
}
|
|
61
|
+
if (subcommand === "update" && preparation.unchanged) {
|
|
62
|
+
if (parsed.json) {
|
|
63
|
+
console.log(JSON.stringify({
|
|
64
|
+
preparation: publicPreparation(preparation),
|
|
65
|
+
unchanged: true,
|
|
66
|
+
}, null, 2));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`Plugin "${preparation.artifact.id}" is already up to date.`);
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
59
73
|
if ((!interactive || parsed.json) &&
|
|
60
|
-
|
|
74
|
+
parsed.force &&
|
|
61
75
|
parsed.expectedDigest === undefined) {
|
|
62
|
-
fail("non-interactive --force
|
|
76
|
+
fail("non-interactive --force confirmation must be bound to the " +
|
|
63
77
|
`prepared artifact; re-run with --expect-digest ${preparation.artifact.digest}`);
|
|
64
78
|
}
|
|
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
79
|
let overwrite = subcommand === "update";
|
|
80
80
|
if (subcommand === "install" && preparation.existing) {
|
|
81
81
|
overwrite = parsed.force;
|
|
@@ -92,49 +92,15 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
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
95
|
const result = await commitResidentPluginInstallation(preparation.token, {
|
|
117
|
-
grants,
|
|
118
96
|
overwrite,
|
|
119
97
|
expectedDigest: preparation.artifact.digest,
|
|
120
98
|
});
|
|
121
99
|
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
100
|
if (parsed.json) {
|
|
134
101
|
console.log(JSON.stringify({
|
|
135
102
|
preparation: publicPreparation(preparation),
|
|
136
103
|
plugin: result.plugin,
|
|
137
|
-
runtimeReloaded,
|
|
138
104
|
}, null, 2));
|
|
139
105
|
}
|
|
140
106
|
else {
|
|
@@ -142,9 +108,7 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
142
108
|
console.log(`Plugin "${result.plugin.id}"${result.plugin.version
|
|
143
109
|
? ` @${result.plugin.version}`
|
|
144
110
|
: ""} ${action}.`);
|
|
145
|
-
console.log(
|
|
146
|
-
? `Plugin "${result.plugin.id}" runtime is ready.`
|
|
147
|
-
: "Use --restart to reload the plugin runtime now.");
|
|
111
|
+
console.log(`Runtime state: ${result.plugin.runtimeState}.`);
|
|
148
112
|
}
|
|
149
113
|
return 0;
|
|
150
114
|
}
|
|
@@ -155,15 +119,15 @@ export async function runPluginManageCommand(args, options = {}) {
|
|
|
155
119
|
}
|
|
156
120
|
}
|
|
157
121
|
default:
|
|
158
|
-
fail("plugin
|
|
122
|
+
fail("plugin: expected list, install, update, uninstall, enable, or disable");
|
|
159
123
|
}
|
|
160
124
|
}
|
|
161
125
|
export async function runPluginCommand(args) {
|
|
162
126
|
const [pluginId, command] = args;
|
|
163
127
|
if (!pluginId)
|
|
164
128
|
fail("plugin: missing plugin id");
|
|
165
|
-
if (
|
|
166
|
-
return runPluginManageCommand(args
|
|
129
|
+
if (["list", "install", "update", "uninstall", "enable", "disable"].includes(pluginId)) {
|
|
130
|
+
return runPluginManageCommand(args);
|
|
167
131
|
}
|
|
168
132
|
if (!command)
|
|
169
133
|
fail(`plugin ${pluginId}: missing command`);
|
|
@@ -184,15 +148,12 @@ function parseInstallationArgs(operation, args) {
|
|
|
184
148
|
const target = args[0];
|
|
185
149
|
if (!target || target.startsWith("--")) {
|
|
186
150
|
fail(operation === "install"
|
|
187
|
-
? "plugin
|
|
188
|
-
: "plugin
|
|
151
|
+
? "plugin install: missing Git/local source or market plugin identity"
|
|
152
|
+
: "plugin update: missing canonical plugin id");
|
|
189
153
|
}
|
|
190
154
|
const flags = new Set();
|
|
191
155
|
const allowedFlags = new Set([
|
|
192
156
|
"--force",
|
|
193
|
-
"--grant-all",
|
|
194
|
-
"--allow-scripts",
|
|
195
|
-
"--restart",
|
|
196
157
|
"--json",
|
|
197
158
|
]);
|
|
198
159
|
let expectedDigest;
|
|
@@ -200,36 +161,33 @@ function parseInstallationArgs(operation, args) {
|
|
|
200
161
|
const arg = args[index];
|
|
201
162
|
if (arg === "--expect-digest" || arg === "--expected-digest") {
|
|
202
163
|
if (expectedDigest !== undefined) {
|
|
203
|
-
fail(`plugin
|
|
164
|
+
fail(`plugin ${operation}: duplicate digest expectation`);
|
|
204
165
|
}
|
|
205
166
|
const value = args[index + 1];
|
|
206
167
|
if (!value || value.startsWith("--")) {
|
|
207
|
-
fail(`plugin
|
|
168
|
+
fail(`plugin ${operation}: ${arg} requires a value`);
|
|
208
169
|
}
|
|
209
170
|
if (!PLUGIN_DIGEST_PATTERN.test(value)) {
|
|
210
|
-
fail(`plugin
|
|
171
|
+
fail(`plugin ${operation}: ${arg} must be a sha256 digest`);
|
|
211
172
|
}
|
|
212
173
|
expectedDigest = value;
|
|
213
174
|
index += 1;
|
|
214
175
|
continue;
|
|
215
176
|
}
|
|
216
177
|
if (!arg.startsWith("--")) {
|
|
217
|
-
fail(`plugin
|
|
178
|
+
fail(`plugin ${operation}: unexpected argument ${arg}`);
|
|
218
179
|
}
|
|
219
180
|
if (!allowedFlags.has(arg)) {
|
|
220
|
-
fail(`plugin
|
|
181
|
+
fail(`plugin ${operation}: unknown option ${arg}`);
|
|
221
182
|
}
|
|
222
183
|
if (flags.has(arg)) {
|
|
223
|
-
fail(`plugin
|
|
184
|
+
fail(`plugin ${operation}: duplicate option ${arg}`);
|
|
224
185
|
}
|
|
225
186
|
flags.add(arg);
|
|
226
187
|
}
|
|
227
188
|
return {
|
|
228
189
|
target,
|
|
229
190
|
force: flags.has("--force"),
|
|
230
|
-
grantAll: flags.has("--grant-all"),
|
|
231
|
-
allowScripts: flags.has("--allow-scripts"),
|
|
232
|
-
restart: flags.has("--restart"),
|
|
233
191
|
json: flags.has("--json"),
|
|
234
192
|
...(expectedDigest ? { expectedDigest } : {}),
|
|
235
193
|
};
|
|
@@ -240,19 +198,8 @@ function printPreparation(preparation) {
|
|
|
240
198
|
: "";
|
|
241
199
|
console.log(`Prepared plugin "${preparation.artifact.id}"${version}.`);
|
|
242
200
|
console.log(`Artifact digest: ${preparation.artifact.digest}`);
|
|
243
|
-
|
|
244
|
-
|
|
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}`);
|
|
201
|
+
console.log(`Source: ${preparation.artifact.source}`);
|
|
202
|
+
console.log(`Runtime: ${preparation.artifact.hasRuntime ? "declared" : "none"}`);
|
|
256
203
|
}
|
|
257
204
|
function publicPreparation(preparation) {
|
|
258
205
|
const { token: _token, ...publicFields } = preparation;
|
|
@@ -272,9 +219,6 @@ async function confirmInTerminal(message) {
|
|
|
272
219
|
prompt.close();
|
|
273
220
|
}
|
|
274
221
|
}
|
|
275
|
-
function messageOf(error) {
|
|
276
|
-
return error instanceof Error ? error.message : String(error);
|
|
277
|
-
}
|
|
278
222
|
async function readStdinBounded(maxBytes) {
|
|
279
223
|
const chunks = [];
|
|
280
224
|
let bytes = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSessionCommand(args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { callResidentRuntime } from "../control-client.js";
|
|
3
|
+
import { fail } from "./errors.js";
|
|
4
|
+
export async function runSessionCommand(args) {
|
|
5
|
+
const [command, sourceSessionId, ...options] = args;
|
|
6
|
+
if (command !== "fork" || !sourceSessionId) {
|
|
7
|
+
fail("Usage: rynx session fork <session-id> [--title <title>] [--json]");
|
|
8
|
+
}
|
|
9
|
+
let title;
|
|
10
|
+
let json = false;
|
|
11
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
12
|
+
const option = options[index];
|
|
13
|
+
if (option === "--json") {
|
|
14
|
+
if (json)
|
|
15
|
+
fail("session fork: duplicate option --json");
|
|
16
|
+
json = true;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (option === "--title") {
|
|
20
|
+
if (title !== undefined)
|
|
21
|
+
fail("session fork: duplicate option --title");
|
|
22
|
+
title = options[++index];
|
|
23
|
+
if (!title?.trim())
|
|
24
|
+
fail("session fork: --title requires a value");
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
fail(`session fork: unknown option ${option}`);
|
|
28
|
+
}
|
|
29
|
+
const result = await callResidentRuntime("local", "session.fork", {
|
|
30
|
+
sourceSessionId,
|
|
31
|
+
operationId: `cli_${randomUUID()}`,
|
|
32
|
+
...(title ? { title: title.trim() } : {}),
|
|
33
|
+
});
|
|
34
|
+
console.log(json
|
|
35
|
+
? JSON.stringify(result)
|
|
36
|
+
: result.sessionId);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export interface BuiltinSkill {
|
|
2
2
|
name: string;
|
|
3
|
-
|
|
4
|
-
skillFile: string;
|
|
3
|
+
description: string;
|
|
5
4
|
content: string;
|
|
5
|
+
full: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function runSkillsCommand(args: readonly string[]): Promise<number>;
|
|
8
8
|
export declare function listBuiltinSkills(): Promise<BuiltinSkill[]>;
|
|
9
|
-
export declare function readBuiltinSkill(name: string): Promise<BuiltinSkill | null>;
|
|
10
|
-
|
|
9
|
+
export declare function readBuiltinSkill(name: string, full?: boolean): Promise<BuiltinSkill | null>;
|
|
10
|
+
/** Resolve only inside the independently published @rynx-ai/cli package.
|
|
11
|
+
* The source and compiled module depths are identical (`src|dist/commands`). */
|
|
12
|
+
export declare function builtinSkillGuidesDirectory(moduleUrl?: string): string;
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { fail } from "./errors.js";
|
|
6
5
|
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
7
6
|
const MAX_SKILL_BYTES = 256 * 1024;
|
|
7
|
+
const BUILTIN_SKILL_NAMES = ["browser", "emulator"];
|
|
8
8
|
export async function runSkillsCommand(args) {
|
|
9
9
|
const [subcommand, name] = args;
|
|
10
10
|
const json = args.includes("--json");
|
|
11
11
|
if (subcommand === "list") {
|
|
12
12
|
const skills = await listBuiltinSkills();
|
|
13
13
|
if (json) {
|
|
14
|
-
console.log(JSON.stringify({
|
|
14
|
+
console.log(JSON.stringify({
|
|
15
|
+
topics: skills.map(({ name: topicName, description }) => ({
|
|
16
|
+
name: topicName,
|
|
17
|
+
description,
|
|
18
|
+
})),
|
|
19
|
+
}, null, 2));
|
|
15
20
|
}
|
|
16
21
|
else {
|
|
17
22
|
for (const skill of skills)
|
|
18
|
-
console.log(skill.name);
|
|
23
|
+
console.log(`${skill.name}: ${skill.description}`);
|
|
19
24
|
}
|
|
20
25
|
return 0;
|
|
21
26
|
}
|
|
@@ -23,15 +28,17 @@ export async function runSkillsCommand(args) {
|
|
|
23
28
|
if (!name || name.startsWith("--")) {
|
|
24
29
|
fail("skills get: missing Builtin Skill name");
|
|
25
30
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
const full = args.includes("--full");
|
|
32
|
+
const skill = await readBuiltinSkill(name, full);
|
|
33
|
+
if (!skill) {
|
|
34
|
+
const available = (await listBuiltinSkills()).map((entry) => entry.name).join(", ");
|
|
35
|
+
fail(`Unknown Skill guide "${name}". Available topics: ${available}`);
|
|
36
|
+
}
|
|
29
37
|
if (json) {
|
|
30
38
|
console.log(JSON.stringify({
|
|
31
39
|
name: skill.name,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
content: skill.content,
|
|
40
|
+
full: skill.full,
|
|
41
|
+
markdown: skill.content,
|
|
35
42
|
}, null, 2));
|
|
36
43
|
}
|
|
37
44
|
else {
|
|
@@ -44,21 +51,25 @@ export async function runSkillsCommand(args) {
|
|
|
44
51
|
fail("skills: expected list or get <name>");
|
|
45
52
|
}
|
|
46
53
|
export async function listBuiltinSkills() {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
return skills.filter((skill) => skill !== null);
|
|
54
|
+
const guides = await Promise.all(BUILTIN_SKILL_NAMES.map((name) => readBuiltinSkill(name)));
|
|
55
|
+
return guides.filter((guide) => guide !== null);
|
|
50
56
|
}
|
|
51
|
-
export async function readBuiltinSkill(name) {
|
|
57
|
+
export async function readBuiltinSkill(name, full = false) {
|
|
52
58
|
if (!SKILL_NAME_PATTERN.test(name))
|
|
53
59
|
return null;
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
if (!BUILTIN_SKILL_NAMES.includes(name))
|
|
61
|
+
return null;
|
|
62
|
+
const guideFile = path.join(builtinSkillGuidesDirectory(), `${name}.md`);
|
|
56
63
|
try {
|
|
57
|
-
const content = await readFile(
|
|
64
|
+
const content = await readFile(guideFile, "utf8");
|
|
58
65
|
if (Buffer.byteLength(content, "utf8") > MAX_SKILL_BYTES) {
|
|
59
|
-
throw new Error(`Builtin Skill "${name}" exceeds ${MAX_SKILL_BYTES} bytes`);
|
|
66
|
+
throw new Error(`Builtin Skill guide "${name}" exceeds ${MAX_SKILL_BYTES} bytes`);
|
|
67
|
+
}
|
|
68
|
+
const metadata = parseGuideFrontmatter(content, guideFile);
|
|
69
|
+
if (metadata.name !== name) {
|
|
70
|
+
throw new Error(`Builtin Skill guide "${name}" declares name "${metadata.name}"`);
|
|
60
71
|
}
|
|
61
|
-
return { name,
|
|
72
|
+
return { name, description: metadata.description, content, full };
|
|
62
73
|
}
|
|
63
74
|
catch (error) {
|
|
64
75
|
if (error.code === "ENOENT")
|
|
@@ -66,15 +77,17 @@ export async function readBuiltinSkill(name) {
|
|
|
66
77
|
throw error;
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
/** Resolve only inside the independently published @rynx-ai/cli package.
|
|
81
|
+
* The source and compiled module depths are identical (`src|dist/commands`). */
|
|
82
|
+
export function builtinSkillGuidesDirectory(moduleUrl = import.meta.url) {
|
|
83
|
+
return path.resolve(fileURLToPath(new URL("../../skill-guides/", moduleUrl)));
|
|
84
|
+
}
|
|
85
|
+
function parseGuideFrontmatter(markdown, sourcePath) {
|
|
86
|
+
const match = /^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/.exec(markdown);
|
|
87
|
+
const name = match && /^name:\s*(.+?)\s*$/m.exec(match[1])?.[1];
|
|
88
|
+
const description = match && /^description:\s*(.+?)\s*$/m.exec(match[1])?.[1];
|
|
89
|
+
if (!name || !description) {
|
|
90
|
+
throw new Error(`Builtin Skill guide must declare single-line name and description: ${sourcePath}`);
|
|
73
91
|
}
|
|
74
|
-
|
|
75
|
-
if (existsSync(packaged))
|
|
76
|
-
return packaged;
|
|
77
|
-
// Source-tree tests run before a package build has copied the canonical
|
|
78
|
-
// artifact. Published/App builds always take the packaged branch above.
|
|
79
|
-
return fileURLToPath(new URL("../../../../skills/", moduleUrl));
|
|
92
|
+
return { name, description: description.replace(/\s+/g, " ").trim() };
|
|
80
93
|
}
|
package/dist/control-client.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type DaemonBrowserArtifactCleanResult, type DaemonBrowserArtifactInstal
|
|
|
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
|
-
import { type PluginInstallCommitInput, type PluginInstallCommitResult, type PluginInstallPreparation, type PluginInstallPrepareInput, type PluginManagementItem, type PluginManagementState } from "@rynx-ai/protocol/plugin-management";
|
|
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
8
|
export interface ResidentDaemon {
|
|
9
9
|
origin: string;
|
|
@@ -142,6 +142,10 @@ export declare function getResidentRuntimeLocalBrowserAutomationAccess(options?:
|
|
|
142
142
|
export declare function invokeResidentPluginCommand(pluginId: string, args: readonly string[], options?: InvokeResidentPluginCommandOptions): Promise<ResidentPluginCommandResult>;
|
|
143
143
|
/** Read the daemon-owned plugin registry without exposing package paths/specs. */
|
|
144
144
|
export declare function listResidentPlugins(): Promise<ResidentPluginManagementItem[]>;
|
|
145
|
+
export declare function listResidentPluginMarketplaces(): Promise<PluginMarketplaceItem[]>;
|
|
146
|
+
export declare function addResidentPluginMarketplace(input: PluginMarketplaceAddInput): Promise<PluginMarketplaceItem>;
|
|
147
|
+
export declare function refreshResidentPluginMarketplace(id: string): Promise<PluginMarketplaceItem>;
|
|
148
|
+
export declare function removeResidentPluginMarketplace(id: string): Promise<boolean>;
|
|
145
149
|
/** Enable or disable one installed plugin through authenticated management RPC. */
|
|
146
150
|
export declare function setResidentPluginEnabled(pluginId: string, enabled: boolean): Promise<ResidentPluginManagementItem>;
|
|
147
151
|
/** Remove one plugin registry entry through authenticated management RPC. */
|
|
@@ -149,13 +153,11 @@ export declare function uninstallResidentPlugin(pluginId: string): Promise<{
|
|
|
149
153
|
uninstalled: true;
|
|
150
154
|
pluginId: string;
|
|
151
155
|
}>;
|
|
152
|
-
/** Reload one plugin runtime after a durable registry mutation. */
|
|
153
|
-
export declare function reloadResidentPluginRuntime(pluginId: string): Promise<Record<string, unknown>>;
|
|
154
156
|
/** Materialize and hold one exact plugin artifact in daemon-owned staging. */
|
|
155
157
|
export declare function prepareResidentPluginInstallation(input: PluginInstallPrepareInput, options?: {
|
|
156
158
|
signal?: AbortSignal;
|
|
157
159
|
}): Promise<PluginInstallPreparation>;
|
|
158
|
-
/** Consume one preparation token and promote only its
|
|
160
|
+
/** Consume one preparation token and promote only its inspected artifact. */
|
|
159
161
|
export declare function commitResidentPluginInstallation(token: string, input: PluginInstallCommitInput, options?: {
|
|
160
162
|
signal?: AbortSignal;
|
|
161
163
|
}): Promise<PluginInstallCommitResult>;
|
package/dist/control-client.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseRemoteRuntimeRpcRequest, parseRemoteRuntimeRpcResponseForMethod, R
|
|
|
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";
|
|
8
8
|
import { parseRuntimeBrowserStateGetParams } from "@rynx-ai/protocol/runtime-browser";
|
|
9
|
-
import { PLUGIN_INSTALL_PREPARATIONS_PATH, parsePluginInstallCommitInput, parsePluginInstallCommitResult, parsePluginInstallPreparation, parsePluginInstallPrepareInput, parsePluginManagementItem, pluginInstallCommitPath, pluginInstallPreparationPath, } from "@rynx-ai/protocol/plugin-management";
|
|
9
|
+
import { PLUGIN_INSTALL_PREPARATIONS_PATH, PLUGIN_MARKETPLACES_PATH, parsePluginInstallCommitInput, parsePluginInstallCommitResult, parsePluginInstallPreparation, parsePluginInstallPrepareInput, parsePluginManagementItem, parsePluginMarketplaceAddInput, parsePluginMarketplaceItem, pluginInstallCommitPath, pluginInstallPreparationPath, } from "@rynx-ai/protocol/plugin-management";
|
|
10
10
|
import { ensureDaemonControlEndpoint } from "./control-endpoint.js";
|
|
11
11
|
export { connectResidentDesktopBrowserHost, } from "./desktop-browser-host-client.js";
|
|
12
12
|
const IDENTITY_TIMEOUT_MS = 5_000;
|
|
@@ -42,7 +42,7 @@ const MAINTENANCE_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
|
42
42
|
const CHROME_INSPECTION_TIMEOUT_MS = 30_000;
|
|
43
43
|
const CHROME_INSPECTION_RESPONSE_MAX_BYTES = 256 * 1024;
|
|
44
44
|
const INSTALLATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
45
|
-
const PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}
|
|
45
|
+
const 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})?$/;
|
|
46
46
|
/** Stable error returned by the resident daemon's typed Runtime gateway. */
|
|
47
47
|
export class ResidentRuntimeCallError extends Error {
|
|
48
48
|
code;
|
|
@@ -671,6 +671,29 @@ export async function listResidentPlugins() {
|
|
|
671
671
|
}
|
|
672
672
|
return parsed.plugins.map(parsePluginManagementItem);
|
|
673
673
|
}
|
|
674
|
+
export async function listResidentPluginMarketplaces() {
|
|
675
|
+
const parsed = await pluginManagementRequest(PLUGIN_MARKETPLACES_PATH, { method: "GET" });
|
|
676
|
+
if (!Array.isArray(parsed.marketplaces)) {
|
|
677
|
+
throw new Error("daemon returned an invalid plugin marketplace list");
|
|
678
|
+
}
|
|
679
|
+
return parsed.marketplaces.map(parsePluginMarketplaceItem);
|
|
680
|
+
}
|
|
681
|
+
export async function addResidentPluginMarketplace(input) {
|
|
682
|
+
const validated = parsePluginMarketplaceAddInput(input);
|
|
683
|
+
return parsePluginMarketplaceItem(await pluginInstallRequest(PLUGIN_MARKETPLACES_PATH, { method: "POST", body: JSON.stringify(validated) }));
|
|
684
|
+
}
|
|
685
|
+
export async function refreshResidentPluginMarketplace(id) {
|
|
686
|
+
validateMarketplaceId(id);
|
|
687
|
+
return parsePluginMarketplaceItem(await pluginManagementRequest(`${PLUGIN_MARKETPLACES_PATH}/${encodeURIComponent(id)}/refresh`, { method: "POST" }));
|
|
688
|
+
}
|
|
689
|
+
export async function removeResidentPluginMarketplace(id) {
|
|
690
|
+
validateMarketplaceId(id);
|
|
691
|
+
const parsed = await pluginManagementRequest(`${PLUGIN_MARKETPLACES_PATH}/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
692
|
+
if (parsed.removed !== true) {
|
|
693
|
+
throw new Error("daemon returned an invalid plugin marketplace removal response");
|
|
694
|
+
}
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
674
697
|
/** Enable or disable one installed plugin through authenticated management RPC. */
|
|
675
698
|
export async function setResidentPluginEnabled(pluginId, enabled) {
|
|
676
699
|
validatePluginId(pluginId);
|
|
@@ -686,11 +709,6 @@ export async function uninstallResidentPlugin(pluginId) {
|
|
|
686
709
|
}
|
|
687
710
|
return { uninstalled: true, pluginId };
|
|
688
711
|
}
|
|
689
|
-
/** Reload one plugin runtime after a durable registry mutation. */
|
|
690
|
-
export async function reloadResidentPluginRuntime(pluginId) {
|
|
691
|
-
validatePluginId(pluginId);
|
|
692
|
-
return pluginManagementRequest(`/api/internal/plugin-runtimes/${encodeURIComponent(pluginId)}/reload`, { method: "POST" });
|
|
693
|
-
}
|
|
694
712
|
/** Materialize and hold one exact plugin artifact in daemon-owned staging. */
|
|
695
713
|
export async function prepareResidentPluginInstallation(input, options = {}) {
|
|
696
714
|
const validated = parsePluginInstallPrepareInput(input);
|
|
@@ -701,7 +719,7 @@ export async function prepareResidentPluginInstallation(input, options = {}) {
|
|
|
701
719
|
});
|
|
702
720
|
return parsePluginInstallPreparation(response);
|
|
703
721
|
}
|
|
704
|
-
/** Consume one preparation token and promote only its
|
|
722
|
+
/** Consume one preparation token and promote only its inspected artifact. */
|
|
705
723
|
export async function commitResidentPluginInstallation(token, input, options = {}) {
|
|
706
724
|
const validated = parsePluginInstallCommitInput(input);
|
|
707
725
|
const response = await pluginInstallRequest(pluginInstallCommitPath(token), {
|
|
@@ -762,6 +780,11 @@ function validatePluginId(pluginId) {
|
|
|
762
780
|
if (!PLUGIN_ID_PATTERN.test(pluginId))
|
|
763
781
|
throw new Error("invalid plugin id");
|
|
764
782
|
}
|
|
783
|
+
function validateMarketplaceId(id) {
|
|
784
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}(?:\/[a-z0-9][a-z0-9._-]{0,99})?$/.test(id)) {
|
|
785
|
+
throw new Error("invalid plugin marketplace id");
|
|
786
|
+
}
|
|
787
|
+
}
|
|
765
788
|
async function localManagementJsonRequest(pathname, input) {
|
|
766
789
|
const endpoint = await ensureDaemonControlEndpoint();
|
|
767
790
|
assertLoopbackOrigin(endpoint.origin);
|
package/dist/run-cli.js
CHANGED
|
@@ -4,8 +4,10 @@ import { runBrowserCommand } from "./commands/browser.js";
|
|
|
4
4
|
import { runCleanupCommand } from "./commands/cleanup.js";
|
|
5
5
|
import { runEmulatorCommand } from "./commands/emulator.js";
|
|
6
6
|
import { fail } from "./commands/errors.js";
|
|
7
|
-
import {
|
|
7
|
+
import { runMarketCommand } from "./commands/market.js";
|
|
8
|
+
import { runPluginCommand, } from "./commands/plugin.js";
|
|
8
9
|
import { runRuntimeCommand } from "./commands/runtime.js";
|
|
10
|
+
import { runSessionCommand } from "./commands/session.js";
|
|
9
11
|
import { runSkillsCommand } from "./commands/skills.js";
|
|
10
12
|
import { runLegacyDaemonCli } from "./legacy-adapter.js";
|
|
11
13
|
import { USAGE } from "./usage.js";
|
|
@@ -34,16 +36,17 @@ export async function runCli(argv) {
|
|
|
34
36
|
console.log(installedVersion());
|
|
35
37
|
return 0;
|
|
36
38
|
case "plugin":
|
|
37
|
-
if (argv[1] === "manage") {
|
|
38
|
-
return runPluginManageCommand(argv.slice(2));
|
|
39
|
-
}
|
|
40
39
|
return runPluginCommand(argv.slice(1));
|
|
40
|
+
case "market":
|
|
41
|
+
return runMarketCommand(argv.slice(1));
|
|
41
42
|
case "agent":
|
|
42
43
|
return runAgentCommand(argv.slice(1));
|
|
43
44
|
case "emulator":
|
|
44
45
|
return runEmulatorCommand(argv.slice(1));
|
|
45
46
|
case "runtime":
|
|
46
47
|
return runRuntimeCommand(argv.slice(1));
|
|
48
|
+
case "session":
|
|
49
|
+
return runSessionCommand(argv.slice(1));
|
|
47
50
|
case "skills":
|
|
48
51
|
return runSkillsCommand(argv.slice(1));
|
|
49
52
|
case "browser":
|
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 plugin
|
|
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";
|
package/dist/usage.js
CHANGED
|
@@ -12,11 +12,15 @@ Lifecycle:
|
|
|
12
12
|
update [version] [--check] [--json]
|
|
13
13
|
|
|
14
14
|
Plugins:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
plugin
|
|
15
|
+
market list
|
|
16
|
+
market add <git-or-local-source> [--alias <id>]
|
|
17
|
+
market refresh [market-id]
|
|
18
|
+
market remove <market-id>
|
|
19
|
+
plugin list
|
|
20
|
+
plugin install <source|plugin@market> [--force] [--expect-digest <sha256-...>]
|
|
21
|
+
plugin update <plugin@market> [--expect-digest <sha256-...>]
|
|
22
|
+
plugin enable|disable|uninstall <plugin@market>
|
|
23
|
+
plugin <plugin@market> <command> invoke a plugin-owned command
|
|
20
24
|
|
|
21
25
|
Agents:
|
|
22
26
|
agent list
|
|
@@ -26,7 +30,7 @@ Agents:
|
|
|
26
30
|
|
|
27
31
|
Builtin Skills:
|
|
28
32
|
skills list [--json]
|
|
29
|
-
skills get <
|
|
33
|
+
skills get <browser|emulator> [--full] [--json]
|
|
30
34
|
|
|
31
35
|
Maintenance:
|
|
32
36
|
cleanup sessions [--dry-run]
|
|
@@ -43,14 +47,17 @@ Remote Runtime:
|
|
|
43
47
|
runtime clients list [--json]
|
|
44
48
|
runtime clients revoke <grant-id>
|
|
45
49
|
|
|
50
|
+
Sessions:
|
|
51
|
+
session fork <session-id> [--title <title>] [--json]
|
|
52
|
+
|
|
46
53
|
Browser:
|
|
47
54
|
browser install|update|version|clean [...]
|
|
48
|
-
browser open [url] --session <id> [--runtime <local|daemon-id>] [--json]
|
|
49
|
-
browser status|pages|close --session <id> [--runtime <local|daemon-id>] [--json]
|
|
50
|
-
browser endpoint [--ensure] --session <local-id> [--json]
|
|
51
|
-
browser snapshot --session <local-id> [--json]
|
|
52
|
-
browser navigate <url> --session <local-id> [--json]
|
|
53
|
-
browser click (--ref <ref>|--selector <css>|--x <n> --y <n>) --session <local-id> [--json]
|
|
54
|
-
browser type (--ref <ref>|--selector <css>) --text <text> --session <local-id> [--json]
|
|
55
|
-
browser screenshot --output <absolute-path> --session <local-id> [--json]
|
|
55
|
+
browser open [url] [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
56
|
+
browser status|pages|close [--session <id>] [--runtime <local|daemon-id>] [--json]
|
|
57
|
+
browser endpoint [--ensure] [--session <local-id>] [--json]
|
|
58
|
+
browser snapshot [--session <local-id>] [--json]
|
|
59
|
+
browser navigate <url> [--session <local-id>] [--json]
|
|
60
|
+
browser click (--ref <ref>|--selector <css>|--x <n> --y <n>) [--session <local-id>] [--json]
|
|
61
|
+
browser type (--ref <ref>|--selector <css>) --text <text> [--session <local-id>] [--json]
|
|
62
|
+
browser screenshot --output <absolute-path> [--session <local-id>] [--json]
|
|
56
63
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11-beta.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
-
"skills"
|
|
18
|
+
"skills",
|
|
19
|
+
"skill-guides"
|
|
19
20
|
],
|
|
20
21
|
"bin": {
|
|
21
22
|
"rynx": "./dist/cli.js"
|
|
@@ -38,10 +39,10 @@
|
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"ws": "^8.21.0",
|
|
41
|
-
"@rynx-ai/
|
|
42
|
-
"@rynx-ai/
|
|
43
|
-
"@rynx-ai/
|
|
44
|
-
"@rynx-ai/
|
|
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"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@types/ws": "^8.18.1"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser
|
|
3
|
+
description: Use the bundled Rynx CLI to inspect and automate the Browser owned by the active Rynx Session with snapshots, accessibility refs, navigation, clicks, typing, screenshots, page inspection, and safe failure handling.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rynx Browser
|
|
7
|
+
|
|
8
|
+
Use the bundled `rynx` executable supplied by the active Rynx Session. Choose
|
|
9
|
+
that executable once and reuse it. Do not locate another installation with
|
|
10
|
+
`which`, `npx`, npm, filesystem searches, App-name guesses, or alternate paths.
|
|
11
|
+
|
|
12
|
+
The active Session already owns the resident daemon, Browser host, and Session
|
|
13
|
+
binding. Omit `--session`; the managed Session credential scopes every command
|
|
14
|
+
to the current Session. Prefer `--json`.
|
|
15
|
+
|
|
16
|
+
## Sandbox boundary
|
|
17
|
+
|
|
18
|
+
If a `rynx browser ...` command cannot connect to its local endpoint, do not
|
|
19
|
+
interpret that as a stopped daemon. Stop and ask the user to approve rerunning
|
|
20
|
+
the exact same command outside the Agent sandbox.
|
|
21
|
+
|
|
22
|
+
Do not change the command or try another executable, endpoint, port, transport,
|
|
23
|
+
installation, App, browser, or service. Do not scan `/Applications`, use `find`
|
|
24
|
+
or `mdfind`, run `open`, `open -a`, or `osascript`, inspect Preview/dev builds,
|
|
25
|
+
or launch/relaunch a Rynx App or daemon.
|
|
26
|
+
|
|
27
|
+
## Core loop
|
|
28
|
+
|
|
29
|
+
Open or navigate the Session Browser when needed:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
rynx browser open https://example.com --json
|
|
33
|
+
rynx browser navigate https://example.com/next --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Inspect before acting:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
rynx browser snapshot --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use a returned accessibility `ref` for the next action:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
rynx browser click --ref <ref> --json
|
|
46
|
+
rynx browser type --ref <ref> --text "hello" --json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Take a fresh snapshot after navigation or substantial DOM replacement because
|
|
50
|
+
old refs become stale. Use `--selector` only when no useful accessibility ref
|
|
51
|
+
exists. Use coordinate clicks only as a final fallback after a screenshot.
|
|
52
|
+
|
|
53
|
+
Inspect pages and save visual evidence with:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
rynx browser status --json
|
|
57
|
+
rynx browser pages --json
|
|
58
|
+
rynx browser screenshot --output /absolute/path/page.png --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Do not request or connect to the raw CDP endpoint unless diagnosing Rynx itself.
|
|
62
|
+
Do not close a Browser generation the user still needs.
|
|
63
|
+
|
|
64
|
+
## Failure handling
|
|
65
|
+
|
|
66
|
+
- On endpoint access failure, follow **Sandbox boundary** and make no fallback
|
|
67
|
+
attempt.
|
|
68
|
+
- On `stale reference`, take a new snapshot and retry once.
|
|
69
|
+
- On `busy`, preserve the current Turn or Terminal for the user to resolve.
|
|
70
|
+
- On an unavailable capability, report the missing App integration or macOS
|
|
71
|
+
permission; do not install an unpinned replacement.
|
|
72
|
+
- Preserve structured error codes and relevant stderr when reporting failure.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: emulator
|
|
3
|
+
description: Use the bundled Rynx CLI to inspect and control the iOS Simulator, Android emulator, or adb-visible Android device bound to the active Rynx Session, including device discovery, attach, taps, typing, buttons, screenshots, rotation, launch, and gestures.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rynx Emulator
|
|
7
|
+
|
|
8
|
+
Use the bundled `rynx` executable supplied by the active Rynx Session and run
|
|
9
|
+
Emulator operations through `rynx emulator ...`. Choose that executable once
|
|
10
|
+
and reuse it. Do not locate another CLI or call `serve-sim`, `simctl`, or `adb`
|
|
11
|
+
directly.
|
|
12
|
+
|
|
13
|
+
The active Session already owns the resident daemon and Session binding. Use the
|
|
14
|
+
Session's bound device by default. Add `--runtime <local|daemon-id>` only when
|
|
15
|
+
the user explicitly selected another paired Runtime. Prefer `--json`.
|
|
16
|
+
|
|
17
|
+
## Sandbox boundary
|
|
18
|
+
|
|
19
|
+
If a `rynx emulator ...` command cannot connect to its local endpoint, do not
|
|
20
|
+
interpret that as a stopped daemon. Stop and ask the user to approve rerunning
|
|
21
|
+
the exact same command outside the Agent sandbox.
|
|
22
|
+
|
|
23
|
+
Do not change the command or try another executable, endpoint, port, transport,
|
|
24
|
+
installation, App, or service. Do not scan `/Applications`, use `find` or
|
|
25
|
+
`mdfind`, run `open`, `open -a`, or `osascript`, inspect Preview/dev builds, or
|
|
26
|
+
launch/relaunch a Rynx App or daemon.
|
|
27
|
+
|
|
28
|
+
## Inspect and select
|
|
29
|
+
|
|
30
|
+
Inspect current state before acting:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
rynx emulator devices --json
|
|
34
|
+
rynx emulator active --json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Attach only when no suitable device is already bound or the user asks to switch:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
rynx emulator attach <device-id-or-exact-name> --json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Never reuse a device id, serial, or name remembered from another Session. After
|
|
44
|
+
switching or reconnecting, read `active --json` again.
|
|
45
|
+
|
|
46
|
+
## Control loop
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
rynx emulator tap <x> <y> --json
|
|
50
|
+
rynx emulator type "hello" --json
|
|
51
|
+
rynx emulator type --mode keys "hello" --json
|
|
52
|
+
rynx emulator button home --json
|
|
53
|
+
rynx emulator button backspace --json
|
|
54
|
+
rynx emulator button appswitch --json
|
|
55
|
+
rynx emulator screenshot --json
|
|
56
|
+
rynx emulator rotate portrait --json
|
|
57
|
+
rynx emulator launch <bundle-id-or-package> --json
|
|
58
|
+
rynx emulator gesture '<json-points>' --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Coordinates are normalized `0..1` with top-left origin. Re-inspect with a
|
|
62
|
+
screenshot after every meaningful UI-changing action.
|
|
63
|
+
|
|
64
|
+
For `launch`, use an exact iOS `CFBundleIdentifier` or Android package name.
|
|
65
|
+
Never pass a display name or guess an identifier. If only a visible app name is
|
|
66
|
+
known, navigate visually and confirm with screenshots.
|
|
67
|
+
|
|
68
|
+
Do not run `exec`, `kill`, or `shutdown` as routine cleanup. Raw host commands
|
|
69
|
+
and device/process lifecycle changes require an explicit user request.
|
|
70
|
+
|
|
71
|
+
## Failure handling
|
|
72
|
+
|
|
73
|
+
- On endpoint access failure, follow **Sandbox boundary** and make no fallback
|
|
74
|
+
attempt.
|
|
75
|
+
- On a missing/stale device, refresh `devices --json` and `active --json`.
|
|
76
|
+
- On `busy`, preserve the current Turn or Terminal for the user to resolve.
|
|
77
|
+
- On an unavailable capability, report the missing integration or macOS
|
|
78
|
+
permission; do not install an unpinned replacement.
|
|
79
|
+
- Preserve structured error codes and relevant stderr when reporting failure.
|
package/skills/rynx-cli/SKILL.md
CHANGED
|
@@ -1,99 +1,31 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rynx-cli
|
|
3
|
-
description:
|
|
3
|
+
description: Use the bundled `rynx` CLI when a task needs to inspect or automate the Browser or Emulator owned by the active Rynx Session. Load the version-matched Browser or Emulator guide from the CLI before operating it. Prefer this over direct CDP, browser drivers, `serve-sim`, `simctl`, adb, guessed ports, or guessed App installations.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Rynx CLI
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
Session
|
|
8
|
+
This file is a discovery stub, not the usage guide. Rynx injects it into an
|
|
9
|
+
active Session only so the Agent can discover the capability without carrying a
|
|
10
|
+
stale copy of the CLI command surface.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Use the bundled `rynx` executable supplied by the Session. Do not locate or try
|
|
13
|
+
another installation with `which`, `npx`, npm, filesystem searches, App-name
|
|
14
|
+
guesses, or alternate executable paths.
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- Outside a managed Session, pass `--session <id>`.
|
|
16
|
-
- Add `--runtime <local|daemon-id>` only when the user explicitly selected a
|
|
17
|
-
different paired Runtime.
|
|
18
|
-
- Prefer `--json` for inspection and automation output.
|
|
16
|
+
Before running an operational command, load only the matching version-matched
|
|
17
|
+
guide from that exact CLI:
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Browser workflow
|
|
24
|
-
|
|
25
|
-
Open the Session Browser when needed:
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
rynx browser open https://example.com --json
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Inspect before acting:
|
|
32
|
-
|
|
33
|
-
```sh
|
|
34
|
-
rynx browser snapshot --json
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Use a returned `ref` for the next action. Take a fresh snapshot after navigation
|
|
38
|
-
or substantial DOM replacement because old references become stale.
|
|
39
|
-
|
|
40
|
-
```sh
|
|
41
|
-
rynx browser click --ref <ref> --json
|
|
42
|
-
rynx browser type --ref <ref> --text "hello" --json
|
|
43
|
-
rynx browser navigate https://example.com/next --json
|
|
44
|
-
rynx browser screenshot --output /absolute/path/page.png --json
|
|
19
|
+
```text
|
|
20
|
+
rynx skills get browser
|
|
21
|
+
rynx skills get emulator
|
|
45
22
|
```
|
|
46
23
|
|
|
47
|
-
Use
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Useful inspection commands:
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
rynx browser status --json
|
|
54
|
-
rynx browser pages --json
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Do not request or connect to the raw CDP endpoint unless diagnosing Rynx itself.
|
|
58
|
-
Do not close a Browser generation that the user still needs.
|
|
59
|
-
|
|
60
|
-
## Emulator workflow
|
|
61
|
-
|
|
62
|
-
Inspect available commands and devices before acting:
|
|
63
|
-
|
|
64
|
-
```sh
|
|
65
|
-
rynx emulator --help
|
|
66
|
-
rynx emulator devices
|
|
67
|
-
rynx emulator active
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Use the Session's bound device by default. Select another device or Runtime only
|
|
71
|
-
when the user asks. Re-inspect the screen after every navigation or state-changing
|
|
72
|
-
input, and never call `kill` or `shutdown` as cleanup unless requested.
|
|
73
|
-
|
|
74
|
-
## Runtime and plugin operations
|
|
75
|
-
|
|
76
|
-
List and test Runtime targets before selecting a remote target:
|
|
77
|
-
|
|
78
|
-
```sh
|
|
79
|
-
rynx runtime list --json
|
|
80
|
-
rynx runtime test <local|daemon-id> --json
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Invoke plugin-declared commands through:
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
rynx plugin <plugin-id> <command> --json
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Treat pairing, client revocation, plugin installation, Browser engine updates,
|
|
90
|
-
and daemon lifecycle changes as administrative operations. Perform them only
|
|
91
|
-
when the user explicitly requests them.
|
|
92
|
-
|
|
93
|
-
## Failure handling
|
|
24
|
+
Use `browser` for Browser inspection/automation and `emulator` for mobile device
|
|
25
|
+
inspection/control. Do not load an unrelated guide. These commands read guides
|
|
26
|
+
shipped in the exact CLI npm package and do not contact the daemon or network.
|
|
94
27
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
- Preserve structured error codes and relevant stderr when reporting failure.
|
|
28
|
+
If `rynx skills get <topic>` itself cannot execute, report its exact error and
|
|
29
|
+
stop. Do not scan `/Applications`, use `find` or `mdfind`, run `open`, `open -a`,
|
|
30
|
+
or `osascript`, inspect Preview/dev builds, start a daemon, guess a port, or try
|
|
31
|
+
a different Rynx App or CLI.
|