@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 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, reloadResidentPluginRuntime, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, type InvokeResidentPluginCommandOptions, type ResidentDaemon, type ResidentDaemonIdentity, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostFailure, type ResidentPluginCommandResult, type ResidentPluginManagementItem, type ResidentPluginManagementState, type ResidentRemoteRuntimeClientGrant, type ResidentRuntimeCallErrorCode, type ResidentRuntimeCallOutcome, type ResidentRuntimeTargetTestResult, type ResidentRuntimeTargetView, } from "./control-client.js";
1
+ export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, 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, reloadResidentPluginRuntime, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, } from "./control-client.js";
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";
@@ -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 { builtinSkillsDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
10
+ export { runSessionCommand } from "./session.js";
11
+ export { builtinSkillGuidesDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
@@ -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 { builtinSkillsDirectory, listBuiltinSkills, readBuiltinSkill, runSkillsCommand, } from "./skills.js";
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
+ }
@@ -1,8 +1,9 @@
1
1
  import { createInterface } from "node:readline/promises";
2
- import { cancelResidentPluginInstallation, commitResidentPluginInstallation, invokeResidentPluginCommand, listResidentPlugins, prepareResidentPluginInstallation, reloadResidentPluginRuntime, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
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 manage ${subcommand}: missing plugin id`);
26
+ fail(`plugin ${subcommand}: missing canonical plugin id`);
26
27
  await setResidentPluginEnabled(pluginId, subcommand === "enable");
27
- console.log(`Plugin "${pluginId}" ${subcommand}d. Run \`rynx restart\` to apply.`);
28
+ console.log(`Plugin "${pluginId}" ${subcommand}d.`);
28
29
  return 0;
29
30
  }
30
31
  case "uninstall": {
31
32
  if (!pluginId)
32
- fail("plugin manage uninstall: missing plugin id");
33
+ fail("plugin uninstall: missing canonical plugin id");
33
34
  await uninstallResidentPlugin(pluginId);
34
- console.log(`Plugin "${pluginId}" uninstalled. Run \`rynx restart\` to apply.`);
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
- ? { spec: parsed.target }
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
- (parsed.force || parsed.grantAll || parsed.allowScripts) &&
74
+ parsed.force &&
61
75
  parsed.expectedDigest === undefined) {
62
- fail("non-interactive --force/--grant-all/--allow-scripts approval must be bound to the " +
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(parsed.restart
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 manage: expected list, install, update, uninstall, enable, or disable");
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 (pluginId === "manage") {
166
- return runPluginManageCommand(args.slice(1));
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 manage install: missing npm/path/git/url source"
188
- : "plugin manage update: missing plugin id");
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 manage ${operation}: duplicate digest expectation`);
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 manage ${operation}: ${arg} requires a value`);
168
+ fail(`plugin ${operation}: ${arg} requires a value`);
208
169
  }
209
170
  if (!PLUGIN_DIGEST_PATTERN.test(value)) {
210
- fail(`plugin manage ${operation}: ${arg} must be a sha256 digest`);
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 manage ${operation}: unexpected argument ${arg}`);
178
+ fail(`plugin ${operation}: unexpected argument ${arg}`);
218
179
  }
219
180
  if (!allowedFlags.has(arg)) {
220
- fail(`plugin manage ${operation}: unknown option ${arg}`);
181
+ fail(`plugin ${operation}: unknown option ${arg}`);
221
182
  }
222
183
  if (flags.has(arg)) {
223
- fail(`plugin manage ${operation}: duplicate option ${arg}`);
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
- 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}`);
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
- directory: string;
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
- export declare function builtinSkillsDirectory(moduleUrl?: string, env?: NodeJS.ProcessEnv): string;
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;
@@ -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({ skills: skills.map(({ name: skillName }) => ({ name: skillName })) }, null, 2));
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 skill = await readBuiltinSkill(name);
27
- if (!skill)
28
- fail(`Builtin Skill "${name}" was not found`);
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
- directory: skill.directory,
33
- skillFile: skill.skillFile,
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 known = ["rynx-cli"];
48
- const skills = await Promise.all(known.map((name) => readBuiltinSkill(name)));
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
- const directory = path.join(builtinSkillsDirectory(), name);
55
- const skillFile = path.join(directory, "SKILL.md");
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(skillFile, "utf8");
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, directory, skillFile, content };
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
- export function builtinSkillsDirectory(moduleUrl = import.meta.url, env = process.env) {
70
- const configured = env.RYNX_BUILTIN_SKILLS_DIR?.trim();
71
- if (configured && path.isAbsolute(configured) && existsSync(configured)) {
72
- return configured;
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
- const packaged = fileURLToPath(new URL("../../skills/", moduleUrl));
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
  }
@@ -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 already-approved artifact. */
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>;
@@ -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 already-approved artifact. */
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 { runPluginCommand, runPluginManageCommand, } from "./commands/plugin.js";
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 manage list\n plugin manage install <source> [--force] [--grant-all] [--expect-digest <sha256-...>] [...]\n plugin manage update <id> [--grant-all] [--expect-digest <sha256-...>] [...]\n plugin manage enable|disable|uninstall <id>\n plugin <id> <command> invoke a plugin command through daemon RPC\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 <name> [--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\nBrowser:\n browser install|update|version|clean [...]\n browser open [url] --session <id> [--runtime <local|daemon-id>] [--json]\n browser status|pages|close --session <id> [--runtime <local|daemon-id>] [--json]\n browser endpoint [--ensure] --session <local-id> [--json]\n browser snapshot --session <local-id> [--json]\n browser navigate <url> --session <local-id> [--json]\n browser click (--ref <ref>|--selector <css>|--x <n> --y <n>) --session <local-id> [--json]\n browser type (--ref <ref>|--selector <css>) --text <text> --session <local-id> [--json]\n browser screenshot --output <absolute-path> --session <local-id> [--json]\n";
1
+ export declare const USAGE = "Usage: rynx <command>\n\nGeneral:\n version | --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
- plugin manage list
16
- plugin manage install <source> [--force] [--grant-all] [--expect-digest <sha256-...>] [...]
17
- plugin manage update <id> [--grant-all] [--expect-digest <sha256-...>] [...]
18
- plugin manage enable|disable|uninstall <id>
19
- plugin <id> <command> invoke a plugin command through daemon RPC
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 <name> [--json]
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.10",
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/core": "0.1.10",
42
- "@rynx-ai/emulator": "0.1.10",
43
- "@rynx-ai/protocol": "0.1.10",
44
- "@rynx-ai/browser-cdp": "0.1.10"
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.
@@ -1,99 +1,31 @@
1
1
  ---
2
2
  name: rynx-cli
3
- description: Operate Rynx-managed local and remote runtimes through the bundled `rynx` CLI, including Browser inspection and automation, Emulator control, runtime pairing, and plugin commands. Use when a task needs to inspect or act on a Rynx Session Browser or Emulator, manage a Rynx runtime target, or invoke a Rynx plugin. Prefer this bundled CLI over direct CDP calls, guessed ports, globally installed browser drivers, or ad-hoc device tools.
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
- Use the bundled `rynx` command. Rynx supplies the correct executable and current
9
- Session context; do not locate another installation with `which`, `npx`, or npm.
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
- ## Choose the target
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
- - Inside a Rynx Session, omit `--session`; the injected Session credential
14
- restricts commands to that Session.
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
- If the CLI reports that the daemon is unavailable, ask the user to open Rynx or
21
- enable its resident daemon. Never start an unrelated service or guess a port.
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 `--selector` only when no useful accessibility reference exists. Use
48
- coordinate clicks only as a final fallback after a screenshot.
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
- - On `stale reference`, take a new Browser snapshot and retry once.
96
- - On `busy`, preserve the current Turn or Terminal and let the user resolve it.
97
- - On an unavailable capability, report the missing App integration or macOS
98
- permission; do not install an unpinned replacement.
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.