skills-package-manager 0.7.0 → 0.9.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/README.md CHANGED
@@ -11,6 +11,8 @@ npx skills-package-manager --help
11
11
  npx skills-package-manager --version
12
12
  npx skills-package-manager add <specifier> [--skill <name>]
13
13
  npx skills-package-manager install
14
+ npx skills-package-manager patch <skill>
15
+ npx skills-package-manager patch-commit <edit-dir>
14
16
  npx skills-package-manager update [skill...]
15
17
  npx skills-package-manager init [--yes]
16
18
  ```
@@ -104,6 +106,40 @@ npx skills-package-manager install
104
106
 
105
107
  This resolves each skill from its specifier, materializes it into `installDir` (default `.agents/skills/`), and creates symlinks for each `linkTarget`.
106
108
  When `selfSkill` is `true`, `npx skills-package-manager install` also installs the bundled `skills-package-manager-cli` skill so users get guidance for `skills.json`, `skills-lock.yaml`, and `npx skills-package-manager` commands. This helper skill is not written to `skills-lock.yaml`.
109
+ If `patchedSkills` contains an entry for a skill, the corresponding patch file is applied after the skill is materialized.
110
+
111
+ ### `npx skills-package-manager patch`
112
+
113
+ Prepare a skill for patching without changing the manifest yet:
114
+
115
+ ```bash
116
+ npx skills-package-manager patch hello-skill
117
+ npx skills-package-manager patch hello-skill --edit-dir ./tmp/hello-skill
118
+ ```
119
+
120
+ Behavior:
121
+
122
+ - Resolves the currently locked content for the target skill
123
+ - Extracts an editable copy into a temporary directory by default
124
+ - Reapplies any committed patch for that skill unless `--ignore-existing` is passed
125
+ - Writes patch edit metadata so `patch-commit` can generate a new patch file later
126
+
127
+ ### `npx skills-package-manager patch-commit`
128
+
129
+ Commit an edited patch directory back into the project:
130
+
131
+ ```bash
132
+ npx skills-package-manager patch-commit /tmp/skills-pm-patch-hello-skill-12345
133
+ npx skills-package-manager patch-commit ./tmp/hello-skill --patches-dir ./custom-patches
134
+ ```
135
+
136
+ Behavior:
137
+
138
+ - Compares the edited directory with the original resolved skill content
139
+ - Writes a unified diff patch file to `patches/<skill>.patch` by default
140
+ - Updates `skills.json` through the `patchedSkills` field
141
+ - Updates `skills-lock.yaml` with patch path and digest metadata
142
+ - Reinstalls and relinks the patched skill so the working tree reflects the committed patch
107
143
 
108
144
  ### `npx skills-package-manager update`
109
145
 
@@ -172,10 +208,11 @@ link: link:<path-to-skill-dir>
172
208
  src/
173
209
  ├── bin/ # CLI entry points
174
210
  ├── cli/ # CLI runner and interactive prompts
175
- ├── commands/ # add, install command implementations
211
+ ├── commands/ # add, install, patch command implementations
176
212
  ├── config/ # skills.json / skills-lock.yaml read/write
177
213
  ├── github/ # Git clone + skill discovery (listSkills)
178
214
  ├── install/ # Skill materialization, linking, pruning
215
+ ├── patches/ # Patch edit state, diff generation, patch application
179
216
  ├── specifiers/ # Specifier parsing and normalization
180
217
  └── utils/ # Hashing, filesystem helpers
181
218
  ```
@@ -0,0 +1,488 @@
1
+ export declare function addCommand(options: AddCommandOptions): Promise<{
2
+ skillName: string;
3
+ specifier: string;
4
+ } | {
5
+ skillName: string;
6
+ specifier: string;
7
+ }[]>;
8
+
9
+ export declare type AddCommandOptions = {
10
+ cwd: string;
11
+ specifier: string;
12
+ skill?: string;
13
+ global?: boolean;
14
+ yes?: boolean;
15
+ agent?: string[];
16
+ };
17
+
18
+ /**
19
+ * Clone a git repo (shallow) into a temp dir, discover skills, then clean up.
20
+ */
21
+ export declare function cloneAndDiscover(gitUrl: string, ref?: string): Promise<{
22
+ skills: SkillInfo[];
23
+ cleanup: () => Promise<void>;
24
+ }>;
25
+
26
+ /**
27
+ * Converts a Node.js file system error to an appropriate SPM error type
28
+ */
29
+ export declare function convertNodeError(error: NodeJS.ErrnoException, context: {
30
+ operation: string;
31
+ path: string;
32
+ }): FileSystemError;
33
+
34
+ export declare function createInstallProgressReporter(options?: ProgressReporterOptions): InstallProgressReporter;
35
+
36
+ /**
37
+ * Discover skills in a local directory by scanning for SKILL.md files.
38
+ * Recursively scans the repo tree for directories containing SKILL.md.
39
+ */
40
+ export declare function discoverSkillsInDir(baseDir: string): Promise<SkillInfo[]>;
41
+
42
+ /**
43
+ * Error codes for SPM (Skills Package Manager)
44
+ * Inspired by pnpm's error code system
45
+ */
46
+ export declare enum ErrorCode {
47
+ FILE_NOT_FOUND = "ENOENT",
48
+ PERMISSION_DENIED = "EACCES",
49
+ FILE_EXISTS = "EEXIST",
50
+ FS_ERROR = "EFS",
51
+ GIT_CLONE_FAILED = "EGITCLONE",
52
+ GIT_FETCH_FAILED = "EGITFETCH",
53
+ GIT_CHECKOUT_FAILED = "EGITCHECKOUT",
54
+ GIT_REF_NOT_FOUND = "EGITREF",
55
+ GIT_NOT_INSTALLED = "EGITNOTFOUND",
56
+ PARSE_ERROR = "EPARSE",
57
+ JSON_PARSE_ERROR = "EJSONPARSE",
58
+ YAML_PARSE_ERROR = "EYAMLPARSE",
59
+ INVALID_SPECIFIER = "EINVALIDSPEC",
60
+ MANIFEST_NOT_FOUND = "EMANIFEST",
61
+ LOCKFILE_NOT_FOUND = "ELOCKFILE",
62
+ LOCKFILE_OUTDATED = "ELOCKOUTDATED",
63
+ MANIFEST_EXISTS = "EMANIFESTEXISTS",
64
+ MANIFEST_VALIDATION_ERROR = "EMANIFESTVAL",
65
+ NETWORK_ERROR = "ENETWORK",
66
+ REPO_NOT_FOUND = "EREPONOTFOUND",
67
+ INSTALL_ERROR = "EINSTALL",
68
+ UNKNOWN_ERROR = "EUNKNOWN",
69
+ NOT_IMPLEMENTED = "ENOTIMPL",
70
+ VALIDATION_ERROR = "EVALIDATION",
71
+ SKILL_NOT_FOUND = "ESKILLNOTFOUND",
72
+ SKILL_EXISTS = "ESKILLEXISTS"
73
+ }
74
+
75
+ export declare function expandSkillsManifest(_rootDir: string, manifest: SkillsManifest): Promise<NormalizedSkillsManifest>;
76
+
77
+ export declare function fetchSkillsFromLock(rootDir: string, manifest: SkillsManifest, lockfile: SkillsLock, options?: {
78
+ onProgress?: InstallProgressListener;
79
+ }): Promise<{
80
+ readonly status: "skipped";
81
+ readonly reason: "up-to-date";
82
+ readonly fetched?: undefined;
83
+ } | {
84
+ readonly status: "fetched";
85
+ readonly fetched: string[];
86
+ reason?: undefined;
87
+ }>;
88
+
89
+ /**
90
+ * Error thrown when file system operations fail
91
+ */
92
+ export declare class FileSystemError extends SpmError {
93
+ readonly operation: string;
94
+ readonly path: string;
95
+ constructor(options: {
96
+ code: ErrorCode.FILE_NOT_FOUND | ErrorCode.PERMISSION_DENIED | ErrorCode.FILE_EXISTS | ErrorCode.FS_ERROR;
97
+ operation: 'read' | 'write' | 'access' | 'mkdir' | 'rm' | 'copy' | 'symlink' | string;
98
+ path: string;
99
+ message?: string;
100
+ cause?: Error;
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Formats an error for display to the user
106
+ * Provides helpful context for known error types
107
+ */
108
+ export declare function formatErrorForDisplay(error: unknown): string;
109
+
110
+ /**
111
+ * Gets the exit code for an error
112
+ * Returns 1 for general errors, specific codes for known error types
113
+ */
114
+ export declare function getExitCode(error: unknown): number;
115
+
116
+ /**
117
+ * Error thrown when git operations fail
118
+ */
119
+ export declare class GitError extends SpmError {
120
+ readonly operation: string;
121
+ readonly repoUrl?: string;
122
+ readonly ref?: string;
123
+ constructor(options: {
124
+ code: ErrorCode.GIT_CLONE_FAILED | ErrorCode.GIT_FETCH_FAILED | ErrorCode.GIT_CHECKOUT_FAILED | ErrorCode.GIT_REF_NOT_FOUND | ErrorCode.GIT_NOT_INSTALLED;
125
+ operation: 'clone' | 'fetch' | 'checkout' | 'ls-remote' | 'rev-parse' | string;
126
+ repoUrl?: string;
127
+ ref?: string;
128
+ message?: string;
129
+ cause?: Error;
130
+ });
131
+ }
132
+
133
+ export declare function initCommand(options: InitCommandOptions, promptInit?: InitPrompter): Promise<SkillsManifest>;
134
+
135
+ export declare type InitCommandOptions = {
136
+ cwd: string;
137
+ yes?: boolean;
138
+ };
139
+
140
+ declare type InitPrompter = () => Promise<InitPromptResult>;
141
+
142
+ declare type InitPromptResult = {
143
+ installDir: string;
144
+ linkTargets: string[];
145
+ };
146
+
147
+ export declare function installCommand(options: InstallCommandOptions): Promise<{
148
+ status: "skipped";
149
+ reason: string;
150
+ installed?: undefined;
151
+ } | {
152
+ status: "installed";
153
+ installed: string[];
154
+ reason?: undefined;
155
+ }>;
156
+
157
+ export declare type InstallCommandOptions = {
158
+ cwd: string;
159
+ frozenLockfile?: boolean;
160
+ onProgress?: InstallProgressListener;
161
+ };
162
+
163
+ declare type InstallPhase = 'resolving' | 'fetching' | 'linking' | 'finalizing' | 'done';
164
+
165
+ export declare type InstallProgressEvent = {
166
+ type: 'resolved';
167
+ skillName: string;
168
+ } | {
169
+ type: 'reused';
170
+ skillName: string;
171
+ } | {
172
+ type: 'downloaded';
173
+ skillName: string;
174
+ } | {
175
+ type: 'added';
176
+ skillName: string;
177
+ } | {
178
+ type: 'installed';
179
+ skillName: string;
180
+ };
181
+
182
+ export declare type InstallProgressListener = (event: InstallProgressEvent) => void;
183
+
184
+ declare type InstallProgressReporter = {
185
+ start(total: number): void;
186
+ setPhase(phase: Exclude<InstallPhase, 'done'>): void;
187
+ onProgress(event: InstallProgressEvent): void;
188
+ complete(): void;
189
+ fail(): void;
190
+ };
191
+
192
+ export declare function installSkills(rootDir: string, options?: {
193
+ frozenLockfile?: boolean;
194
+ onProgress?: InstallProgressListener;
195
+ }): Promise<{
196
+ readonly status: "skipped";
197
+ readonly reason: "manifest-missing";
198
+ installed?: undefined;
199
+ } | {
200
+ readonly status: "installed";
201
+ readonly installed: string[];
202
+ reason?: undefined;
203
+ }>;
204
+
205
+ export declare const installStageHooks: {
206
+ beforeFetch: (_rootDir: string, _manifest: SkillsManifest, _lockfile: SkillsLock) => Promise<void>;
207
+ };
208
+
209
+ export declare function isLockInSync(rootDir: string, manifest: NormalizedSkillsManifest, lock: SkillsLock | null, manifestStat?: ManifestStat | null, installState?: {
210
+ manifestStat?: ManifestStat;
211
+ } | null): Promise<boolean>;
212
+
213
+ /**
214
+ * Checks if an error is a known SPM error
215
+ */
216
+ export declare function isSpmError(error: unknown): error is SpmError;
217
+
218
+ export declare function linkSkillsFromLock(rootDir: string, manifest: SkillsManifest, lockfile: SkillsLock, options?: {
219
+ onProgress?: InstallProgressListener;
220
+ }): Promise<{
221
+ readonly status: "linked";
222
+ readonly linked: string[];
223
+ }>;
224
+
225
+ /**
226
+ * List skills in a GitHub repo by cloning and scanning.
227
+ * This avoids GitHub API rate limits.
228
+ */
229
+ export declare function listRepoSkills(owner: string, repo: string, ref?: string): Promise<SkillInfo[]>;
230
+
231
+ /**
232
+ * Error thrown when manifest or lockfile operations fail
233
+ */
234
+ export declare class ManifestError extends SpmError {
235
+ readonly filePath: string;
236
+ constructor(options: {
237
+ code: ErrorCode.MANIFEST_NOT_FOUND | ErrorCode.LOCKFILE_NOT_FOUND | ErrorCode.LOCKFILE_OUTDATED | ErrorCode.MANIFEST_EXISTS | ErrorCode.MANIFEST_VALIDATION_ERROR;
238
+ filePath: string;
239
+ message?: string;
240
+ cause?: Error;
241
+ });
242
+ }
243
+
244
+ declare interface ManifestStat {
245
+ mtimeMs: number;
246
+ size: number;
247
+ }
248
+
249
+ /**
250
+ * Error thrown when network operations fail
251
+ */
252
+ export declare class NetworkError extends SpmError {
253
+ readonly url?: string;
254
+ constructor(options: {
255
+ code: ErrorCode.NETWORK_ERROR | ErrorCode.REPO_NOT_FOUND;
256
+ url?: string;
257
+ message: string;
258
+ cause?: Error;
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Skills manifest output type after validation/default application.
264
+ * Use this for normalized manifests returned from reads/parsing.
265
+ */
266
+ declare type NormalizedSkillsManifest = {
267
+ $schema?: string;
268
+ installDir: string;
269
+ linkTargets: string[];
270
+ selfSkill?: boolean;
271
+ skills: Record<string, string>;
272
+ patchedSkills?: Record<string, string>;
273
+ };
274
+
275
+ export declare type NormalizedSpecifier = {
276
+ type: 'git' | 'link' | 'file' | 'npm';
277
+ source: string;
278
+ ref: string | null;
279
+ path: string;
280
+ normalized: string;
281
+ skillName: string;
282
+ };
283
+
284
+ export declare function normalizeSkillsManifest(manifest: Partial<SkillsManifest>): NormalizedSkillsManifest;
285
+
286
+ export declare function normalizeSpecifier(specifier: string): NormalizedSpecifier;
287
+
288
+ /**
289
+ * Error thrown when parsing fails (JSON, YAML, specifiers)
290
+ */
291
+ export declare class ParseError extends SpmError {
292
+ readonly filePath?: string;
293
+ readonly content?: string;
294
+ constructor(options: {
295
+ code: ErrorCode.PARSE_ERROR | ErrorCode.JSON_PARSE_ERROR | ErrorCode.YAML_PARSE_ERROR | ErrorCode.INVALID_SPECIFIER;
296
+ filePath?: string;
297
+ content?: string;
298
+ message: string;
299
+ cause?: Error;
300
+ });
301
+ }
302
+
303
+ export declare function parseGitHubUrl(input: string): {
304
+ owner: string;
305
+ repo: string;
306
+ } | null;
307
+
308
+ export declare function parseOwnerRepo(input: string): {
309
+ owner: string;
310
+ repo: string;
311
+ } | null;
312
+
313
+ export declare function parseSpecifier(specifier: string): {
314
+ sourcePart: string;
315
+ ref: string | null;
316
+ path: string;
317
+ };
318
+
319
+ export declare function patchCommand(options: PatchCommandOptions): Promise<PatchCommandResult>;
320
+
321
+ export declare type PatchCommandOptions = {
322
+ cwd: string;
323
+ skillName: string;
324
+ editDir?: string;
325
+ ignoreExisting?: boolean;
326
+ };
327
+
328
+ export declare type PatchCommandResult = {
329
+ status: 'patched';
330
+ skillName: string;
331
+ editDir: string;
332
+ originalSpecifier: string;
333
+ };
334
+
335
+ export declare function patchCommitCommand(options: PatchCommitCommandOptions): Promise<PatchCommitCommandResult>;
336
+
337
+ export declare type PatchCommitCommandOptions = {
338
+ cwd: string;
339
+ editDir: string;
340
+ patchesDir?: string;
341
+ };
342
+
343
+ export declare type PatchCommitCommandResult = {
344
+ status: 'patched';
345
+ skillName: string;
346
+ patchFile: string;
347
+ };
348
+
349
+ declare type ProgressReporterOptions = {
350
+ isTTY?: boolean;
351
+ write?: (text: string) => void;
352
+ info?: (text: string) => void;
353
+ };
354
+
355
+ export declare function readSkillsLock(rootDir: string): Promise<SkillsLock | null>;
356
+
357
+ export declare function readSkillsManifest(rootDir: string): Promise<NormalizedSkillsManifest | null>;
358
+
359
+ export declare function resolveLockEntry(cwd: string, specifier: string, skillName?: string): Promise<{
360
+ skillName: string;
361
+ entry: SkillsLockEntry;
362
+ }>;
363
+
364
+ export declare function runCli(argv: string[], context?: {
365
+ cwd?: string;
366
+ }): Promise<unknown>;
367
+
368
+ /**
369
+ * Error thrown when a skill operation fails
370
+ */
371
+ export declare class SkillError extends SpmError {
372
+ readonly skillName: string;
373
+ constructor(options: {
374
+ code: ErrorCode.SKILL_NOT_FOUND | ErrorCode.SKILL_EXISTS | ErrorCode.VALIDATION_ERROR;
375
+ skillName: string;
376
+ message?: string;
377
+ cause?: Error;
378
+ });
379
+ }
380
+
381
+ export declare type SkillInfo = {
382
+ name: string;
383
+ description: string;
384
+ path: string;
385
+ };
386
+
387
+ export declare type SkillsLock = {
388
+ lockfileVersion: '0.1';
389
+ installDir: string;
390
+ linkTargets: string[];
391
+ skills: Record<string, SkillsLockEntry>;
392
+ };
393
+
394
+ export declare type SkillsLockEntry = {
395
+ specifier: string;
396
+ resolution: {
397
+ type: 'link';
398
+ path: string;
399
+ } | {
400
+ type: 'file';
401
+ tarball: string;
402
+ path: string;
403
+ } | {
404
+ type: 'git';
405
+ url: string;
406
+ commit: string;
407
+ path: string;
408
+ } | {
409
+ type: 'npm';
410
+ packageName: string;
411
+ version: string;
412
+ path: string;
413
+ tarball: string;
414
+ integrity?: string;
415
+ registry?: string;
416
+ };
417
+ digest: string;
418
+ patch?: {
419
+ path: string;
420
+ digest: string;
421
+ };
422
+ };
423
+
424
+ /**
425
+ * Skills manifest input type used for authoring/writing manifests.
426
+ * This preserves optionality for fields with defaults.
427
+ */
428
+ export declare type SkillsManifest = {
429
+ $schema?: string;
430
+ installDir?: string;
431
+ linkTargets?: string[];
432
+ selfSkill?: boolean;
433
+ skills?: Record<string, string>;
434
+ patchedSkills?: Record<string, string>;
435
+ };
436
+
437
+ /**
438
+ * Base error class for SPM (Skills Package Manager)
439
+ * All custom errors should extend this class
440
+ */
441
+ export declare class SpmError extends Error {
442
+ readonly code: ErrorCode;
443
+ readonly cause?: Error;
444
+ readonly context: Record<string, unknown>;
445
+ constructor(options: {
446
+ code: ErrorCode;
447
+ message: string;
448
+ cause?: Error;
449
+ context?: Record<string, unknown>;
450
+ });
451
+ /**
452
+ * Returns a formatted string representation of the error
453
+ */
454
+ toString(): string;
455
+ /**
456
+ * Returns a detailed object representation for logging/debugging
457
+ */
458
+ toJSON(): Record<string, unknown>;
459
+ }
460
+
461
+ export declare function updateCommand(options: UpdateCommandOptions): Promise<UpdateCommandResult>;
462
+
463
+ export declare type UpdateCommandOptions = {
464
+ cwd: string;
465
+ skills?: string[];
466
+ };
467
+
468
+ export declare type UpdateCommandResult = {
469
+ status: 'updated' | 'skipped' | 'failed';
470
+ updated: string[];
471
+ unchanged: string[];
472
+ skipped: Array<{
473
+ name: string;
474
+ reason: 'link-specifier';
475
+ }>;
476
+ failed: Array<{
477
+ name: string;
478
+ reason: string;
479
+ }>;
480
+ };
481
+
482
+ export declare function withBundledSelfSkillLock(rootDir: string, manifest: SkillsManifest, lockfile: SkillsLock): Promise<SkillsLock>;
483
+
484
+ export declare function writeSkillsLock(rootDir: string, lockfile: SkillsLock): Promise<void>;
485
+
486
+ export declare function writeSkillsManifest(rootDir: string, manifest: SkillsManifest): Promise<void>;
487
+
488
+ export { }