@rynx-ai/cli 0.1.11-beta.52 → 0.1.11-beta.53

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.
@@ -1,69 +1,81 @@
1
- import { addResidentPluginMarketplace, listResidentPluginMarketplaces, refreshResidentPluginMarketplace, removeResidentPluginMarketplace, } from "../control-client.js";
1
+ import { withPluginManagement } from "../plugin-management.js";
2
2
  import { fail } from "./errors.js";
3
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)");
4
+ return withPluginManagement(async ({ addResidentPluginMarketplace, listResidentPluginMarketplaces, refreshResidentPluginMarketplace, removeResidentPluginMarketplace, }) => {
5
+ const json = args.includes("--json");
6
+ const [command, target, ...rest] = args.filter((arg) => arg !== "--json");
7
+ switch (command) {
8
+ case "list": {
9
+ rejectExtra(rest, "market list");
10
+ const markets = await listResidentPluginMarketplaces();
11
+ if (json) {
12
+ console.log(JSON.stringify({ markets }));
13
+ return 0;
14
+ }
15
+ console.log("Markets");
16
+ if (markets.length === 0) {
17
+ console.log(" (none)");
18
+ return 0;
19
+ }
20
+ for (const market of markets) {
21
+ const source = market.builtIn ? "built-in" : market.source;
22
+ const revision = market.builtIn ? "bundled with Rynx" : market.resolvedRevision;
23
+ console.log(` ${market.id.padEnd(24)} ${source.padEnd(8)} ` +
24
+ `${String(market.plugins.length).padStart(3)} plugins ${revision}`);
25
+ }
12
26
  return 0;
13
27
  }
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}`);
28
+ case "add": {
29
+ if (!target || target.startsWith("--"))
30
+ fail("market add: missing Git/local source");
31
+ let alias;
32
+ for (let index = 0; index < rest.length; index += 1) {
33
+ const arg = rest[index];
34
+ if (arg !== "--alias" || alias !== undefined)
35
+ fail(`market add: unexpected option ${arg}`);
36
+ const value = rest[index + 1];
37
+ if (!value || value.startsWith("--"))
38
+ fail("market add: --alias requires a value");
39
+ alias = value;
40
+ index += 1;
41
+ }
42
+ const market = await addResidentPluginMarketplace({
43
+ source: target,
44
+ ...(alias ? { alias } : {}),
45
+ });
46
+ console.log(json ? JSON.stringify({ market }) : `Market "${market.id}" added with ${market.plugins.length} plugin(s).`);
47
+ return 0;
19
48
  }
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;
49
+ case "refresh": {
50
+ rejectExtra(rest, "market refresh");
51
+ const ids = target
52
+ ? [target]
53
+ : (await listResidentPluginMarketplaces()).map((market) => market.id);
54
+ const markets = [];
55
+ for (const id of ids) {
56
+ const market = await refreshResidentPluginMarketplace(id);
57
+ markets.push(market);
58
+ if (!json)
59
+ console.log(`Market "${market.id}" refreshed at ${market.resolvedRevision}.`);
60
+ }
61
+ if (json)
62
+ console.log(JSON.stringify({ markets }));
63
+ else if (ids.length === 0)
64
+ console.log("No Markets to refresh.");
65
+ return 0;
35
66
  }
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}.`);
67
+ case "remove": {
68
+ if (!target)
69
+ fail("market remove: missing market id");
70
+ rejectExtra(rest, "market remove");
71
+ await removeResidentPluginMarketplace(target);
72
+ console.log(json ? JSON.stringify({ removed: target }) : `Market "${target}" removed.`);
73
+ return 0;
51
74
  }
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;
75
+ default:
76
+ fail("market: expected list, add, refresh, or remove");
63
77
  }
64
- default:
65
- fail("market: expected list, add, refresh, or remove");
66
- }
78
+ });
67
79
  }
68
80
  function rejectExtra(args, command) {
69
81
  if (args.length > 0)
@@ -1,12 +1,14 @@
1
1
  import { createInterface } from "node:readline/promises";
2
- import { cancelResidentPluginInstallation, commitResidentPluginInstallation, prepareResidentPluginInstallation, setResidentPluginEnabled, uninstallResidentPlugin, } from "../control-client.js";
2
+ import { withPluginManagement } from "../plugin-management.js";
3
3
  import { fail } from "./errors.js";
4
4
  const PLUGIN_DIGEST_PATTERN = /^sha256-[A-Za-z0-9+/]{43}={0,2}$/;
5
5
  const CANONICAL_PLUGIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,62}@[a-z0-9][a-z0-9-]{0,62}(?:\/[a-z0-9][a-z0-9._-]{0,99})?$/;
6
6
  const PLUGIN_USAGE = `Usage: rynx plugin <command|plugin-id>
7
7
 
8
8
  Management:
9
- list
9
+ list [--json]
10
+ capabilities [--json]
11
+ restore <recovery-directory>
10
12
  install <source|plugin@market> [--force] [--expect-digest <sha256-...>]
11
13
  update <plugin@market> [--expect-digest <sha256-...>]
12
14
  enable|disable|uninstall <plugin@market>
@@ -15,123 +17,132 @@ Plugin commands:
15
17
  <plugin-id> <command> [args...]
16
18
  <plugin-id> --help`;
17
19
  export async function runPluginManageCommand(args, options = {}) {
18
- const [subcommand, pluginId] = args;
19
- switch (subcommand) {
20
- case "list": {
21
- const { listInstalledPlugins } = await import("@rynx-ai/daemon/plugin-cli");
22
- const plugins = listInstalledPlugins();
23
- console.log("Plugins");
24
- if (plugins.length === 0) {
25
- console.log(" (none)");
20
+ if (args[0] === "capabilities") {
21
+ console.log(JSON.stringify({ offlineManagement: 1, asynchronousActivation: 1, installedDigest: 1, recoveryFormat: 1 }));
22
+ return 0;
23
+ }
24
+ if (args[0] === "restore") {
25
+ if (args.length !== 2 || !args[1])
26
+ fail("plugin restore: expected a recovery snapshot directory");
27
+ const { restorePluginRecovery } = await import("@rynx-ai/daemon/plugin-maintenance");
28
+ console.log(JSON.stringify(await restorePluginRecovery(args[1])));
29
+ return 0;
30
+ }
31
+ return withPluginManagement(async ({ listResidentPlugins, setResidentPluginEnabled, uninstallResidentPlugin, prepareResidentPluginInstallation, commitResidentPluginInstallation, cancelResidentPluginInstallation, }) => {
32
+ const [subcommand, pluginId] = args;
33
+ switch (subcommand) {
34
+ case "list": {
35
+ const plugins = await listResidentPlugins();
36
+ if (args.includes("--json")) {
37
+ console.log(JSON.stringify({ plugins }));
38
+ return 0;
39
+ }
40
+ console.log("Plugins");
41
+ if (plugins.length === 0) {
42
+ console.log(" (none)");
43
+ return 0;
44
+ }
45
+ for (const plugin of plugins) {
46
+ console.log(` ${plugin.id.padEnd(16)} ${(plugin.version ?? "-").padEnd(10)} ` +
47
+ `${plugin.source.padEnd(6)} ${plugin.state}`);
48
+ }
26
49
  return 0;
27
50
  }
28
- for (const plugin of plugins) {
29
- console.log(` ${plugin.id.padEnd(16)} ${(plugin.version ?? "-").padEnd(10)} ` +
30
- `${plugin.source.padEnd(6)} ${plugin.state}`);
51
+ case "enable":
52
+ case "disable": {
53
+ if (!pluginId)
54
+ fail(`plugin ${subcommand}: missing canonical plugin id`);
55
+ await setResidentPluginEnabled(pluginId, subcommand === "enable");
56
+ console.log(`Plugin "${pluginId}" ${subcommand}d.`);
57
+ return 0;
31
58
  }
32
- return 0;
33
- }
34
- case "enable":
35
- case "disable": {
36
- if (!pluginId)
37
- fail(`plugin ${subcommand}: missing canonical plugin id`);
38
- await setResidentPluginEnabled(pluginId, subcommand === "enable");
39
- console.log(`Plugin "${pluginId}" ${subcommand}d.`);
40
- return 0;
41
- }
42
- case "uninstall": {
43
- if (!pluginId)
44
- fail("plugin uninstall: missing canonical plugin id");
45
- await uninstallResidentPlugin(pluginId);
46
- console.log(`Plugin "${pluginId}" uninstalled.`);
47
- return 0;
48
- }
49
- case "install":
50
- case "update": {
51
- const parsed = parseInstallationArgs(subcommand, args.slice(1));
52
- const preparation = await prepareResidentPluginInstallation({
53
- operation: subcommand,
54
- ...(subcommand === "install"
55
- ? CANONICAL_PLUGIN_ID_PATTERN.test(parsed.target)
56
- ? { marketplacePlugin: parsed.target }
57
- : { spec: parsed.target }
58
- : { pluginId: parsed.target }),
59
- });
60
- let committed = false;
61
- try {
62
- if (!parsed.json)
63
- printPreparation(preparation);
64
- const interactive = options.interactive ??
65
- Boolean(process.stdin.isTTY && process.stdout.isTTY);
66
- const confirm = options.confirm ?? confirmInTerminal;
67
- if (parsed.expectedDigest !== undefined &&
68
- parsed.expectedDigest !== preparation.artifact.digest) {
69
- fail(`prepared plugin digest ${preparation.artifact.digest} does not match ` +
70
- `--expect-digest ${parsed.expectedDigest}`);
71
- }
72
- if (subcommand === "update" && preparation.unchanged) {
59
+ case "uninstall": {
60
+ if (!pluginId)
61
+ fail("plugin uninstall: missing canonical plugin id");
62
+ await uninstallResidentPlugin(pluginId);
63
+ console.log(`Plugin "${pluginId}" uninstalled.`);
64
+ return 0;
65
+ }
66
+ case "install":
67
+ case "update": {
68
+ const parsed = parseInstallationArgs(subcommand, args.slice(1));
69
+ const preparation = await prepareResidentPluginInstallation({
70
+ operation: subcommand,
71
+ ...(subcommand === "install"
72
+ ? CANONICAL_PLUGIN_ID_PATTERN.test(parsed.target)
73
+ ? { marketplacePlugin: parsed.target }
74
+ : { spec: parsed.target }
75
+ : { pluginId: parsed.target }),
76
+ });
77
+ let committed = false;
78
+ try {
79
+ if (!parsed.json)
80
+ printPreparation(preparation);
81
+ const interactive = options.interactive ??
82
+ Boolean(process.stdin.isTTY && process.stdout.isTTY);
83
+ const confirm = options.confirm ?? confirmInTerminal;
84
+ if (parsed.expectedDigest !== undefined &&
85
+ parsed.expectedDigest !== preparation.artifact.digest) {
86
+ fail(`prepared plugin digest ${preparation.artifact.digest} does not match ` +
87
+ `--expect-digest ${parsed.expectedDigest}`);
88
+ }
89
+ if (subcommand === "update" && preparation.unchanged) {
90
+ if (parsed.json) {
91
+ console.log(JSON.stringify({
92
+ preparation: publicPreparation(preparation),
93
+ unchanged: true,
94
+ }, null, 2));
95
+ }
96
+ else {
97
+ console.log(`Plugin "${preparation.artifact.id}" is already up to date.`);
98
+ }
99
+ return 0;
100
+ }
101
+ let overwrite = subcommand === "update";
102
+ if (subcommand === "install" && preparation.existing) {
103
+ overwrite = parsed.force;
104
+ if (!overwrite) {
105
+ if (!interactive || parsed.json) {
106
+ fail(`plugin "${preparation.artifact.id}" already exists; ` +
107
+ `re-run with --force to allow replacement`);
108
+ }
109
+ overwrite = await confirm(`Overwrite installed plugin "${preparation.artifact.id}" with this artifact?`);
110
+ if (!overwrite) {
111
+ console.log("Plugin installation cancelled; the existing plugin was not changed.");
112
+ return 0;
113
+ }
114
+ }
115
+ }
116
+ const result = await commitResidentPluginInstallation(preparation.token, {
117
+ overwrite,
118
+ expectedDigest: preparation.artifact.digest,
119
+ });
120
+ committed = true;
73
121
  if (parsed.json) {
74
122
  console.log(JSON.stringify({
75
123
  preparation: publicPreparation(preparation),
76
- unchanged: true,
124
+ plugin: result.plugin,
77
125
  }, null, 2));
78
126
  }
79
127
  else {
80
- console.log(`Plugin "${preparation.artifact.id}" is already up to date.`);
128
+ const action = subcommand === "install" ? "installed" : "updated";
129
+ console.log(`Plugin "${result.plugin.id}"${result.plugin.version
130
+ ? ` @${result.plugin.version}`
131
+ : ""} ${action}.`);
132
+ console.log(`Runtime state: ${result.plugin.runtimeState}.`);
81
133
  }
82
134
  return 0;
83
135
  }
84
- if ((!interactive || parsed.json) &&
85
- parsed.force &&
86
- parsed.expectedDigest === undefined) {
87
- fail("non-interactive --force confirmation must be bound to the " +
88
- `prepared artifact; re-run with --expect-digest ${preparation.artifact.digest}`);
89
- }
90
- let overwrite = subcommand === "update";
91
- if (subcommand === "install" && preparation.existing) {
92
- overwrite = parsed.force;
93
- if (!overwrite) {
94
- if (!interactive || parsed.json) {
95
- fail(`plugin "${preparation.artifact.id}" already exists; ` +
96
- `inspect digest ${preparation.artifact.digest} and re-run with ` +
97
- `--force --expect-digest ${preparation.artifact.digest}`);
98
- }
99
- overwrite = await confirm(`Overwrite installed plugin "${preparation.artifact.id}" with this artifact?`);
100
- if (!overwrite) {
101
- console.log("Plugin installation cancelled; the existing plugin was not changed.");
102
- return 0;
103
- }
136
+ finally {
137
+ if (!committed) {
138
+ await cancelResidentPluginInstallation(preparation.token).catch(() => false);
104
139
  }
105
140
  }
106
- const result = await commitResidentPluginInstallation(preparation.token, {
107
- overwrite,
108
- expectedDigest: preparation.artifact.digest,
109
- });
110
- committed = true;
111
- if (parsed.json) {
112
- console.log(JSON.stringify({
113
- preparation: publicPreparation(preparation),
114
- plugin: result.plugin,
115
- }, null, 2));
116
- }
117
- else {
118
- const action = subcommand === "install" ? "installed" : "updated";
119
- console.log(`Plugin "${result.plugin.id}"${result.plugin.version
120
- ? ` @${result.plugin.version}`
121
- : ""} ${action}.`);
122
- console.log(`Runtime state: ${result.plugin.runtimeState}.`);
123
- }
124
- return 0;
125
- }
126
- finally {
127
- if (!committed) {
128
- await cancelResidentPluginInstallation(preparation.token).catch(() => false);
129
- }
130
141
  }
142
+ default:
143
+ fail("plugin: expected list, install, update, uninstall, enable, or disable");
131
144
  }
132
- default:
133
- fail("plugin: expected list, install, update, uninstall, enable, or disable");
134
- }
145
+ });
135
146
  }
136
147
  export async function runPluginCommand(args) {
137
148
  const [pluginId, command] = args;
@@ -141,7 +152,7 @@ export async function runPluginCommand(args) {
141
152
  console.log(PLUGIN_USAGE);
142
153
  return 0;
143
154
  }
144
- if (["list", "install", "update", "uninstall", "enable", "disable"].includes(pluginId)) {
155
+ if (["list", "install", "update", "uninstall", "enable", "disable", "capabilities", "restore"].includes(pluginId)) {
145
156
  return runPluginManageCommand(args);
146
157
  }
147
158
  if (!command)
@@ -0,0 +1,4 @@
1
+ import * as resident from "./control-client.js";
2
+ type Management = Pick<typeof resident, "listResidentPlugins" | "listResidentPluginMarketplaces" | "addResidentPluginMarketplace" | "refreshResidentPluginMarketplace" | "removeResidentPluginMarketplace" | "setResidentPluginEnabled" | "uninstallResidentPlugin" | "prepareResidentPluginInstallation" | "commitResidentPluginInstallation" | "cancelResidentPluginInstallation">;
3
+ export declare function withPluginManagement<T>(run: (api: Management) => Promise<T>): Promise<T>;
4
+ export {};
@@ -0,0 +1,16 @@
1
+ import * as resident from "./control-client.js";
2
+ import { resolveDaemonControlEndpoint } from "./control-endpoint.js";
3
+ export async function withPluginManagement(run) {
4
+ if (await resolveDaemonControlEndpoint())
5
+ return run(resident);
6
+ const { openOfflinePluginManagement } = await import("@rynx-ai/daemon/plugin-management");
7
+ // A missing/unhealthy HTTP endpoint is not proof of an absent owner. The OS
8
+ // lease rejects a starting, wedged or concurrently starting daemon here.
9
+ const local = await openOfflinePluginManagement();
10
+ try {
11
+ return await run(local);
12
+ }
13
+ finally {
14
+ await local.close();
15
+ }
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/cli",
3
- "version": "0.1.11-beta.52",
3
+ "version": "0.1.11-beta.53",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -51,11 +51,11 @@
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.6.0",
53
53
  "ws": "^8.21.0",
54
- "@rynx-ai/core": "0.1.11-beta.52",
55
- "@rynx-ai/daemon": "0.1.11-beta.52",
56
- "@rynx-ai/emulator": "0.1.11-beta.52",
57
- "@rynx-ai/protocol": "0.1.11-beta.52",
58
- "@rynx-ai/tmux": "0.1.11-beta.52"
54
+ "@rynx-ai/core": "0.1.11-beta.53",
55
+ "@rynx-ai/daemon": "0.1.11-beta.53",
56
+ "@rynx-ai/emulator": "0.1.11-beta.53",
57
+ "@rynx-ai/tmux": "0.1.11-beta.53",
58
+ "@rynx-ai/protocol": "0.1.11-beta.53"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/ws": "^8.18.1"