skills-package-manager 0.2.0 → 0.3.0
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/bin/skills-package-manager.js +10 -5
- package/bin/spm.js +10 -5
- package/dist/index.js +691 -172
- package/package.json +1 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runCli } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { formatErrorForDisplay, getExitCode, isSpmError, runCli } from '../dist/index.js'
|
|
3
|
+
|
|
4
|
+
runCli(process.argv).catch((error) => {
|
|
5
|
+
if (isSpmError(error)) {
|
|
6
|
+
console.error(formatErrorForDisplay(error))
|
|
7
|
+
process.exit(getExitCode(error))
|
|
8
|
+
}
|
|
9
|
+
console.error(error instanceof Error ? error.message : error)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
})
|
package/bin/spm.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runCli } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { formatErrorForDisplay, getExitCode, isSpmError, runCli } from '../dist/index.js'
|
|
3
|
+
|
|
4
|
+
runCli(process.argv).catch((error) => {
|
|
5
|
+
if (isSpmError(error)) {
|
|
6
|
+
console.error(formatErrorForDisplay(error))
|
|
7
|
+
process.exit(getExitCode(error))
|
|
8
|
+
}
|
|
9
|
+
console.error(error instanceof Error ? error.message : error)
|
|
10
|
+
process.exit(1)
|
|
11
|
+
})
|
package/dist/index.js
CHANGED
|
@@ -1,46 +1,470 @@
|
|
|
1
|
+
import { cac } from "cac";
|
|
1
2
|
import picocolors from "picocolors";
|
|
2
3
|
import { access, cp as promises_cp, lstat, mkdir, mkdtemp, readFile, readdir, rm as promises_rm, stat as promises_stat, symlink, writeFile } from "node:fs/promises";
|
|
3
4
|
import node_path, { join } from "node:path";
|
|
4
5
|
import yaml from "yaml";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
6
|
import { execFile } from "node:child_process";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
-
import {
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
10
|
import * as __rspack_external__clack_prompts_3cae1695 from "@clack/prompts";
|
|
11
|
+
var package_namespaceObject = {
|
|
12
|
+
rE: "0.3.0"
|
|
13
|
+
};
|
|
14
|
+
const UNIVERSAL_AGENT_NAMES = [
|
|
15
|
+
'Amp',
|
|
16
|
+
'Antigravity',
|
|
17
|
+
'Cline',
|
|
18
|
+
'Codex',
|
|
19
|
+
'Cursor',
|
|
20
|
+
'Deep Agents',
|
|
21
|
+
'Firebender',
|
|
22
|
+
'Gemini CLI',
|
|
23
|
+
'GitHub Copilot',
|
|
24
|
+
'Kimi Code CLI',
|
|
25
|
+
'OpenCode',
|
|
26
|
+
'Warp'
|
|
27
|
+
];
|
|
28
|
+
const ADDITIONAL_AGENT_TARGETS = [
|
|
29
|
+
{
|
|
30
|
+
label: 'Augment',
|
|
31
|
+
path: '.augment/skills'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'IBM Bob',
|
|
35
|
+
path: '.bob/skills'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: 'Claude Code',
|
|
39
|
+
path: '.claude/skills'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: 'OpenClaw',
|
|
43
|
+
path: 'skills'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
label: 'CodeBuddy',
|
|
47
|
+
path: '.codebuddy/skills'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: 'Command Code',
|
|
51
|
+
path: '.commandcode/skills'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
label: 'Continue',
|
|
55
|
+
path: '.continue/skills'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
label: 'Cortex Code',
|
|
59
|
+
path: '.cortex/skills'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: 'Trae',
|
|
63
|
+
path: '.trae/skills'
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
async function promptSkillSelection(skills) {
|
|
67
|
+
if (0 === skills.length) throw new Error('No skills found in repository');
|
|
68
|
+
if (1 === skills.length) return skills;
|
|
69
|
+
const options = skills.map((skill)=>({
|
|
70
|
+
value: skill,
|
|
71
|
+
label: skill.name,
|
|
72
|
+
hint: skill.description ? skill.description.length > 60 ? `${skill.description.slice(0, 57)}...` : skill.description : void 0
|
|
73
|
+
}));
|
|
74
|
+
const selected = await __rspack_external__clack_prompts_3cae1695.multiselect({
|
|
75
|
+
message: 'Select skills to install',
|
|
76
|
+
options,
|
|
77
|
+
required: true
|
|
78
|
+
});
|
|
79
|
+
if (__rspack_external__clack_prompts_3cae1695.isCancel(selected)) {
|
|
80
|
+
__rspack_external__clack_prompts_3cae1695.cancel('Installation cancelled');
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
return selected;
|
|
84
|
+
}
|
|
85
|
+
async function promptInitManifestOptions(promptApi = __rspack_external__clack_prompts_3cae1695, exit = process.exit) {
|
|
86
|
+
const installDirInput = await promptApi.text({
|
|
87
|
+
message: 'Where should skills be installed?',
|
|
88
|
+
initialValue: '.agents/skills'
|
|
89
|
+
});
|
|
90
|
+
if (promptApi.isCancel(installDirInput)) {
|
|
91
|
+
promptApi.cancel('Initialization cancelled');
|
|
92
|
+
exit(0);
|
|
93
|
+
}
|
|
94
|
+
promptApi.note(UNIVERSAL_AGENT_NAMES.join('\n'), 'Universal (.agents/skills) — always included');
|
|
95
|
+
const selected = await promptApi.groupMultiselect({
|
|
96
|
+
message: 'Which agents do you want to install to?',
|
|
97
|
+
options: {
|
|
98
|
+
'Additional agents': ADDITIONAL_AGENT_TARGETS.map((agent)=>({
|
|
99
|
+
value: agent.path,
|
|
100
|
+
label: `${agent.label} (${agent.path})`
|
|
101
|
+
}))
|
|
102
|
+
},
|
|
103
|
+
required: false
|
|
104
|
+
});
|
|
105
|
+
if (promptApi.isCancel(selected)) {
|
|
106
|
+
promptApi.cancel('Initialization cancelled');
|
|
107
|
+
exit(0);
|
|
108
|
+
}
|
|
109
|
+
const installDir = String(installDirInput).trim() || '.agents/skills';
|
|
110
|
+
return {
|
|
111
|
+
installDir,
|
|
112
|
+
linkTargets: selected
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var codes_ErrorCode = /*#__PURE__*/ function(ErrorCode) {
|
|
116
|
+
ErrorCode["FILE_NOT_FOUND"] = "ENOENT";
|
|
117
|
+
ErrorCode["PERMISSION_DENIED"] = "EACCES";
|
|
118
|
+
ErrorCode["FILE_EXISTS"] = "EEXIST";
|
|
119
|
+
ErrorCode["FS_ERROR"] = "EFS";
|
|
120
|
+
ErrorCode["GIT_CLONE_FAILED"] = "EGITCLONE";
|
|
121
|
+
ErrorCode["GIT_FETCH_FAILED"] = "EGITFETCH";
|
|
122
|
+
ErrorCode["GIT_CHECKOUT_FAILED"] = "EGITCHECKOUT";
|
|
123
|
+
ErrorCode["GIT_REF_NOT_FOUND"] = "EGITREF";
|
|
124
|
+
ErrorCode["GIT_NOT_INSTALLED"] = "EGITNOTFOUND";
|
|
125
|
+
ErrorCode["PARSE_ERROR"] = "EPARSE";
|
|
126
|
+
ErrorCode["JSON_PARSE_ERROR"] = "EJSONPARSE";
|
|
127
|
+
ErrorCode["YAML_PARSE_ERROR"] = "EYAMLPARSE";
|
|
128
|
+
ErrorCode["INVALID_SPECIFIER"] = "EINVALIDSPEC";
|
|
129
|
+
ErrorCode["MANIFEST_NOT_FOUND"] = "EMANIFEST";
|
|
130
|
+
ErrorCode["LOCKFILE_NOT_FOUND"] = "ELOCKFILE";
|
|
131
|
+
ErrorCode["LOCKFILE_OUTDATED"] = "ELOCKOUTDATED";
|
|
132
|
+
ErrorCode["MANIFEST_EXISTS"] = "EMANIFESTEXISTS";
|
|
133
|
+
ErrorCode["NETWORK_ERROR"] = "ENETWORK";
|
|
134
|
+
ErrorCode["REPO_NOT_FOUND"] = "EREPONOTFOUND";
|
|
135
|
+
ErrorCode["UNKNOWN_ERROR"] = "EUNKNOWN";
|
|
136
|
+
ErrorCode["NOT_IMPLEMENTED"] = "ENOTIMPL";
|
|
137
|
+
ErrorCode["VALIDATION_ERROR"] = "EVALIDATION";
|
|
138
|
+
ErrorCode["SKILL_NOT_FOUND"] = "ESKILLNOTFOUND";
|
|
139
|
+
ErrorCode["SKILL_EXISTS"] = "ESKILLEXISTS";
|
|
140
|
+
return ErrorCode;
|
|
141
|
+
}({});
|
|
142
|
+
class SpmError extends Error {
|
|
143
|
+
code;
|
|
144
|
+
cause;
|
|
145
|
+
context;
|
|
146
|
+
constructor(options){
|
|
147
|
+
super(options.message);
|
|
148
|
+
this.code = options.code;
|
|
149
|
+
this.cause = options.cause;
|
|
150
|
+
this.context = options.context ?? {};
|
|
151
|
+
this.name = 'SpmError';
|
|
152
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, SpmError);
|
|
153
|
+
}
|
|
154
|
+
toString() {
|
|
155
|
+
let result = `${this.code}: ${this.message}`;
|
|
156
|
+
if (this.cause) result += `\n Caused by: ${this.cause.message}`;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
toJSON() {
|
|
160
|
+
return {
|
|
161
|
+
name: this.name,
|
|
162
|
+
code: this.code,
|
|
163
|
+
message: this.message,
|
|
164
|
+
context: this.context,
|
|
165
|
+
cause: this.cause ? {
|
|
166
|
+
name: this.cause.name,
|
|
167
|
+
message: this.cause.message
|
|
168
|
+
} : void 0,
|
|
169
|
+
stack: this.stack
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
class FileSystemError extends SpmError {
|
|
174
|
+
operation;
|
|
175
|
+
path;
|
|
176
|
+
constructor(options){
|
|
177
|
+
const message = options.message ?? `${options.operation} failed for ${options.path}`;
|
|
178
|
+
super({
|
|
179
|
+
code: options.code,
|
|
180
|
+
message,
|
|
181
|
+
cause: options.cause,
|
|
182
|
+
context: {
|
|
183
|
+
operation: options.operation,
|
|
184
|
+
path: options.path
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
this.operation = options.operation;
|
|
188
|
+
this.path = options.path;
|
|
189
|
+
this.name = 'FileSystemError';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
class GitError extends SpmError {
|
|
193
|
+
operation;
|
|
194
|
+
repoUrl;
|
|
195
|
+
ref;
|
|
196
|
+
constructor(options){
|
|
197
|
+
const message = options.message ?? `git ${options.operation} failed${options.repoUrl ? ` for ${options.repoUrl}` : ''}`;
|
|
198
|
+
super({
|
|
199
|
+
code: options.code,
|
|
200
|
+
message,
|
|
201
|
+
cause: options.cause,
|
|
202
|
+
context: {
|
|
203
|
+
operation: options.operation,
|
|
204
|
+
repoUrl: options.repoUrl,
|
|
205
|
+
ref: options.ref
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
this.operation = options.operation;
|
|
209
|
+
this.repoUrl = options.repoUrl;
|
|
210
|
+
this.ref = options.ref;
|
|
211
|
+
this.name = 'GitError';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
class ParseError extends SpmError {
|
|
215
|
+
filePath;
|
|
216
|
+
content;
|
|
217
|
+
constructor(options){
|
|
218
|
+
super({
|
|
219
|
+
code: options.code,
|
|
220
|
+
message: options.message,
|
|
221
|
+
cause: options.cause,
|
|
222
|
+
context: {
|
|
223
|
+
filePath: options.filePath,
|
|
224
|
+
contentSnippet: options.content?.slice(0, 200)
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
this.filePath = options.filePath;
|
|
228
|
+
this.content = options.content;
|
|
229
|
+
this.name = 'ParseError';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
class ManifestError extends SpmError {
|
|
233
|
+
filePath;
|
|
234
|
+
constructor(options){
|
|
235
|
+
const defaultMessages = {
|
|
236
|
+
[codes_ErrorCode.MANIFEST_NOT_FOUND]: `Manifest not found: ${options.filePath}`,
|
|
237
|
+
[codes_ErrorCode.LOCKFILE_NOT_FOUND]: `Lockfile not found: ${options.filePath}`,
|
|
238
|
+
[codes_ErrorCode.LOCKFILE_OUTDATED]: `Lockfile is out of date: ${options.filePath}`,
|
|
239
|
+
[codes_ErrorCode.MANIFEST_EXISTS]: `Manifest already exists: ${options.filePath}`
|
|
240
|
+
};
|
|
241
|
+
const message = options.message ?? defaultMessages[options.code] ?? `Manifest error: ${options.filePath}`;
|
|
242
|
+
super({
|
|
243
|
+
code: options.code,
|
|
244
|
+
message,
|
|
245
|
+
cause: options.cause,
|
|
246
|
+
context: {
|
|
247
|
+
filePath: options.filePath
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
this.filePath = options.filePath;
|
|
251
|
+
this.name = 'ManifestError';
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
class SkillError extends SpmError {
|
|
255
|
+
skillName;
|
|
256
|
+
constructor(options){
|
|
257
|
+
const defaultMessages = {
|
|
258
|
+
[codes_ErrorCode.SKILL_NOT_FOUND]: `Skill not found: ${options.skillName}`,
|
|
259
|
+
[codes_ErrorCode.SKILL_EXISTS]: `Skill already exists: ${options.skillName}`,
|
|
260
|
+
[codes_ErrorCode.VALIDATION_ERROR]: `Skill validation failed: ${options.skillName}`
|
|
261
|
+
};
|
|
262
|
+
const message = options.message ?? defaultMessages[options.code] ?? `Skill error: ${options.skillName}`;
|
|
263
|
+
super({
|
|
264
|
+
code: options.code,
|
|
265
|
+
message,
|
|
266
|
+
cause: options.cause,
|
|
267
|
+
context: {
|
|
268
|
+
skillName: options.skillName
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
this.skillName = options.skillName;
|
|
272
|
+
this.name = 'SkillError';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
class NetworkError extends SpmError {
|
|
276
|
+
url;
|
|
277
|
+
constructor(options){
|
|
278
|
+
super({
|
|
279
|
+
code: options.code,
|
|
280
|
+
message: options.message,
|
|
281
|
+
cause: options.cause,
|
|
282
|
+
context: {
|
|
283
|
+
url: options.url
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
this.url = options.url;
|
|
287
|
+
this.name = 'NetworkError';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function convertNodeError(error, context) {
|
|
291
|
+
switch(error.code){
|
|
292
|
+
case 'ENOENT':
|
|
293
|
+
return new FileSystemError({
|
|
294
|
+
code: codes_ErrorCode.FILE_NOT_FOUND,
|
|
295
|
+
operation: context.operation,
|
|
296
|
+
path: context.path,
|
|
297
|
+
cause: error
|
|
298
|
+
});
|
|
299
|
+
case 'EACCES':
|
|
300
|
+
case 'EPERM':
|
|
301
|
+
return new FileSystemError({
|
|
302
|
+
code: codes_ErrorCode.PERMISSION_DENIED,
|
|
303
|
+
operation: context.operation,
|
|
304
|
+
path: context.path,
|
|
305
|
+
cause: error
|
|
306
|
+
});
|
|
307
|
+
case 'EEXIST':
|
|
308
|
+
return new FileSystemError({
|
|
309
|
+
code: codes_ErrorCode.FILE_EXISTS,
|
|
310
|
+
operation: context.operation,
|
|
311
|
+
path: context.path,
|
|
312
|
+
cause: error
|
|
313
|
+
});
|
|
314
|
+
case 'ENOTDIR':
|
|
315
|
+
return new FileSystemError({
|
|
316
|
+
code: codes_ErrorCode.FS_ERROR,
|
|
317
|
+
operation: context.operation,
|
|
318
|
+
path: context.path,
|
|
319
|
+
message: `Not a directory: ${context.path}`,
|
|
320
|
+
cause: error
|
|
321
|
+
});
|
|
322
|
+
case 'EISDIR':
|
|
323
|
+
return new FileSystemError({
|
|
324
|
+
code: codes_ErrorCode.FS_ERROR,
|
|
325
|
+
operation: context.operation,
|
|
326
|
+
path: context.path,
|
|
327
|
+
message: `Is a directory: ${context.path}`,
|
|
328
|
+
cause: error
|
|
329
|
+
});
|
|
330
|
+
default:
|
|
331
|
+
return new FileSystemError({
|
|
332
|
+
code: codes_ErrorCode.FS_ERROR,
|
|
333
|
+
operation: context.operation,
|
|
334
|
+
path: context.path,
|
|
335
|
+
message: error.message,
|
|
336
|
+
cause: error
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function formatErrorForDisplay(error) {
|
|
341
|
+
if (error instanceof SpmError) {
|
|
342
|
+
let output = `Error [${error.code}]: ${error.message}`;
|
|
343
|
+
if (error instanceof FileSystemError) {
|
|
344
|
+
if (error.code === codes_ErrorCode.FILE_NOT_FOUND) {
|
|
345
|
+
output += `\n\nThe file "${error.path}" was not found.`;
|
|
346
|
+
if (error.path.endsWith('skills.json')) output += `\nRun "spm init" to create a new skills.json file.`;
|
|
347
|
+
} else if (error.code === codes_ErrorCode.PERMISSION_DENIED) {
|
|
348
|
+
output += `\n\nPermission denied for "${error.path}".`;
|
|
349
|
+
output += `\nCheck that you have read/write access to this location.`;
|
|
350
|
+
}
|
|
351
|
+
} else if (error instanceof GitError) {
|
|
352
|
+
if (error.code === codes_ErrorCode.GIT_REF_NOT_FOUND) {
|
|
353
|
+
output += `\n\nThe reference "${error.ref}" could not be found in the repository.`;
|
|
354
|
+
output += `\nPlease check that the branch, tag, or commit hash is correct.`;
|
|
355
|
+
} else if (error.code === codes_ErrorCode.GIT_CLONE_FAILED) {
|
|
356
|
+
output += `\n\nFailed to clone the repository.`;
|
|
357
|
+
output += `\nPossible causes:`;
|
|
358
|
+
output += `\n - The repository URL is incorrect`;
|
|
359
|
+
output += `\n - The repository is private and requires authentication`;
|
|
360
|
+
output += `\n - There is no internet connection`;
|
|
361
|
+
}
|
|
362
|
+
} else if (error instanceof ParseError) {
|
|
363
|
+
if (error.code === codes_ErrorCode.JSON_PARSE_ERROR || error.code === codes_ErrorCode.YAML_PARSE_ERROR) {
|
|
364
|
+
output += `\n\nFile: ${error.filePath}`;
|
|
365
|
+
output += `\nPlease check the file syntax and fix any formatting issues.`;
|
|
366
|
+
} else if (error.code === codes_ErrorCode.INVALID_SPECIFIER) {
|
|
367
|
+
output += `\n\nInvalid skill specifier format.`;
|
|
368
|
+
output += `\nExpected formats:`;
|
|
369
|
+
output += `\n - owner/repo (GitHub shorthand)`;
|
|
370
|
+
output += `\n - https://github.com/owner/repo.git`;
|
|
371
|
+
output += `\n - file:./path/to/skill`;
|
|
372
|
+
}
|
|
373
|
+
} else if (error instanceof ManifestError) {
|
|
374
|
+
if (error.code === codes_ErrorCode.LOCKFILE_OUTDATED) {
|
|
375
|
+
output += `\n\nThe lockfile is out of sync with skills.json.`;
|
|
376
|
+
output += `\nRun "spm install" to update the lockfile.`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (error.cause && !(error instanceof GitError || error instanceof FileSystemError)) output += `\n\nCaused by: ${error.cause.message}`;
|
|
380
|
+
return output;
|
|
381
|
+
}
|
|
382
|
+
if (error instanceof Error) return `Error: ${error.message}`;
|
|
383
|
+
return `Error: ${String(error)}`;
|
|
384
|
+
}
|
|
385
|
+
function isSpmError(error) {
|
|
386
|
+
return error instanceof SpmError;
|
|
387
|
+
}
|
|
388
|
+
function getExitCode(error) {
|
|
389
|
+
if (error instanceof SpmError) {
|
|
390
|
+
if (error.code.startsWith('E1')) return 101;
|
|
391
|
+
if (error.code.startsWith('E2')) return 102;
|
|
392
|
+
if (error.code.startsWith('E3')) return 103;
|
|
393
|
+
if (error.code.startsWith('E4')) return 104;
|
|
394
|
+
if (error.code.startsWith('E5')) return 105;
|
|
395
|
+
if (error.code.startsWith('E9')) return 109;
|
|
396
|
+
}
|
|
397
|
+
return 1;
|
|
398
|
+
}
|
|
11
399
|
async function readSkillsLock(rootDir) {
|
|
12
400
|
const filePath = node_path.join(rootDir, 'skills-lock.yaml');
|
|
13
401
|
try {
|
|
14
402
|
const raw = await readFile(filePath, 'utf8');
|
|
15
|
-
|
|
403
|
+
try {
|
|
404
|
+
return yaml.parse(raw);
|
|
405
|
+
} catch (parseError) {
|
|
406
|
+
throw new ParseError({
|
|
407
|
+
code: codes_ErrorCode.YAML_PARSE_ERROR,
|
|
408
|
+
filePath,
|
|
409
|
+
content: raw,
|
|
410
|
+
message: `Failed to parse skills-lock.yaml: ${parseError.message}`,
|
|
411
|
+
cause: parseError
|
|
412
|
+
});
|
|
413
|
+
}
|
|
16
414
|
} catch (error) {
|
|
17
415
|
if ('ENOENT' === error.code) return null;
|
|
18
|
-
throw error;
|
|
416
|
+
if (error instanceof ParseError) throw error;
|
|
417
|
+
throw convertNodeError(error, {
|
|
418
|
+
operation: 'read',
|
|
419
|
+
path: filePath
|
|
420
|
+
});
|
|
19
421
|
}
|
|
20
422
|
}
|
|
21
423
|
async function readSkillsManifest(rootDir) {
|
|
22
424
|
const filePath = node_path.join(rootDir, 'skills.json');
|
|
23
425
|
try {
|
|
24
426
|
const raw = await readFile(filePath, 'utf8');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
427
|
+
try {
|
|
428
|
+
const json = JSON.parse(raw);
|
|
429
|
+
return {
|
|
430
|
+
installDir: json.installDir ?? '.agents/skills',
|
|
431
|
+
linkTargets: json.linkTargets ?? [],
|
|
432
|
+
skills: json.skills ?? {}
|
|
433
|
+
};
|
|
434
|
+
} catch (parseError) {
|
|
435
|
+
throw new ParseError({
|
|
436
|
+
code: codes_ErrorCode.JSON_PARSE_ERROR,
|
|
437
|
+
filePath,
|
|
438
|
+
content: raw,
|
|
439
|
+
message: `Failed to parse skills.json: ${parseError.message}`,
|
|
440
|
+
cause: parseError
|
|
441
|
+
});
|
|
442
|
+
}
|
|
31
443
|
} catch (error) {
|
|
32
444
|
if ('ENOENT' === error.code) return null;
|
|
33
|
-
throw error;
|
|
445
|
+
if (error instanceof ParseError) throw error;
|
|
446
|
+
throw convertNodeError(error, {
|
|
447
|
+
operation: 'read',
|
|
448
|
+
path: filePath
|
|
449
|
+
});
|
|
34
450
|
}
|
|
35
451
|
}
|
|
36
452
|
function parseSpecifier(specifier) {
|
|
37
453
|
const firstHashIndex = specifier.indexOf('#');
|
|
38
454
|
const secondHashIndex = firstHashIndex >= 0 ? specifier.indexOf('#', firstHashIndex + 1) : -1;
|
|
39
|
-
if (secondHashIndex >= 0) throw new
|
|
455
|
+
if (secondHashIndex >= 0) throw new ParseError({
|
|
456
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
457
|
+
message: 'Invalid specifier: multiple # fragments are not supported',
|
|
458
|
+
content: specifier
|
|
459
|
+
});
|
|
40
460
|
const hashIndex = firstHashIndex;
|
|
41
461
|
const sourcePart = hashIndex >= 0 ? specifier.slice(0, hashIndex) : specifier;
|
|
42
462
|
const fragment = hashIndex >= 0 ? specifier.slice(hashIndex + 1) : '';
|
|
43
|
-
if (!sourcePart) throw new
|
|
463
|
+
if (!sourcePart) throw new ParseError({
|
|
464
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
465
|
+
message: 'Specifier source is required',
|
|
466
|
+
content: specifier
|
|
467
|
+
});
|
|
44
468
|
if (!fragment) return {
|
|
45
469
|
sourcePart,
|
|
46
470
|
ref: null,
|
|
@@ -63,7 +487,18 @@ function parseSpecifier(specifier) {
|
|
|
63
487
|
};
|
|
64
488
|
}
|
|
65
489
|
function normalizeSpecifier(specifier) {
|
|
66
|
-
|
|
490
|
+
let parsed;
|
|
491
|
+
try {
|
|
492
|
+
parsed = parseSpecifier(specifier);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
if (error instanceof ParseError) throw error;
|
|
495
|
+
throw new ParseError({
|
|
496
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
497
|
+
message: `Invalid specifier: ${error.message}`,
|
|
498
|
+
content: specifier,
|
|
499
|
+
cause: error
|
|
500
|
+
});
|
|
501
|
+
}
|
|
67
502
|
const type = parsed.sourcePart.startsWith('file:') ? 'file' : parsed.sourcePart.startsWith('npm:') ? 'npm' : 'git';
|
|
68
503
|
const skillPath = parsed.path || '/';
|
|
69
504
|
const skillName = node_path.posix.basename(skillPath);
|
|
@@ -130,14 +565,32 @@ async function resolveGitCommit(url, ref) {
|
|
|
130
565
|
if (commit) return commit;
|
|
131
566
|
const clonedCommit = await resolveGitCommitByClone(url, target);
|
|
132
567
|
if (clonedCommit) return clonedCommit;
|
|
133
|
-
throw new
|
|
568
|
+
throw new GitError({
|
|
569
|
+
code: codes_ErrorCode.GIT_REF_NOT_FOUND,
|
|
570
|
+
operation: 'resolve-ref',
|
|
571
|
+
repoUrl: url,
|
|
572
|
+
ref: target,
|
|
573
|
+
message: `Unable to resolve git ref "${target}" for ${url}`
|
|
574
|
+
});
|
|
134
575
|
}
|
|
135
|
-
async function resolveLockEntry(cwd, specifier) {
|
|
136
|
-
|
|
576
|
+
async function resolveLockEntry(cwd, specifier, skillName) {
|
|
577
|
+
let normalized;
|
|
578
|
+
try {
|
|
579
|
+
normalized = normalizeSpecifier(specifier);
|
|
580
|
+
} catch (error) {
|
|
581
|
+
if (error instanceof ParseError) throw error;
|
|
582
|
+
throw new ParseError({
|
|
583
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
584
|
+
message: `Failed to parse specifier "${specifier}": ${error.message}`,
|
|
585
|
+
content: specifier,
|
|
586
|
+
cause: error
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
const finalSkillName = skillName || normalized.skillName;
|
|
137
590
|
if ('file' === normalized.type) {
|
|
138
591
|
const sourceRoot = node_path.resolve(cwd, normalized.source.slice(5));
|
|
139
592
|
return {
|
|
140
|
-
skillName:
|
|
593
|
+
skillName: finalSkillName,
|
|
141
594
|
entry: {
|
|
142
595
|
specifier: normalized.normalized,
|
|
143
596
|
resolution: {
|
|
@@ -151,7 +604,7 @@ async function resolveLockEntry(cwd, specifier) {
|
|
|
151
604
|
if ('git' === normalized.type) {
|
|
152
605
|
const commit = await resolveGitCommit(normalized.source, normalized.ref);
|
|
153
606
|
return {
|
|
154
|
-
skillName:
|
|
607
|
+
skillName: finalSkillName,
|
|
155
608
|
entry: {
|
|
156
609
|
specifier: normalized.normalized,
|
|
157
610
|
resolution: {
|
|
@@ -164,14 +617,21 @@ async function resolveLockEntry(cwd, specifier) {
|
|
|
164
617
|
}
|
|
165
618
|
};
|
|
166
619
|
}
|
|
167
|
-
throw new
|
|
620
|
+
throw new ParseError({
|
|
621
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
622
|
+
message: `Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`,
|
|
623
|
+
content: specifier
|
|
624
|
+
});
|
|
168
625
|
}
|
|
169
|
-
async function syncSkillsLock(cwd, manifest,
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
626
|
+
async function syncSkillsLock(cwd, manifest, _existingLock) {
|
|
627
|
+
const entries = await Promise.all(Object.entries(manifest.skills).map(async ([skillName, specifier])=>{
|
|
628
|
+
const { skillName: resolvedName, entry } = await resolveLockEntry(cwd, specifier, skillName);
|
|
629
|
+
return [
|
|
630
|
+
resolvedName,
|
|
631
|
+
entry
|
|
632
|
+
];
|
|
633
|
+
}));
|
|
634
|
+
const nextSkills = Object.fromEntries(entries);
|
|
175
635
|
return {
|
|
176
636
|
lockfileVersion: '0.1',
|
|
177
637
|
installDir: manifest.installDir ?? '.agents/skills',
|
|
@@ -181,7 +641,14 @@ async function syncSkillsLock(cwd, manifest, existingLock) {
|
|
|
181
641
|
}
|
|
182
642
|
async function writeSkillsLock(rootDir, lockfile) {
|
|
183
643
|
const filePath = node_path.join(rootDir, 'skills-lock.yaml');
|
|
184
|
-
|
|
644
|
+
try {
|
|
645
|
+
await writeFile(filePath, yaml.stringify(lockfile), 'utf8');
|
|
646
|
+
} catch (error) {
|
|
647
|
+
throw convertNodeError(error, {
|
|
648
|
+
operation: 'write',
|
|
649
|
+
path: filePath
|
|
650
|
+
});
|
|
651
|
+
}
|
|
185
652
|
}
|
|
186
653
|
async function writeSkillsManifest(rootDir, manifest) {
|
|
187
654
|
const filePath = node_path.join(rootDir, 'skills.json');
|
|
@@ -190,7 +657,14 @@ async function writeSkillsManifest(rootDir, manifest) {
|
|
|
190
657
|
linkTargets: manifest.linkTargets ?? [],
|
|
191
658
|
skills: manifest.skills
|
|
192
659
|
};
|
|
193
|
-
|
|
660
|
+
try {
|
|
661
|
+
await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
|
|
662
|
+
} catch (error) {
|
|
663
|
+
throw convertNodeError(error, {
|
|
664
|
+
operation: 'write',
|
|
665
|
+
path: filePath
|
|
666
|
+
});
|
|
667
|
+
}
|
|
194
668
|
}
|
|
195
669
|
const listSkills_execFileAsync = promisify(execFile);
|
|
196
670
|
const SKIP_DIRS = new Set([
|
|
@@ -343,107 +817,46 @@ function parseGitHubUrl(input) {
|
|
|
343
817
|
repo: match[2]
|
|
344
818
|
};
|
|
345
819
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
'Antigravity',
|
|
349
|
-
'Cline',
|
|
350
|
-
'Codex',
|
|
351
|
-
'Cursor',
|
|
352
|
-
'Deep Agents',
|
|
353
|
-
'Firebender',
|
|
354
|
-
'Gemini CLI',
|
|
355
|
-
'GitHub Copilot',
|
|
356
|
-
'Kimi Code CLI',
|
|
357
|
-
'OpenCode',
|
|
358
|
-
'Warp'
|
|
359
|
-
];
|
|
360
|
-
const ADDITIONAL_AGENT_TARGETS = [
|
|
361
|
-
{
|
|
362
|
-
label: 'Augment',
|
|
363
|
-
path: '.augment/skills'
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
label: 'IBM Bob',
|
|
367
|
-
path: '.bob/skills'
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
label: 'Claude Code',
|
|
371
|
-
path: '.claude/skills'
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
label: 'OpenClaw',
|
|
375
|
-
path: 'skills'
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
label: 'CodeBuddy',
|
|
379
|
-
path: '.codebuddy/skills'
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
label: 'Command Code',
|
|
383
|
-
path: '.commandcode/skills'
|
|
384
|
-
},
|
|
385
|
-
{
|
|
386
|
-
label: 'Continue',
|
|
387
|
-
path: '.continue/skills'
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
label: 'Cortex Code',
|
|
391
|
-
path: '.cortex/skills'
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
label: 'Trae',
|
|
395
|
-
path: '.trae/skills'
|
|
396
|
-
}
|
|
397
|
-
];
|
|
398
|
-
async function promptSkillSelection(skills) {
|
|
399
|
-
if (0 === skills.length) throw new Error('No skills found in repository');
|
|
400
|
-
if (1 === skills.length) return skills;
|
|
401
|
-
const options = skills.map((skill)=>({
|
|
402
|
-
value: skill,
|
|
403
|
-
label: skill.name,
|
|
404
|
-
hint: skill.description ? skill.description.length > 60 ? `${skill.description.slice(0, 57)}...` : skill.description : void 0
|
|
405
|
-
}));
|
|
406
|
-
const selected = await __rspack_external__clack_prompts_3cae1695.multiselect({
|
|
407
|
-
message: 'Select skills to install',
|
|
408
|
-
options,
|
|
409
|
-
required: true
|
|
410
|
-
});
|
|
411
|
-
if (__rspack_external__clack_prompts_3cae1695.isCancel(selected)) {
|
|
412
|
-
__rspack_external__clack_prompts_3cae1695.cancel('Installation cancelled');
|
|
413
|
-
process.exit(0);
|
|
414
|
-
}
|
|
415
|
-
return selected;
|
|
416
|
-
}
|
|
417
|
-
async function promptInitManifestOptions(promptApi = __rspack_external__clack_prompts_3cae1695, exit = process.exit) {
|
|
418
|
-
const installDirInput = await promptApi.text({
|
|
419
|
-
message: 'Where should skills be installed?',
|
|
420
|
-
initialValue: '.agents/skills'
|
|
421
|
-
});
|
|
422
|
-
if (promptApi.isCancel(installDirInput)) {
|
|
423
|
-
promptApi.cancel('Initialization cancelled');
|
|
424
|
-
exit(0);
|
|
425
|
-
}
|
|
426
|
-
promptApi.note(UNIVERSAL_AGENT_NAMES.join('\n'), 'Universal (.agents/skills) — always included');
|
|
427
|
-
const selected = await promptApi.groupMultiselect({
|
|
428
|
-
message: 'Which agents do you want to install to?',
|
|
429
|
-
options: {
|
|
430
|
-
'Additional agents': ADDITIONAL_AGENT_TARGETS.map((agent)=>({
|
|
431
|
-
value: agent.path,
|
|
432
|
-
label: `${agent.label} (${agent.path})`
|
|
433
|
-
}))
|
|
434
|
-
},
|
|
435
|
-
required: false
|
|
436
|
-
});
|
|
437
|
-
if (promptApi.isCancel(selected)) {
|
|
438
|
-
promptApi.cancel('Initialization cancelled');
|
|
439
|
-
exit(0);
|
|
440
|
-
}
|
|
441
|
-
const installDir = String(installDirInput).trim() || '.agents/skills';
|
|
820
|
+
function parseForComparison(specifier) {
|
|
821
|
+
const parsed = parseSpecifier(specifier);
|
|
442
822
|
return {
|
|
443
|
-
|
|
444
|
-
|
|
823
|
+
sourcePart: parsed.sourcePart,
|
|
824
|
+
ref: parsed.ref,
|
|
825
|
+
path: parsed.path || '/'
|
|
445
826
|
};
|
|
446
827
|
}
|
|
828
|
+
function isSpecifierCompatible(manifestSpecifier, lockSpecifier) {
|
|
829
|
+
const manifest = parseForComparison(manifestSpecifier);
|
|
830
|
+
const lock = parseForComparison(lockSpecifier);
|
|
831
|
+
if (manifest.sourcePart !== lock.sourcePart) return false;
|
|
832
|
+
if (manifest.path !== lock.path) return false;
|
|
833
|
+
if (null === manifest.ref) return true;
|
|
834
|
+
return manifest.ref === lock.ref;
|
|
835
|
+
}
|
|
836
|
+
function normalizeInstallDir(dir) {
|
|
837
|
+
return dir ?? '.agents/skills';
|
|
838
|
+
}
|
|
839
|
+
function normalizeLinkTargets(targets) {
|
|
840
|
+
return targets ?? [];
|
|
841
|
+
}
|
|
842
|
+
function arraysEqual(a, b) {
|
|
843
|
+
if (a.length !== b.length) return false;
|
|
844
|
+
return a.every((val, i)=>val === b[i]);
|
|
845
|
+
}
|
|
846
|
+
function isLockInSync(manifest, lock) {
|
|
847
|
+
if (!lock) return false;
|
|
848
|
+
if (normalizeInstallDir(manifest.installDir) !== normalizeInstallDir(lock.installDir)) return false;
|
|
849
|
+
if (!arraysEqual(normalizeLinkTargets(manifest.linkTargets), normalizeLinkTargets(lock.linkTargets))) return false;
|
|
850
|
+
const manifestSkills = Object.entries(manifest.skills);
|
|
851
|
+
const lockSkillNames = Object.keys(lock.skills);
|
|
852
|
+
if (manifestSkills.length !== lockSkillNames.length) return false;
|
|
853
|
+
for (const [name, specifier] of manifestSkills){
|
|
854
|
+
const lockEntry = lock.skills[name];
|
|
855
|
+
if (!lockEntry) return false;
|
|
856
|
+
if (!isSpecifierCompatible(specifier, lockEntry.specifier)) return false;
|
|
857
|
+
}
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
447
860
|
async function ensureDir(dirPath) {
|
|
448
861
|
await mkdir(dirPath, {
|
|
449
862
|
recursive: true
|
|
@@ -459,12 +872,6 @@ async function replaceSymlink(target, linkPath) {
|
|
|
459
872
|
async function writeJson(filePath, value) {
|
|
460
873
|
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
461
874
|
}
|
|
462
|
-
async function linkSkill(rootDir, installDir, linkTarget, skillName) {
|
|
463
|
-
const absoluteTarget = node_path.join(rootDir, installDir, skillName);
|
|
464
|
-
const absoluteLink = node_path.join(rootDir, linkTarget, skillName);
|
|
465
|
-
await ensureDir(node_path.dirname(absoluteLink));
|
|
466
|
-
await replaceSymlink(absoluteTarget, absoluteLink);
|
|
467
|
-
}
|
|
468
875
|
async function readInstallState(rootDir) {
|
|
469
876
|
const filePath = node_path.join(rootDir, '.agents/skills/.skills-pm-install-state.json');
|
|
470
877
|
try {
|
|
@@ -479,6 +886,12 @@ async function writeInstallState(rootDir, value) {
|
|
|
479
886
|
const filePath = node_path.join(dirPath, '.skills-pm-install-state.json');
|
|
480
887
|
await writeJson(filePath, value);
|
|
481
888
|
}
|
|
889
|
+
async function linkSkill(rootDir, installDir, linkTarget, skillName) {
|
|
890
|
+
const absoluteTarget = node_path.join(rootDir, installDir, skillName);
|
|
891
|
+
const absoluteLink = node_path.join(rootDir, linkTarget, skillName);
|
|
892
|
+
await ensureDir(node_path.dirname(absoluteLink));
|
|
893
|
+
await replaceSymlink(absoluteTarget, absoluteLink);
|
|
894
|
+
}
|
|
482
895
|
async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath, installDir) {
|
|
483
896
|
const relativeSkillPath = sourcePath.replace(/^\//, '');
|
|
484
897
|
const absoluteSkillPath = node_path.join(sourceRoot, relativeSkillPath);
|
|
@@ -504,14 +917,24 @@ async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath,
|
|
|
504
917
|
}
|
|
505
918
|
const materializeGitSkill_execFileAsync = promisify(execFile);
|
|
506
919
|
async function checkoutCommit(checkoutRoot, commit) {
|
|
507
|
-
|
|
508
|
-
'
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
920
|
+
try {
|
|
921
|
+
await materializeGitSkill_execFileAsync('git', [
|
|
922
|
+
'checkout',
|
|
923
|
+
commit
|
|
924
|
+
], {
|
|
925
|
+
cwd: checkoutRoot
|
|
926
|
+
});
|
|
927
|
+
} catch (error) {
|
|
928
|
+
throw new GitError({
|
|
929
|
+
code: codes_ErrorCode.GIT_CHECKOUT_FAILED,
|
|
930
|
+
operation: 'checkout',
|
|
931
|
+
ref: commit,
|
|
932
|
+
message: `Failed to checkout commit ${commit}`,
|
|
933
|
+
cause: error
|
|
934
|
+
});
|
|
935
|
+
}
|
|
513
936
|
}
|
|
514
|
-
async function fetchCommitFallback(checkoutRoot, commit) {
|
|
937
|
+
async function fetchCommitFallback(checkoutRoot, commit, _repoUrl) {
|
|
515
938
|
try {
|
|
516
939
|
await materializeGitSkill_execFileAsync('git', [
|
|
517
940
|
'fetch',
|
|
@@ -554,18 +977,33 @@ async function fetchCommitFallback(checkoutRoot, commit) {
|
|
|
554
977
|
async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePath, installDir) {
|
|
555
978
|
const checkoutRoot = await mkdtemp(node_path.join(tmpdir(), 'skills-pm-git-checkout-'));
|
|
556
979
|
try {
|
|
557
|
-
|
|
558
|
-
'
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
980
|
+
try {
|
|
981
|
+
await materializeGitSkill_execFileAsync('git', [
|
|
982
|
+
'clone',
|
|
983
|
+
'--depth',
|
|
984
|
+
'1',
|
|
985
|
+
repoUrl,
|
|
986
|
+
checkoutRoot
|
|
987
|
+
]);
|
|
988
|
+
} catch (error) {
|
|
989
|
+
throw new GitError({
|
|
990
|
+
code: codes_ErrorCode.GIT_CLONE_FAILED,
|
|
991
|
+
operation: 'clone',
|
|
992
|
+
repoUrl,
|
|
993
|
+
message: `Failed to clone repository ${repoUrl}`,
|
|
994
|
+
cause: error
|
|
995
|
+
});
|
|
996
|
+
}
|
|
564
997
|
if (commit && 'HEAD' !== commit) try {
|
|
565
998
|
await checkoutCommit(checkoutRoot, commit);
|
|
566
|
-
} catch
|
|
567
|
-
|
|
568
|
-
|
|
999
|
+
} catch (checkoutError) {
|
|
1000
|
+
if (checkoutError instanceof GitError) try {
|
|
1001
|
+
await fetchCommitFallback(checkoutRoot, commit, repoUrl);
|
|
1002
|
+
await checkoutCommit(checkoutRoot, commit);
|
|
1003
|
+
} catch {
|
|
1004
|
+
throw checkoutError;
|
|
1005
|
+
}
|
|
1006
|
+
else throw checkoutError;
|
|
569
1007
|
}
|
|
570
1008
|
const skillDocPath = node_path.join(checkoutRoot, sourcePath.replace(/^\//, ''), 'SKILL.md');
|
|
571
1009
|
await readFile(skillDocPath, 'utf8');
|
|
@@ -666,17 +1104,22 @@ async function linkSkillsFromLock(rootDir, manifest, lockfile) {
|
|
|
666
1104
|
linked: Object.keys(lockfile.skills)
|
|
667
1105
|
};
|
|
668
1106
|
}
|
|
669
|
-
async function installSkills(rootDir) {
|
|
1107
|
+
async function installSkills(rootDir, options) {
|
|
670
1108
|
const manifest = await readSkillsManifest(rootDir);
|
|
671
1109
|
if (!manifest) return {
|
|
672
1110
|
status: 'skipped',
|
|
673
1111
|
reason: 'manifest-missing'
|
|
674
1112
|
};
|
|
675
1113
|
const currentLock = await readSkillsLock(rootDir);
|
|
676
|
-
|
|
1114
|
+
let lockfile;
|
|
1115
|
+
if (options?.frozenLockfile) {
|
|
1116
|
+
if (!currentLock) throw new Error('Lockfile is required in frozen mode but none was found');
|
|
1117
|
+
if (!isLockInSync(manifest, currentLock)) throw new Error('Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.');
|
|
1118
|
+
lockfile = currentLock;
|
|
1119
|
+
} else lockfile = await syncSkillsLock(rootDir, manifest, currentLock);
|
|
677
1120
|
await fetchSkillsFromLock(rootDir, manifest, lockfile);
|
|
678
1121
|
await linkSkillsFromLock(rootDir, manifest, lockfile);
|
|
679
|
-
await writeSkillsLock(rootDir, lockfile);
|
|
1122
|
+
if (!options?.frozenLockfile) await writeSkillsLock(rootDir, lockfile);
|
|
680
1123
|
return {
|
|
681
1124
|
status: 'installed',
|
|
682
1125
|
installed: Object.keys(lockfile.skills)
|
|
@@ -686,14 +1129,29 @@ function buildGitHubSpecifier(owner, repo, skillPath) {
|
|
|
686
1129
|
return `https://github.com/${owner}/${repo}.git#path:${skillPath}`;
|
|
687
1130
|
}
|
|
688
1131
|
async function addSingleSkill(cwd, specifier) {
|
|
689
|
-
|
|
1132
|
+
let normalized;
|
|
1133
|
+
try {
|
|
1134
|
+
normalized = normalizeSpecifier(specifier);
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
if (error instanceof ParseError) throw error;
|
|
1137
|
+
throw new ParseError({
|
|
1138
|
+
code: codes_ErrorCode.INVALID_SPECIFIER,
|
|
1139
|
+
message: `Invalid specifier: ${error.message}`,
|
|
1140
|
+
content: specifier,
|
|
1141
|
+
cause: error
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
690
1144
|
const existingManifest = await readSkillsManifest(cwd) ?? {
|
|
691
1145
|
installDir: '.agents/skills',
|
|
692
1146
|
linkTargets: [],
|
|
693
1147
|
skills: {}
|
|
694
1148
|
};
|
|
695
1149
|
const existing = existingManifest.skills[normalized.skillName];
|
|
696
|
-
if (existing && existing !== normalized.normalized) throw new
|
|
1150
|
+
if (existing && existing !== normalized.normalized) throw new SkillError({
|
|
1151
|
+
code: codes_ErrorCode.SKILL_EXISTS,
|
|
1152
|
+
skillName: normalized.skillName,
|
|
1153
|
+
message: `Skill ${normalized.skillName} already exists with a different specifier`
|
|
1154
|
+
});
|
|
697
1155
|
existingManifest.skills[normalized.skillName] = normalized.normalized;
|
|
698
1156
|
await writeSkillsManifest(cwd, existingManifest);
|
|
699
1157
|
const existingLock = await readSkillsLock(cwd);
|
|
@@ -731,7 +1189,11 @@ async function addCommand(options) {
|
|
|
731
1189
|
if (0 === skills.length) {
|
|
732
1190
|
spinner.stop(picocolors.red('No skills found'));
|
|
733
1191
|
__rspack_external__clack_prompts_3cae1695.outro(picocolors.red(`No valid skills found in ${source}`));
|
|
734
|
-
throw new
|
|
1192
|
+
throw new SkillError({
|
|
1193
|
+
code: codes_ErrorCode.SKILL_NOT_FOUND,
|
|
1194
|
+
skillName: source,
|
|
1195
|
+
message: `No skills found in ${source}`
|
|
1196
|
+
});
|
|
735
1197
|
}
|
|
736
1198
|
spinner.stop(`Found ${picocolors.green(String(skills.length))} skill${1 !== skills.length ? 's' : ''}`);
|
|
737
1199
|
const selected = await promptSkillSelection(skills);
|
|
@@ -754,10 +1216,21 @@ async function assertManifestMissing(cwd) {
|
|
|
754
1216
|
const filePath = node_path.join(cwd, 'skills.json');
|
|
755
1217
|
try {
|
|
756
1218
|
await access(filePath);
|
|
757
|
-
throw new
|
|
1219
|
+
throw new ManifestError({
|
|
1220
|
+
code: codes_ErrorCode.MANIFEST_EXISTS,
|
|
1221
|
+
filePath,
|
|
1222
|
+
message: 'skills.json already exists'
|
|
1223
|
+
});
|
|
758
1224
|
} catch (error) {
|
|
1225
|
+
if (error instanceof ManifestError) throw error;
|
|
759
1226
|
if ('ENOENT' === error.code) return;
|
|
760
|
-
throw
|
|
1227
|
+
throw new FileSystemError({
|
|
1228
|
+
code: codes_ErrorCode.FS_ERROR,
|
|
1229
|
+
operation: 'access',
|
|
1230
|
+
path: filePath,
|
|
1231
|
+
message: `Failed to check if manifest exists: ${error.message}`,
|
|
1232
|
+
cause: error
|
|
1233
|
+
});
|
|
761
1234
|
}
|
|
762
1235
|
}
|
|
763
1236
|
function createDefaultManifest() {
|
|
@@ -777,7 +1250,39 @@ async function initCommand(options, promptInit = promptInitManifestOptions) {
|
|
|
777
1250
|
return manifest;
|
|
778
1251
|
}
|
|
779
1252
|
async function installCommand(options) {
|
|
780
|
-
|
|
1253
|
+
const manifest = await readSkillsManifest(options.cwd);
|
|
1254
|
+
if (!manifest) throw new ManifestError({
|
|
1255
|
+
code: codes_ErrorCode.MANIFEST_NOT_FOUND,
|
|
1256
|
+
filePath: `${options.cwd}/skills.json`,
|
|
1257
|
+
message: 'No skills.json found in the current directory. Run "spm init" to create one.'
|
|
1258
|
+
});
|
|
1259
|
+
const currentLock = await readSkillsLock(options.cwd);
|
|
1260
|
+
if (options.frozenLockfile) {
|
|
1261
|
+
if (!currentLock) throw new ManifestError({
|
|
1262
|
+
code: codes_ErrorCode.LOCKFILE_NOT_FOUND,
|
|
1263
|
+
filePath: `${options.cwd}/skills-lock.yaml`,
|
|
1264
|
+
message: 'Lockfile is required in frozen mode but none was found. Run "spm install" first.'
|
|
1265
|
+
});
|
|
1266
|
+
if (!isLockInSync(manifest, currentLock)) throw new ManifestError({
|
|
1267
|
+
code: codes_ErrorCode.LOCKFILE_OUTDATED,
|
|
1268
|
+
filePath: `${options.cwd}/skills-lock.yaml`,
|
|
1269
|
+
message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
|
|
1270
|
+
});
|
|
1271
|
+
await fetchSkillsFromLock(options.cwd, manifest, currentLock);
|
|
1272
|
+
await linkSkillsFromLock(options.cwd, manifest, currentLock);
|
|
1273
|
+
return {
|
|
1274
|
+
status: 'installed',
|
|
1275
|
+
installed: Object.keys(currentLock.skills)
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
const lockfile = await syncSkillsLock(options.cwd, manifest, currentLock);
|
|
1279
|
+
await fetchSkillsFromLock(options.cwd, manifest, lockfile);
|
|
1280
|
+
await linkSkillsFromLock(options.cwd, manifest, lockfile);
|
|
1281
|
+
await writeSkillsLock(options.cwd, lockfile);
|
|
1282
|
+
return {
|
|
1283
|
+
status: 'installed',
|
|
1284
|
+
installed: Object.keys(lockfile.skills)
|
|
1285
|
+
};
|
|
781
1286
|
}
|
|
782
1287
|
function createEmptyResult() {
|
|
783
1288
|
return {
|
|
@@ -788,7 +1293,7 @@ function createEmptyResult() {
|
|
|
788
1293
|
failed: []
|
|
789
1294
|
};
|
|
790
1295
|
}
|
|
791
|
-
function createBaseLock(
|
|
1296
|
+
function createBaseLock(_cwd, currentLock) {
|
|
792
1297
|
if (currentLock) return {
|
|
793
1298
|
...currentLock,
|
|
794
1299
|
skills: {
|
|
@@ -804,10 +1309,18 @@ function createBaseLock(cwd, currentLock) {
|
|
|
804
1309
|
}
|
|
805
1310
|
async function updateCommand(options) {
|
|
806
1311
|
const manifest = await readSkillsManifest(options.cwd);
|
|
807
|
-
if (!manifest)
|
|
1312
|
+
if (!manifest) throw new ManifestError({
|
|
1313
|
+
code: codes_ErrorCode.MANIFEST_NOT_FOUND,
|
|
1314
|
+
filePath: `${options.cwd}/skills.json`,
|
|
1315
|
+
message: 'No skills.json found in the current directory. Run "spm init" to create one.'
|
|
1316
|
+
});
|
|
808
1317
|
const currentLock = await readSkillsLock(options.cwd);
|
|
809
1318
|
const targetSkills = options.skills ?? Object.keys(manifest.skills);
|
|
810
|
-
for (const skillName of targetSkills)if (!(skillName in manifest.skills)) throw new
|
|
1319
|
+
for (const skillName of targetSkills)if (!(skillName in manifest.skills)) throw new SkillError({
|
|
1320
|
+
code: codes_ErrorCode.SKILL_NOT_FOUND,
|
|
1321
|
+
skillName,
|
|
1322
|
+
message: `Unknown skill: ${skillName}`
|
|
1323
|
+
});
|
|
811
1324
|
const result = createEmptyResult();
|
|
812
1325
|
const candidateLock = createBaseLock(options.cwd, currentLock);
|
|
813
1326
|
candidateLock.installDir = manifest.installDir ?? '.agents/skills';
|
|
@@ -847,9 +1360,6 @@ async function updateCommand(options) {
|
|
|
847
1360
|
result.status = result.updated.length > 0 ? 'updated' : 'skipped';
|
|
848
1361
|
return result;
|
|
849
1362
|
}
|
|
850
|
-
var package_namespaceObject = {
|
|
851
|
-
rE: "0.2.0"
|
|
852
|
-
};
|
|
853
1363
|
function createHandlers(overrides) {
|
|
854
1364
|
return {
|
|
855
1365
|
addCommand: addCommand,
|
|
@@ -870,7 +1380,7 @@ async function runCli(argv, context = {}) {
|
|
|
870
1380
|
cli.help();
|
|
871
1381
|
cli.version(packageVersion);
|
|
872
1382
|
cli.showVersionOnExit = false;
|
|
873
|
-
cli.command('add [...positionals]').option('--skill <name>', 'Select a skill').action((positionals = [], options)=>{
|
|
1383
|
+
cli.command('add [...positionals]').option('--skill <name>', 'Select a skill').action(async (positionals = [], options)=>{
|
|
874
1384
|
const specifier = positionals[0];
|
|
875
1385
|
if (!specifier) throw new Error('Missing required specifier');
|
|
876
1386
|
return handlers.addCommand({
|
|
@@ -879,16 +1389,17 @@ async function runCli(argv, context = {}) {
|
|
|
879
1389
|
skill: options.skill
|
|
880
1390
|
});
|
|
881
1391
|
});
|
|
882
|
-
cli.command('install [...args]').action(()=>handlers.installCommand({
|
|
883
|
-
cwd
|
|
1392
|
+
cli.command('install [...args]').option('--frozen-lockfile', 'Fail if lockfile is out of sync').action(async (_args, options)=>handlers.installCommand({
|
|
1393
|
+
cwd,
|
|
1394
|
+
frozenLockfile: options.frozenLockfile
|
|
884
1395
|
}));
|
|
885
|
-
cli.command('update [...skills]').action((skills = [])=>handlers.updateCommand({
|
|
1396
|
+
cli.command('update [...skills]').action(async (skills = [])=>handlers.updateCommand({
|
|
886
1397
|
cwd,
|
|
887
1398
|
skills: skills.length > 0 ? skills : void 0
|
|
888
1399
|
}));
|
|
889
1400
|
cli.command('init [...args]', '', {
|
|
890
1401
|
allowUnknownOptions: true
|
|
891
|
-
}).option('--yes [value]', 'Skip prompts and write defaults').action((args = [], options)=>{
|
|
1402
|
+
}).option('--yes [value]', 'Skip prompts and write defaults').action(async (args = [], options)=>{
|
|
892
1403
|
if (args.length > 0) throw new Error('init does not accept positional arguments');
|
|
893
1404
|
for (const key of Object.keys(options))if ('--' !== key) {
|
|
894
1405
|
if ('yes' !== key) throw new Error(`Unknown flag for init: --${formatFlagName(key)}`);
|
|
@@ -907,6 +1418,14 @@ async function runCli(argv, context = {}) {
|
|
|
907
1418
|
if (argv.length <= 2) return void cli.outputHelp();
|
|
908
1419
|
if (globalOptions.help || globalOptions.h) return;
|
|
909
1420
|
if (!cli.matchedCommand) throw new Error(`Unknown command: ${argv[2]}`);
|
|
910
|
-
|
|
1421
|
+
try {
|
|
1422
|
+
return await cli.runMatchedCommand();
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
if (error instanceof SpmError) {
|
|
1425
|
+
const enhancedError = new Error(formatErrorForDisplay(error));
|
|
1426
|
+
throw enhancedError;
|
|
1427
|
+
}
|
|
1428
|
+
throw error;
|
|
1429
|
+
}
|
|
911
1430
|
}
|
|
912
|
-
export { addCommand, cloneAndDiscover, discoverSkillsInDir, fetchSkillsFromLock, initCommand, installCommand, installSkills, installStageHooks, linkSkillsFromLock, listRepoSkills, parseGitHubUrl, parseOwnerRepo, resolveLockEntry, runCli, updateCommand };
|
|
1431
|
+
export { FileSystemError, GitError, ManifestError, NetworkError, ParseError, SkillError, SpmError, addCommand, cloneAndDiscover, codes_ErrorCode as ErrorCode, convertNodeError, discoverSkillsInDir, fetchSkillsFromLock, formatErrorForDisplay, getExitCode, initCommand, installCommand, installSkills, installStageHooks, isLockInSync, isSpmError, linkSkillsFromLock, listRepoSkills, normalizeSpecifier, parseGitHubUrl, parseOwnerRepo, parseSpecifier, readSkillsLock, readSkillsManifest, resolveLockEntry, runCli, updateCommand, writeSkillsLock, writeSkillsManifest };
|