@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.
- package/dist/commands/market.js +70 -58
- package/dist/commands/plugin.js +115 -104
- package/dist/plugin-management.d.ts +4 -0
- package/dist/plugin-management.js +16 -0
- package/package.json +6 -6
package/dist/commands/market.js
CHANGED
|
@@ -1,69 +1,81 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withPluginManagement } from "../plugin-management.js";
|
|
2
2
|
import { fail } from "./errors.js";
|
|
3
3
|
export async function runMarketCommand(args) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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)
|
package/dist/commands/plugin.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
-
import {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
124
|
+
plugin: result.plugin,
|
|
77
125
|
}, null, 2));
|
|
78
126
|
}
|
|
79
127
|
else {
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
55
|
-
"@rynx-ai/daemon": "0.1.11-beta.
|
|
56
|
-
"@rynx-ai/emulator": "0.1.11-beta.
|
|
57
|
-
"@rynx-ai/
|
|
58
|
-
"@rynx-ai/
|
|
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"
|