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.
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from "../dist/index.js";
3
- runCli(process.argv).catch((error)=>{
4
- console.error(error instanceof Error ? error.message : error);
5
- process.exitCode = 1;
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 "../dist/index.js";
3
- runCli(process.argv).catch((error)=>{
4
- console.error(error instanceof Error ? error.message : error);
5
- process.exitCode = 1;
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 { cac } from "cac";
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
- return yaml.parse(raw);
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
- const json = JSON.parse(raw);
26
- return {
27
- installDir: json.installDir ?? '.agents/skills',
28
- linkTargets: json.linkTargets ?? [],
29
- skills: json.skills ?? {}
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 Error('Invalid specifier: multiple # fragments are not supported');
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 Error('Specifier source is required');
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
- const parsed = parseSpecifier(specifier);
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 Error(`Unable to resolve git ref ${target} for ${url}`);
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
- const normalized = normalizeSpecifier(specifier);
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: normalized.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: normalized.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 Error(`Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`);
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, existingLock) {
170
- const nextSkills = {};
171
- for (const specifier of Object.values(manifest.skills)){
172
- const { skillName, entry } = await resolveLockEntry(cwd, specifier);
173
- nextSkills[skillName] = entry;
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
- await writeFile(filePath, yaml.stringify(lockfile), 'utf8');
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
- await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
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
- const UNIVERSAL_AGENT_NAMES = [
347
- 'Amp',
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
- installDir,
444
- linkTargets: selected
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
- await materializeGitSkill_execFileAsync('git', [
508
- 'checkout',
509
- commit
510
- ], {
511
- cwd: checkoutRoot
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
- await materializeGitSkill_execFileAsync('git', [
558
- 'clone',
559
- '--depth',
560
- '1',
561
- repoUrl,
562
- checkoutRoot
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
- await fetchCommitFallback(checkoutRoot, commit);
568
- await checkoutCommit(checkoutRoot, commit);
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
- const lockfile = await syncSkillsLock(rootDir, manifest, currentLock);
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
- const normalized = normalizeSpecifier(specifier);
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 Error(`Skill ${normalized.skillName} already exists with a different specifier`);
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 Error(`No skills found in ${source}`);
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 Error('skills.json already exists');
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 error;
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
- return installSkills(options.cwd);
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(cwd, currentLock) {
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) return createEmptyResult();
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 Error(`Unknown skill: ${skillName}`);
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
- return cli.runMatchedCommand();
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skills-package-manager",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",