@rynx-ai/cli 0.1.11-beta.1 → 0.1.11-beta.3
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/app-runtime.d.ts +10 -0
- package/dist/app-runtime.js +34 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/commands/app-distribution.d.ts +2 -2
- package/dist/commands/app-distribution.js +2 -6
- package/dist/commands/browser.js +9 -5
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.js +3 -1
- package/dist/commands/lifecycle.d.ts +1 -0
- package/dist/commands/lifecycle.js +21 -0
- package/dist/commands/market.d.ts +1 -0
- package/dist/commands/market.js +71 -0
- package/dist/commands/plugin.js +37 -93
- package/dist/commands/session.d.ts +1 -0
- package/dist/commands/session.js +38 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +314 -0
- package/dist/commands/skills.d.ts +6 -4
- package/dist/commands/skills.js +41 -28
- package/dist/commands/update.d.ts +20 -0
- package/dist/commands/update.js +234 -0
- package/dist/control-client.d.ts +7 -14
- package/dist/control-client.js +32 -54
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/run-cli.js +41 -12
- package/dist/standalone.d.ts +13 -0
- package/dist/standalone.js +54 -0
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +27 -16
- package/package.json +20 -6
- package/skill-guides/browser.md +72 -0
- package/skill-guides/emulator.md +79 -0
- package/skills/rynx-cli/SKILL.md +19 -87
- package/dist/legacy-adapter.d.ts +0 -7
- package/dist/legacy-adapter.js +0 -63
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import * as prompts from "@clack/prompts";
|
|
5
|
+
import { AGENT_RUNTIME_IDS, getRuntimeProfile, loadConfig, rynxConfigFile, } from "@rynx-ai/core";
|
|
6
|
+
import { inspectDaemonDiagnostics, prepareBundledPlugins, } from "@rynx-ai/daemon/setup-service";
|
|
7
|
+
import { fail } from "./errors.js";
|
|
8
|
+
export async function runSetupCommand(args) {
|
|
9
|
+
const options = parseSetupOptions(args);
|
|
10
|
+
const interactive = !options.nonInteractive &&
|
|
11
|
+
!options.hasConfigurationArguments &&
|
|
12
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
13
|
+
const existing = readRawConfig();
|
|
14
|
+
const created = !existsSync(rynxConfigFile());
|
|
15
|
+
const defaults = loadConfig({});
|
|
16
|
+
const next = {
|
|
17
|
+
...existing,
|
|
18
|
+
HOST: existing.HOST ?? defaults.HOST,
|
|
19
|
+
PORT: existing.PORT ?? defaults.PORT,
|
|
20
|
+
LOG_LEVEL: existing.LOG_LEVEL ?? defaults.LOG_LEVEL,
|
|
21
|
+
DEFAULT_RUNTIME: existing.DEFAULT_RUNTIME ?? defaults.DEFAULT_RUNTIME,
|
|
22
|
+
};
|
|
23
|
+
if (interactive) {
|
|
24
|
+
if (!await collectInteractiveSetup(next))
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
if (options.defaultRuntime !== undefined)
|
|
29
|
+
next.DEFAULT_RUNTIME = options.defaultRuntime;
|
|
30
|
+
if (options.host !== undefined)
|
|
31
|
+
next.HOST = options.host;
|
|
32
|
+
if (options.port !== undefined)
|
|
33
|
+
next.PORT = options.port;
|
|
34
|
+
if (options.logLevel !== undefined)
|
|
35
|
+
next.LOG_LEVEL = options.logLevel;
|
|
36
|
+
}
|
|
37
|
+
validateSetupConfig(next);
|
|
38
|
+
const updated = ["HOST", "PORT", "LOG_LEVEL", "DEFAULT_RUNTIME"].filter((key) => existing[key] !== next[key]);
|
|
39
|
+
writeConfigAtomically(next);
|
|
40
|
+
const result = {
|
|
41
|
+
configPath: rynxConfigFile(),
|
|
42
|
+
created,
|
|
43
|
+
updated,
|
|
44
|
+
browser: {
|
|
45
|
+
action: options.browser,
|
|
46
|
+
status: "skipped",
|
|
47
|
+
},
|
|
48
|
+
plugins: {
|
|
49
|
+
status: "ready",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
let failed = false;
|
|
53
|
+
try {
|
|
54
|
+
const plugins = await prepareBundledPlugins();
|
|
55
|
+
result.plugins = {
|
|
56
|
+
status: plugins.status,
|
|
57
|
+
changed: [...plugins.installed, ...plugins.updated],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
failed = true;
|
|
62
|
+
result.plugins = {
|
|
63
|
+
status: "error",
|
|
64
|
+
detail: errorMessage(error),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const diagnostics = await inspectDaemonDiagnostics();
|
|
68
|
+
if (!diagnostics.browser.supported) {
|
|
69
|
+
result.browser = { action: options.browser, status: "unsupported" };
|
|
70
|
+
}
|
|
71
|
+
else if (diagnostics.browser.installedVersion) {
|
|
72
|
+
result.browser = {
|
|
73
|
+
action: options.browser,
|
|
74
|
+
status: "ready",
|
|
75
|
+
version: diagnostics.browser.installedVersion,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
else if (options.browser !== "skip") {
|
|
79
|
+
try {
|
|
80
|
+
const { createBrowserArtifactManagementService } = await import("@rynx-ai/daemon/browser-artifacts");
|
|
81
|
+
const installed = await createBrowserArtifactManagementService().install({ channel: "stable" }, options.json
|
|
82
|
+
? {}
|
|
83
|
+
: { onProgress: (message) => console.error(message) });
|
|
84
|
+
result.browser = {
|
|
85
|
+
action: options.browser,
|
|
86
|
+
status: "ready",
|
|
87
|
+
version: installed.installed.version,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
failed = true;
|
|
92
|
+
result.browser = {
|
|
93
|
+
action: options.browser,
|
|
94
|
+
status: "error",
|
|
95
|
+
detail: errorMessage(error),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (options.json) {
|
|
100
|
+
console.log(JSON.stringify(result, null, 2));
|
|
101
|
+
}
|
|
102
|
+
else if (interactive) {
|
|
103
|
+
prompts.log.success(`已写入 ${result.configPath}`);
|
|
104
|
+
prompts.log.info(`HOST=${next.HOST} PORT=${next.PORT} DEFAULT_RUNTIME=${next.DEFAULT_RUNTIME}`);
|
|
105
|
+
if (result.browser.status === "ready") {
|
|
106
|
+
prompts.log.success(`Session Browser ${result.browser.version ?? "ready"}`);
|
|
107
|
+
}
|
|
108
|
+
else if (result.browser.status === "error") {
|
|
109
|
+
prompts.log.warn(`Session Browser: ${result.browser.detail}`);
|
|
110
|
+
}
|
|
111
|
+
prompts.outro(failed ? "初始化完成,但存在失败项" : "配置完成");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(`Initialized ${result.configPath}`);
|
|
115
|
+
if (result.browser.status === "ready") {
|
|
116
|
+
console.log(`Browser ready${result.browser.version ? `: ${result.browser.version}` : ""}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return failed ? 1 : 0;
|
|
120
|
+
}
|
|
121
|
+
export async function runDoctorCommand(args) {
|
|
122
|
+
const json = args.includes("--json");
|
|
123
|
+
if (args.some((arg) => arg !== "--json")) {
|
|
124
|
+
fail(`doctor: unexpected argument ${args.find((arg) => arg !== "--json")}`);
|
|
125
|
+
}
|
|
126
|
+
let config;
|
|
127
|
+
let configError;
|
|
128
|
+
try {
|
|
129
|
+
config = loadConfig();
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
configError = errorMessage(error);
|
|
133
|
+
}
|
|
134
|
+
const runtimes = AGENT_RUNTIME_IDS.map((id) => {
|
|
135
|
+
const binary = getRuntimeProfile(id).defaultBinary;
|
|
136
|
+
const probe = spawnSync(binary, ["--version"], {
|
|
137
|
+
stdio: "ignore",
|
|
138
|
+
timeout: 8_000,
|
|
139
|
+
});
|
|
140
|
+
return { id, installed: !probe.error && probe.status === 0 };
|
|
141
|
+
});
|
|
142
|
+
const daemon = await inspectDaemonDiagnostics();
|
|
143
|
+
const result = {
|
|
144
|
+
ok: configError === undefined && runtimes.some((runtime) => runtime.installed),
|
|
145
|
+
configPath: rynxConfigFile(),
|
|
146
|
+
config,
|
|
147
|
+
configError,
|
|
148
|
+
runtimes,
|
|
149
|
+
daemon,
|
|
150
|
+
};
|
|
151
|
+
if (json)
|
|
152
|
+
console.log(JSON.stringify(result, null, 2));
|
|
153
|
+
else {
|
|
154
|
+
console.log(`config: ${result.configPath}`);
|
|
155
|
+
if (configError)
|
|
156
|
+
console.error(`config: ${configError}`);
|
|
157
|
+
else
|
|
158
|
+
console.log(`default runtime: ${config.DEFAULT_RUNTIME}`);
|
|
159
|
+
for (const runtime of runtimes) {
|
|
160
|
+
console.log(`${runtime.id}: ${runtime.installed ? "installed" : "not installed"}`);
|
|
161
|
+
}
|
|
162
|
+
if (daemon.browser.supported) {
|
|
163
|
+
console.log(`browser: ${daemon.browser.installedVersion ?? daemon.browser.error ?? "not installed"}`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
console.log("browser: unsupported");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result.ok ? 0 : 1;
|
|
170
|
+
}
|
|
171
|
+
function parseSetupOptions(args) {
|
|
172
|
+
const options = {
|
|
173
|
+
nonInteractive: false,
|
|
174
|
+
json: false,
|
|
175
|
+
browser: "auto",
|
|
176
|
+
hasConfigurationArguments: false,
|
|
177
|
+
};
|
|
178
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
179
|
+
const arg = args[index];
|
|
180
|
+
if (arg === "--non-interactive") {
|
|
181
|
+
options.nonInteractive = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (arg === "--json") {
|
|
185
|
+
options.json = true;
|
|
186
|
+
options.nonInteractive = true;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === "--install-browser" || arg === "--skip-browser") {
|
|
190
|
+
if (options.browser !== "auto")
|
|
191
|
+
fail("setup: choose only one Browser action");
|
|
192
|
+
options.browser = arg === "--install-browser" ? "install" : "skip";
|
|
193
|
+
options.hasConfigurationArguments = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const value = args[index + 1];
|
|
197
|
+
if (!value || value.startsWith("--"))
|
|
198
|
+
fail(`setup: ${arg} requires a value`);
|
|
199
|
+
index += 1;
|
|
200
|
+
options.hasConfigurationArguments = true;
|
|
201
|
+
if (arg === "--default-runtime") {
|
|
202
|
+
if (!AGENT_RUNTIME_IDS.includes(value)) {
|
|
203
|
+
fail("setup: --default-runtime must be codex, traex, or claude");
|
|
204
|
+
}
|
|
205
|
+
options.defaultRuntime = value;
|
|
206
|
+
}
|
|
207
|
+
else if (arg === "--host") {
|
|
208
|
+
if (!value.trim() || value.length > 255)
|
|
209
|
+
fail("setup: --host is invalid");
|
|
210
|
+
options.host = value;
|
|
211
|
+
}
|
|
212
|
+
else if (arg === "--port") {
|
|
213
|
+
const port = Number(value);
|
|
214
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
215
|
+
fail("setup: --port must be an integer from 1 to 65535");
|
|
216
|
+
}
|
|
217
|
+
options.port = port;
|
|
218
|
+
}
|
|
219
|
+
else if (arg === "--log-level") {
|
|
220
|
+
if (!value.trim() || value.length > 32)
|
|
221
|
+
fail("setup: --log-level is invalid");
|
|
222
|
+
options.logLevel = value;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
fail(`setup: unknown option ${arg}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return options;
|
|
229
|
+
}
|
|
230
|
+
async function collectInteractiveSetup(config) {
|
|
231
|
+
prompts.intro("rynx setup");
|
|
232
|
+
const runtime = await prompts.select({
|
|
233
|
+
message: "Default runtime",
|
|
234
|
+
initialValue: config.DEFAULT_RUNTIME,
|
|
235
|
+
options: AGENT_RUNTIME_IDS.map((id) => ({ value: id, label: id })),
|
|
236
|
+
});
|
|
237
|
+
if (prompts.isCancel(runtime))
|
|
238
|
+
return cancelled();
|
|
239
|
+
config.DEFAULT_RUNTIME = runtime;
|
|
240
|
+
const port = await prompts.text({
|
|
241
|
+
message: "HTTP port",
|
|
242
|
+
initialValue: String(config.PORT),
|
|
243
|
+
validate: (value) => {
|
|
244
|
+
const parsed = Number(value);
|
|
245
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65_535
|
|
246
|
+
? undefined
|
|
247
|
+
: "Enter a port from 1 to 65535";
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
if (prompts.isCancel(port))
|
|
251
|
+
return cancelled();
|
|
252
|
+
config.PORT = Number(port);
|
|
253
|
+
const host = await prompts.text({
|
|
254
|
+
message: "Bind host",
|
|
255
|
+
initialValue: String(config.HOST),
|
|
256
|
+
});
|
|
257
|
+
if (prompts.isCancel(host))
|
|
258
|
+
return cancelled();
|
|
259
|
+
config.HOST = host;
|
|
260
|
+
const logLevel = await prompts.text({
|
|
261
|
+
message: "Log level",
|
|
262
|
+
initialValue: String(config.LOG_LEVEL),
|
|
263
|
+
});
|
|
264
|
+
if (prompts.isCancel(logLevel))
|
|
265
|
+
return cancelled();
|
|
266
|
+
config.LOG_LEVEL = logLevel;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
function cancelled() {
|
|
270
|
+
prompts.cancel("Cancelled");
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
function validateSetupConfig(config) {
|
|
274
|
+
loadConfig({
|
|
275
|
+
HOST: String(config.HOST),
|
|
276
|
+
PORT: String(config.PORT),
|
|
277
|
+
LOG_LEVEL: String(config.LOG_LEVEL),
|
|
278
|
+
DEFAULT_RUNTIME: String(config.DEFAULT_RUNTIME),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function readRawConfig() {
|
|
282
|
+
try {
|
|
283
|
+
const parsed = JSON.parse(readFileSync(rynxConfigFile(), "utf8"));
|
|
284
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
285
|
+
? parsed
|
|
286
|
+
: {};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function writeConfigAtomically(config) {
|
|
293
|
+
const file = rynxConfigFile();
|
|
294
|
+
const directory = path.dirname(file);
|
|
295
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
296
|
+
const temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.tmp`);
|
|
297
|
+
let descriptor;
|
|
298
|
+
try {
|
|
299
|
+
descriptor = openSync(temporary, "wx", 0o600);
|
|
300
|
+
writeFileSync(descriptor, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
301
|
+
closeSync(descriptor);
|
|
302
|
+
descriptor = undefined;
|
|
303
|
+
renameSync(temporary, file);
|
|
304
|
+
chmodSync(file, 0o600);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
if (descriptor !== undefined)
|
|
308
|
+
closeSync(descriptor);
|
|
309
|
+
rmSync(temporary, { force: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function errorMessage(error) {
|
|
313
|
+
return error instanceof Error ? error.message : String(error);
|
|
314
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export interface BuiltinSkill {
|
|
2
2
|
name: string;
|
|
3
|
-
|
|
4
|
-
skillFile: string;
|
|
3
|
+
description: string;
|
|
5
4
|
content: string;
|
|
5
|
+
full: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare function runSkillsCommand(args: readonly string[]): Promise<number>;
|
|
8
8
|
export declare function listBuiltinSkills(): Promise<BuiltinSkill[]>;
|
|
9
|
-
export declare function readBuiltinSkill(name: string): Promise<BuiltinSkill | null>;
|
|
10
|
-
|
|
9
|
+
export declare function readBuiltinSkill(name: string, full?: boolean): Promise<BuiltinSkill | null>;
|
|
10
|
+
/** Resolve only inside the independently published @rynx-ai/cli package.
|
|
11
|
+
* The source and compiled module depths are identical (`src|dist/commands`). */
|
|
12
|
+
export declare function builtinSkillGuidesDirectory(moduleUrl?: string): string;
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import { readFile } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { fail } from "./errors.js";
|
|
6
5
|
const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
7
6
|
const MAX_SKILL_BYTES = 256 * 1024;
|
|
7
|
+
const BUILTIN_SKILL_NAMES = ["browser", "emulator"];
|
|
8
8
|
export async function runSkillsCommand(args) {
|
|
9
9
|
const [subcommand, name] = args;
|
|
10
10
|
const json = args.includes("--json");
|
|
11
11
|
if (subcommand === "list") {
|
|
12
12
|
const skills = await listBuiltinSkills();
|
|
13
13
|
if (json) {
|
|
14
|
-
console.log(JSON.stringify({
|
|
14
|
+
console.log(JSON.stringify({
|
|
15
|
+
topics: skills.map(({ name: topicName, description }) => ({
|
|
16
|
+
name: topicName,
|
|
17
|
+
description,
|
|
18
|
+
})),
|
|
19
|
+
}, null, 2));
|
|
15
20
|
}
|
|
16
21
|
else {
|
|
17
22
|
for (const skill of skills)
|
|
18
|
-
console.log(skill.name);
|
|
23
|
+
console.log(`${skill.name}: ${skill.description}`);
|
|
19
24
|
}
|
|
20
25
|
return 0;
|
|
21
26
|
}
|
|
@@ -23,15 +28,17 @@ export async function runSkillsCommand(args) {
|
|
|
23
28
|
if (!name || name.startsWith("--")) {
|
|
24
29
|
fail("skills get: missing Builtin Skill name");
|
|
25
30
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
const full = args.includes("--full");
|
|
32
|
+
const skill = await readBuiltinSkill(name, full);
|
|
33
|
+
if (!skill) {
|
|
34
|
+
const available = (await listBuiltinSkills()).map((entry) => entry.name).join(", ");
|
|
35
|
+
fail(`Unknown Skill guide "${name}". Available topics: ${available}`);
|
|
36
|
+
}
|
|
29
37
|
if (json) {
|
|
30
38
|
console.log(JSON.stringify({
|
|
31
39
|
name: skill.name,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
content: skill.content,
|
|
40
|
+
full: skill.full,
|
|
41
|
+
markdown: skill.content,
|
|
35
42
|
}, null, 2));
|
|
36
43
|
}
|
|
37
44
|
else {
|
|
@@ -44,21 +51,25 @@ export async function runSkillsCommand(args) {
|
|
|
44
51
|
fail("skills: expected list or get <name>");
|
|
45
52
|
}
|
|
46
53
|
export async function listBuiltinSkills() {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
return skills.filter((skill) => skill !== null);
|
|
54
|
+
const guides = await Promise.all(BUILTIN_SKILL_NAMES.map((name) => readBuiltinSkill(name)));
|
|
55
|
+
return guides.filter((guide) => guide !== null);
|
|
50
56
|
}
|
|
51
|
-
export async function readBuiltinSkill(name) {
|
|
57
|
+
export async function readBuiltinSkill(name, full = false) {
|
|
52
58
|
if (!SKILL_NAME_PATTERN.test(name))
|
|
53
59
|
return null;
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
if (!BUILTIN_SKILL_NAMES.includes(name))
|
|
61
|
+
return null;
|
|
62
|
+
const guideFile = path.join(builtinSkillGuidesDirectory(), `${name}.md`);
|
|
56
63
|
try {
|
|
57
|
-
const content = await readFile(
|
|
64
|
+
const content = await readFile(guideFile, "utf8");
|
|
58
65
|
if (Buffer.byteLength(content, "utf8") > MAX_SKILL_BYTES) {
|
|
59
|
-
throw new Error(`Builtin Skill "${name}" exceeds ${MAX_SKILL_BYTES} bytes`);
|
|
66
|
+
throw new Error(`Builtin Skill guide "${name}" exceeds ${MAX_SKILL_BYTES} bytes`);
|
|
67
|
+
}
|
|
68
|
+
const metadata = parseGuideFrontmatter(content, guideFile);
|
|
69
|
+
if (metadata.name !== name) {
|
|
70
|
+
throw new Error(`Builtin Skill guide "${name}" declares name "${metadata.name}"`);
|
|
60
71
|
}
|
|
61
|
-
return { name,
|
|
72
|
+
return { name, description: metadata.description, content, full };
|
|
62
73
|
}
|
|
63
74
|
catch (error) {
|
|
64
75
|
if (error.code === "ENOENT")
|
|
@@ -66,15 +77,17 @@ export async function readBuiltinSkill(name) {
|
|
|
66
77
|
throw error;
|
|
67
78
|
}
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
/** Resolve only inside the independently published @rynx-ai/cli package.
|
|
81
|
+
* The source and compiled module depths are identical (`src|dist/commands`). */
|
|
82
|
+
export function builtinSkillGuidesDirectory(moduleUrl = import.meta.url) {
|
|
83
|
+
return path.resolve(fileURLToPath(new URL("../../skill-guides/", moduleUrl)));
|
|
84
|
+
}
|
|
85
|
+
function parseGuideFrontmatter(markdown, sourcePath) {
|
|
86
|
+
const match = /^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/.exec(markdown);
|
|
87
|
+
const name = match && /^name:\s*(.+?)\s*$/m.exec(match[1])?.[1];
|
|
88
|
+
const description = match && /^description:\s*(.+?)\s*$/m.exec(match[1])?.[1];
|
|
89
|
+
if (!name || !description) {
|
|
90
|
+
throw new Error(`Builtin Skill guide must declare single-line name and description: ${sourcePath}`);
|
|
73
91
|
}
|
|
74
|
-
|
|
75
|
-
if (existsSync(packaged))
|
|
76
|
-
return packaged;
|
|
77
|
-
// Source-tree tests run before a package build has copied the canonical
|
|
78
|
-
// artifact. Published/App builds always take the packaged branch above.
|
|
79
|
-
return fileURLToPath(new URL("../../../../skills/", moduleUrl));
|
|
92
|
+
return { name, description: description.replace(/\s+/g, " ").trim() };
|
|
80
93
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface UpdateOptions {
|
|
2
|
+
check?: boolean;
|
|
3
|
+
json?: boolean;
|
|
4
|
+
version?: string;
|
|
5
|
+
}
|
|
6
|
+
export type UpdateCheck = {
|
|
7
|
+
status: "up_to_date";
|
|
8
|
+
current: string;
|
|
9
|
+
} | {
|
|
10
|
+
status: "behind";
|
|
11
|
+
current: string;
|
|
12
|
+
latest: string;
|
|
13
|
+
} | {
|
|
14
|
+
status: "error";
|
|
15
|
+
detail: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function isNewer(latest: string, current: string): boolean;
|
|
18
|
+
export declare function buildUpdateCheck(current: string, latest: string | null): UpdateCheck;
|
|
19
|
+
export declare function checkStatusLine(current: string, latest: string | null): string;
|
|
20
|
+
export declare function runUpdate(options: UpdateOptions): Promise<number>;
|