skills-package-manager 0.7.0 → 0.8.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 +38 -1
- package/dist/index.d.ts +466 -0
- package/dist/index.js +437 -28
- package/package.json +3 -1
- package/skills.schema.json +10 -0
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
|
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
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
|
+
UNKNOWN_ERROR = "EUNKNOWN",
|
|
68
|
+
NOT_IMPLEMENTED = "ENOTIMPL",
|
|
69
|
+
VALIDATION_ERROR = "EVALIDATION",
|
|
70
|
+
SKILL_NOT_FOUND = "ESKILLNOTFOUND",
|
|
71
|
+
SKILL_EXISTS = "ESKILLEXISTS"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export declare function expandSkillsManifest(_rootDir: string, manifest: SkillsManifest): Promise<NormalizedSkillsManifest>;
|
|
75
|
+
|
|
76
|
+
export declare function fetchSkillsFromLock(rootDir: string, manifest: SkillsManifest, lockfile: SkillsLock, options?: {
|
|
77
|
+
onProgress?: InstallProgressListener;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
readonly status: "skipped";
|
|
80
|
+
readonly reason: "up-to-date";
|
|
81
|
+
readonly fetched?: undefined;
|
|
82
|
+
} | {
|
|
83
|
+
readonly status: "fetched";
|
|
84
|
+
readonly fetched: string[];
|
|
85
|
+
readonly reason?: undefined;
|
|
86
|
+
}>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Error thrown when file system operations fail
|
|
90
|
+
*/
|
|
91
|
+
export declare class FileSystemError extends SpmError {
|
|
92
|
+
readonly operation: string;
|
|
93
|
+
readonly path: string;
|
|
94
|
+
constructor(options: {
|
|
95
|
+
code: ErrorCode.FILE_NOT_FOUND | ErrorCode.PERMISSION_DENIED | ErrorCode.FILE_EXISTS | ErrorCode.FS_ERROR;
|
|
96
|
+
operation: 'read' | 'write' | 'access' | 'mkdir' | 'rm' | 'copy' | 'symlink' | string;
|
|
97
|
+
path: string;
|
|
98
|
+
message?: string;
|
|
99
|
+
cause?: Error;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Formats an error for display to the user
|
|
105
|
+
* Provides helpful context for known error types
|
|
106
|
+
*/
|
|
107
|
+
export declare function formatErrorForDisplay(error: unknown): string;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Gets the exit code for an error
|
|
111
|
+
* Returns 1 for general errors, specific codes for known error types
|
|
112
|
+
*/
|
|
113
|
+
export declare function getExitCode(error: unknown): number;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Error thrown when git operations fail
|
|
117
|
+
*/
|
|
118
|
+
export declare class GitError extends SpmError {
|
|
119
|
+
readonly operation: string;
|
|
120
|
+
readonly repoUrl?: string;
|
|
121
|
+
readonly ref?: string;
|
|
122
|
+
constructor(options: {
|
|
123
|
+
code: ErrorCode.GIT_CLONE_FAILED | ErrorCode.GIT_FETCH_FAILED | ErrorCode.GIT_CHECKOUT_FAILED | ErrorCode.GIT_REF_NOT_FOUND | ErrorCode.GIT_NOT_INSTALLED;
|
|
124
|
+
operation: 'clone' | 'fetch' | 'checkout' | 'ls-remote' | 'rev-parse' | string;
|
|
125
|
+
repoUrl?: string;
|
|
126
|
+
ref?: string;
|
|
127
|
+
message?: string;
|
|
128
|
+
cause?: Error;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export declare function initCommand(options: InitCommandOptions, promptInit?: InitPrompter): Promise<SkillsManifest>;
|
|
133
|
+
|
|
134
|
+
export declare type InitCommandOptions = {
|
|
135
|
+
cwd: string;
|
|
136
|
+
yes?: boolean;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
declare type InitPrompter = () => Promise<InitPromptResult>;
|
|
140
|
+
|
|
141
|
+
declare type InitPromptResult = {
|
|
142
|
+
installDir: string;
|
|
143
|
+
linkTargets: string[];
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export declare function installCommand(options: InstallCommandOptions): Promise<{
|
|
147
|
+
readonly status: "installed";
|
|
148
|
+
readonly installed: string[];
|
|
149
|
+
}>;
|
|
150
|
+
|
|
151
|
+
export declare type InstallCommandOptions = {
|
|
152
|
+
cwd: string;
|
|
153
|
+
frozenLockfile?: boolean;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
declare type InstallPhase = 'resolving' | 'fetching' | 'linking' | 'finalizing' | 'done';
|
|
157
|
+
|
|
158
|
+
export declare type InstallProgressEvent = {
|
|
159
|
+
type: 'resolved';
|
|
160
|
+
skillName: string;
|
|
161
|
+
} | {
|
|
162
|
+
type: 'added';
|
|
163
|
+
skillName: string;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'installed';
|
|
166
|
+
skillName: string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export declare type InstallProgressListener = (event: InstallProgressEvent) => void;
|
|
170
|
+
|
|
171
|
+
declare type InstallProgressReporter = {
|
|
172
|
+
start(total: number): void;
|
|
173
|
+
setPhase(phase: Exclude<InstallPhase, 'done'>): void;
|
|
174
|
+
onProgress(event: InstallProgressEvent): void;
|
|
175
|
+
complete(): void;
|
|
176
|
+
fail(): void;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export declare function installSkills(rootDir: string, options?: {
|
|
180
|
+
frozenLockfile?: boolean;
|
|
181
|
+
onProgress?: InstallProgressListener;
|
|
182
|
+
}): Promise<{
|
|
183
|
+
readonly status: "skipped";
|
|
184
|
+
readonly reason: "manifest-missing";
|
|
185
|
+
readonly installed?: undefined;
|
|
186
|
+
} | {
|
|
187
|
+
readonly status: "installed";
|
|
188
|
+
readonly installed: string[];
|
|
189
|
+
readonly reason?: undefined;
|
|
190
|
+
}>;
|
|
191
|
+
|
|
192
|
+
export declare const installStageHooks: {
|
|
193
|
+
beforeFetch: (_rootDir: string, _manifest: SkillsManifest, _lockfile: SkillsLock) => Promise<void>;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export declare function isLockInSync(rootDir: string, manifest: NormalizedSkillsManifest, lock: SkillsLock | null): Promise<boolean>;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Checks if an error is a known SPM error
|
|
200
|
+
*/
|
|
201
|
+
export declare function isSpmError(error: unknown): error is SpmError;
|
|
202
|
+
|
|
203
|
+
export declare function linkSkillsFromLock(rootDir: string, manifest: SkillsManifest, lockfile: SkillsLock, options?: {
|
|
204
|
+
onProgress?: InstallProgressListener;
|
|
205
|
+
}): Promise<{
|
|
206
|
+
readonly status: "linked";
|
|
207
|
+
readonly linked: string[];
|
|
208
|
+
}>;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* List skills in a GitHub repo by cloning and scanning.
|
|
212
|
+
* This avoids GitHub API rate limits.
|
|
213
|
+
*/
|
|
214
|
+
export declare function listRepoSkills(owner: string, repo: string, ref?: string): Promise<SkillInfo[]>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Error thrown when manifest or lockfile operations fail
|
|
218
|
+
*/
|
|
219
|
+
export declare class ManifestError extends SpmError {
|
|
220
|
+
readonly filePath: string;
|
|
221
|
+
constructor(options: {
|
|
222
|
+
code: ErrorCode.MANIFEST_NOT_FOUND | ErrorCode.LOCKFILE_NOT_FOUND | ErrorCode.LOCKFILE_OUTDATED | ErrorCode.MANIFEST_EXISTS | ErrorCode.MANIFEST_VALIDATION_ERROR;
|
|
223
|
+
filePath: string;
|
|
224
|
+
message?: string;
|
|
225
|
+
cause?: Error;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Error thrown when network operations fail
|
|
231
|
+
*/
|
|
232
|
+
export declare class NetworkError extends SpmError {
|
|
233
|
+
readonly url?: string;
|
|
234
|
+
constructor(options: {
|
|
235
|
+
code: ErrorCode.NETWORK_ERROR | ErrorCode.REPO_NOT_FOUND;
|
|
236
|
+
url?: string;
|
|
237
|
+
message: string;
|
|
238
|
+
cause?: Error;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Skills manifest output type after validation/default application.
|
|
244
|
+
* Use this for normalized manifests returned from reads/parsing.
|
|
245
|
+
*/
|
|
246
|
+
declare type NormalizedSkillsManifest = {
|
|
247
|
+
$schema?: string;
|
|
248
|
+
installDir: string;
|
|
249
|
+
linkTargets: string[];
|
|
250
|
+
selfSkill?: boolean;
|
|
251
|
+
skills: Record<string, string>;
|
|
252
|
+
patchedSkills?: Record<string, string>;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export declare type NormalizedSpecifier = {
|
|
256
|
+
type: 'git' | 'link' | 'file' | 'npm';
|
|
257
|
+
source: string;
|
|
258
|
+
ref: string | null;
|
|
259
|
+
path: string;
|
|
260
|
+
normalized: string;
|
|
261
|
+
skillName: string;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export declare function normalizeSkillsManifest(manifest: Partial<SkillsManifest>): NormalizedSkillsManifest;
|
|
265
|
+
|
|
266
|
+
export declare function normalizeSpecifier(specifier: string): NormalizedSpecifier;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Error thrown when parsing fails (JSON, YAML, specifiers)
|
|
270
|
+
*/
|
|
271
|
+
export declare class ParseError extends SpmError {
|
|
272
|
+
readonly filePath?: string;
|
|
273
|
+
readonly content?: string;
|
|
274
|
+
constructor(options: {
|
|
275
|
+
code: ErrorCode.PARSE_ERROR | ErrorCode.JSON_PARSE_ERROR | ErrorCode.YAML_PARSE_ERROR | ErrorCode.INVALID_SPECIFIER;
|
|
276
|
+
filePath?: string;
|
|
277
|
+
content?: string;
|
|
278
|
+
message: string;
|
|
279
|
+
cause?: Error;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export declare function parseGitHubUrl(input: string): {
|
|
284
|
+
owner: string;
|
|
285
|
+
repo: string;
|
|
286
|
+
} | null;
|
|
287
|
+
|
|
288
|
+
export declare function parseOwnerRepo(input: string): {
|
|
289
|
+
owner: string;
|
|
290
|
+
repo: string;
|
|
291
|
+
} | null;
|
|
292
|
+
|
|
293
|
+
export declare function parseSpecifier(specifier: string): {
|
|
294
|
+
sourcePart: string;
|
|
295
|
+
ref: string | null;
|
|
296
|
+
path: string;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export declare function patchCommand(options: PatchCommandOptions): Promise<PatchCommandResult>;
|
|
300
|
+
|
|
301
|
+
export declare type PatchCommandOptions = {
|
|
302
|
+
cwd: string;
|
|
303
|
+
skillName: string;
|
|
304
|
+
editDir?: string;
|
|
305
|
+
ignoreExisting?: boolean;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export declare type PatchCommandResult = {
|
|
309
|
+
status: 'patched';
|
|
310
|
+
skillName: string;
|
|
311
|
+
editDir: string;
|
|
312
|
+
originalSpecifier: string;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export declare function patchCommitCommand(options: PatchCommitCommandOptions): Promise<PatchCommitCommandResult>;
|
|
316
|
+
|
|
317
|
+
export declare type PatchCommitCommandOptions = {
|
|
318
|
+
cwd: string;
|
|
319
|
+
editDir: string;
|
|
320
|
+
patchesDir?: string;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export declare type PatchCommitCommandResult = {
|
|
324
|
+
status: 'patched';
|
|
325
|
+
skillName: string;
|
|
326
|
+
patchFile: string;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
declare type ProgressReporterOptions = {
|
|
330
|
+
isTTY?: boolean;
|
|
331
|
+
write?: (text: string) => void;
|
|
332
|
+
info?: (text: string) => void;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
export declare function readSkillsLock(rootDir: string): Promise<SkillsLock | null>;
|
|
336
|
+
|
|
337
|
+
export declare function readSkillsManifest(rootDir: string): Promise<NormalizedSkillsManifest | null>;
|
|
338
|
+
|
|
339
|
+
export declare function resolveLockEntry(cwd: string, specifier: string, skillName?: string): Promise<{
|
|
340
|
+
skillName: string;
|
|
341
|
+
entry: SkillsLockEntry;
|
|
342
|
+
}>;
|
|
343
|
+
|
|
344
|
+
export declare function runCli(argv: string[], context?: {
|
|
345
|
+
cwd?: string;
|
|
346
|
+
}): Promise<unknown>;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Error thrown when a skill operation fails
|
|
350
|
+
*/
|
|
351
|
+
export declare class SkillError extends SpmError {
|
|
352
|
+
readonly skillName: string;
|
|
353
|
+
constructor(options: {
|
|
354
|
+
code: ErrorCode.SKILL_NOT_FOUND | ErrorCode.SKILL_EXISTS | ErrorCode.VALIDATION_ERROR;
|
|
355
|
+
skillName: string;
|
|
356
|
+
message?: string;
|
|
357
|
+
cause?: Error;
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export declare type SkillInfo = {
|
|
362
|
+
name: string;
|
|
363
|
+
description: string;
|
|
364
|
+
path: string;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
export declare type SkillsLock = {
|
|
368
|
+
lockfileVersion: '0.1';
|
|
369
|
+
installDir: string;
|
|
370
|
+
linkTargets: string[];
|
|
371
|
+
skills: Record<string, SkillsLockEntry>;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
export declare type SkillsLockEntry = {
|
|
375
|
+
specifier: string;
|
|
376
|
+
resolution: {
|
|
377
|
+
type: 'link';
|
|
378
|
+
path: string;
|
|
379
|
+
} | {
|
|
380
|
+
type: 'file';
|
|
381
|
+
tarball: string;
|
|
382
|
+
path: string;
|
|
383
|
+
} | {
|
|
384
|
+
type: 'git';
|
|
385
|
+
url: string;
|
|
386
|
+
commit: string;
|
|
387
|
+
path: string;
|
|
388
|
+
} | {
|
|
389
|
+
type: 'npm';
|
|
390
|
+
packageName: string;
|
|
391
|
+
version: string;
|
|
392
|
+
path: string;
|
|
393
|
+
tarball: string;
|
|
394
|
+
integrity?: string;
|
|
395
|
+
registry?: string;
|
|
396
|
+
};
|
|
397
|
+
digest: string;
|
|
398
|
+
patch?: {
|
|
399
|
+
path: string;
|
|
400
|
+
digest: string;
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Skills manifest input type used for authoring/writing manifests.
|
|
406
|
+
* This preserves optionality for fields with defaults.
|
|
407
|
+
*/
|
|
408
|
+
export declare type SkillsManifest = {
|
|
409
|
+
$schema?: string;
|
|
410
|
+
installDir?: string;
|
|
411
|
+
linkTargets?: string[];
|
|
412
|
+
selfSkill?: boolean;
|
|
413
|
+
skills?: Record<string, string>;
|
|
414
|
+
patchedSkills?: Record<string, string>;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Base error class for SPM (Skills Package Manager)
|
|
419
|
+
* All custom errors should extend this class
|
|
420
|
+
*/
|
|
421
|
+
export declare class SpmError extends Error {
|
|
422
|
+
readonly code: ErrorCode;
|
|
423
|
+
readonly cause?: Error;
|
|
424
|
+
readonly context: Record<string, unknown>;
|
|
425
|
+
constructor(options: {
|
|
426
|
+
code: ErrorCode;
|
|
427
|
+
message: string;
|
|
428
|
+
cause?: Error;
|
|
429
|
+
context?: Record<string, unknown>;
|
|
430
|
+
});
|
|
431
|
+
/**
|
|
432
|
+
* Returns a formatted string representation of the error
|
|
433
|
+
*/
|
|
434
|
+
toString(): string;
|
|
435
|
+
/**
|
|
436
|
+
* Returns a detailed object representation for logging/debugging
|
|
437
|
+
*/
|
|
438
|
+
toJSON(): Record<string, unknown>;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export declare function updateCommand(options: UpdateCommandOptions): Promise<UpdateCommandResult>;
|
|
442
|
+
|
|
443
|
+
export declare type UpdateCommandOptions = {
|
|
444
|
+
cwd: string;
|
|
445
|
+
skills?: string[];
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export declare type UpdateCommandResult = {
|
|
449
|
+
status: 'updated' | 'skipped' | 'failed';
|
|
450
|
+
updated: string[];
|
|
451
|
+
unchanged: string[];
|
|
452
|
+
skipped: Array<{
|
|
453
|
+
name: string;
|
|
454
|
+
reason: 'link-specifier';
|
|
455
|
+
}>;
|
|
456
|
+
failed: Array<{
|
|
457
|
+
name: string;
|
|
458
|
+
reason: string;
|
|
459
|
+
}>;
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
export declare function writeSkillsLock(rootDir: string, lockfile: SkillsLock): Promise<void>;
|
|
463
|
+
|
|
464
|
+
export declare function writeSkillsManifest(rootDir: string, manifest: SkillsManifest): Promise<void>;
|
|
465
|
+
|
|
466
|
+
export { }
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { co } from "./npm-tar.js";
|
|
|
16
16
|
import { cac } from "./npm-cac.js";
|
|
17
17
|
import { Ct } from "./npm-clack_core.js";
|
|
18
18
|
var package_namespaceObject = {
|
|
19
|
-
rE: "0.
|
|
19
|
+
rE: "0.8.0"
|
|
20
20
|
};
|
|
21
21
|
function getHomeDir() {
|
|
22
22
|
return homedir();
|
|
@@ -721,7 +721,8 @@ const skillsManifestSchema = schemas_object({
|
|
|
721
721
|
installDir: schemas_string().default('.agents/skills').describe('Directory where skills will be installed'),
|
|
722
722
|
linkTargets: schemas_array(schemas_string()).default([]).describe('Directories where skill symlinks will be created'),
|
|
723
723
|
selfSkill: schemas_boolean().optional().describe('Whether this project is itself a skill'),
|
|
724
|
-
skills: record(schemas_string(), schemas_string()).default({}).describe('Map of skill names to their specifiers')
|
|
724
|
+
skills: record(schemas_string(), schemas_string()).default({}).describe('Map of skill names to their specifiers'),
|
|
725
|
+
patchedSkills: record(schemas_string(), schemas_string()).optional().describe('Map of skill names to their patch file paths')
|
|
725
726
|
}).strict();
|
|
726
727
|
async function readSkillsManifest(rootDir) {
|
|
727
728
|
const filePath = node_path.join(rootDir, 'skills.json');
|
|
@@ -1093,11 +1094,11 @@ async function sha256File(filePath, suffix = '') {
|
|
|
1093
1094
|
if (suffix) hash.update(suffix);
|
|
1094
1095
|
return `sha256-${hash.digest('hex')}`;
|
|
1095
1096
|
}
|
|
1096
|
-
const execFileAsync = promisify(execFile);
|
|
1097
1097
|
function toPortableRelativePath(from, to) {
|
|
1098
1098
|
const relativePath = node_path.relative(from, to) || '.';
|
|
1099
1099
|
return '/' === node_path.sep ? relativePath : relativePath.split(node_path.sep).join('/');
|
|
1100
1100
|
}
|
|
1101
|
+
const execFileAsync = promisify(execFile);
|
|
1101
1102
|
async function resolveGitCommitByLsRemote(url, target) {
|
|
1102
1103
|
try {
|
|
1103
1104
|
const { stdout } = await execFileAsync('git', [
|
|
@@ -1247,16 +1248,29 @@ async function resolveLockEntry(cwd, specifier, skillName) {
|
|
|
1247
1248
|
content: specifier
|
|
1248
1249
|
});
|
|
1249
1250
|
}
|
|
1251
|
+
async function attachManifestPatchToEntry(cwd, manifest, skillName, entry) {
|
|
1252
|
+
const patchPath = manifest.patchedSkills?.[skillName];
|
|
1253
|
+
if (!patchPath) return entry;
|
|
1254
|
+
const absolutePatchPath = node_path.resolve(cwd, patchPath);
|
|
1255
|
+
return {
|
|
1256
|
+
...entry,
|
|
1257
|
+
patch: {
|
|
1258
|
+
path: toPortableRelativePath(cwd, absolutePatchPath),
|
|
1259
|
+
digest: await sha256File(absolutePatchPath)
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1250
1263
|
async function syncSkillsLock(cwd, manifest, _existingLock, options) {
|
|
1251
1264
|
const entries = await Promise.all(Object.entries(manifest.skills).map(async ([skillName, specifier])=>{
|
|
1252
1265
|
const { skillName: resolvedName, entry } = await resolveLockEntry(cwd, specifier, skillName);
|
|
1266
|
+
const entryWithPatch = await attachManifestPatchToEntry(cwd, manifest, resolvedName, entry);
|
|
1253
1267
|
options?.onProgress?.({
|
|
1254
1268
|
type: 'resolved',
|
|
1255
1269
|
skillName: resolvedName
|
|
1256
1270
|
});
|
|
1257
1271
|
return [
|
|
1258
1272
|
resolvedName,
|
|
1259
|
-
|
|
1273
|
+
entryWithPatch
|
|
1260
1274
|
];
|
|
1261
1275
|
}));
|
|
1262
1276
|
const nextSkills = Object.fromEntries(entries);
|
|
@@ -1288,6 +1302,7 @@ async function writeSkillsManifest(rootDir, manifest) {
|
|
|
1288
1302
|
skills: manifest.skills
|
|
1289
1303
|
};
|
|
1290
1304
|
if (void 0 !== manifest.selfSkill) nextManifest.selfSkill = manifest.selfSkill;
|
|
1305
|
+
if (manifest.patchedSkills && Object.keys(manifest.patchedSkills).length > 0) nextManifest.patchedSkills = manifest.patchedSkills;
|
|
1291
1306
|
try {
|
|
1292
1307
|
await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
|
|
1293
1308
|
} catch (error) {
|
|
@@ -1470,17 +1485,31 @@ function arraysEqual(a, b) {
|
|
|
1470
1485
|
if (a.length !== b.length) return false;
|
|
1471
1486
|
return a.every((val, i)=>val === b[i]);
|
|
1472
1487
|
}
|
|
1473
|
-
function
|
|
1488
|
+
async function isPatchInSync(rootDir, manifest, skillName, lock) {
|
|
1489
|
+
const lockEntry = lock.skills[skillName];
|
|
1490
|
+
if (!lockEntry) return false;
|
|
1491
|
+
const manifestPatchPath = manifest.patchedSkills?.[skillName];
|
|
1492
|
+
if (!manifestPatchPath) return void 0 === lockEntry.patch;
|
|
1493
|
+
if (!lockEntry.patch) return false;
|
|
1494
|
+
const absolutePatchPath = node_path.resolve(rootDir, manifestPatchPath);
|
|
1495
|
+
const normalizedPatchPath = toPortableRelativePath(rootDir, absolutePatchPath);
|
|
1496
|
+
if (lockEntry.patch.path !== normalizedPatchPath) return false;
|
|
1497
|
+
return lockEntry.patch.digest === await sha256File(absolutePatchPath);
|
|
1498
|
+
}
|
|
1499
|
+
async function isLockInSync(rootDir, manifest, lock) {
|
|
1474
1500
|
if (!lock) return false;
|
|
1475
1501
|
if (normalizeInstallDir(manifest.installDir) !== normalizeInstallDir(lock.installDir)) return false;
|
|
1476
1502
|
if (!arraysEqual(normalizeLinkTargets(manifest.linkTargets), normalizeLinkTargets(lock.linkTargets))) return false;
|
|
1477
1503
|
const manifestSkills = Object.entries(manifest.skills);
|
|
1478
1504
|
const lockSkillNames = Object.keys(lock.skills);
|
|
1505
|
+
const patchedSkillNames = Object.keys(manifest.patchedSkills ?? {});
|
|
1479
1506
|
if (manifestSkills.length !== lockSkillNames.length) return false;
|
|
1507
|
+
if (patchedSkillNames.some((skillName)=>!(skillName in manifest.skills))) return false;
|
|
1480
1508
|
for (const [name, specifier] of manifestSkills){
|
|
1481
1509
|
const lockEntry = lock.skills[name];
|
|
1482
1510
|
if (!lockEntry) return false;
|
|
1483
1511
|
if (!isSpecifierCompatible(specifier, lockEntry.specifier)) return false;
|
|
1512
|
+
if (!await isPatchInSync(rootDir, manifest, name, lock)) return false;
|
|
1484
1513
|
}
|
|
1485
1514
|
return true;
|
|
1486
1515
|
}
|
|
@@ -1522,7 +1551,8 @@ function normalizeSkillsManifest(manifest) {
|
|
|
1522
1551
|
installDir: manifest.installDir ?? '.agents/skills',
|
|
1523
1552
|
linkTargets: manifest.linkTargets ?? [],
|
|
1524
1553
|
selfSkill: manifest.selfSkill ?? false,
|
|
1525
|
-
skills: manifest.skills ?? {}
|
|
1554
|
+
skills: manifest.skills ?? {},
|
|
1555
|
+
patchedSkills: manifest.patchedSkills
|
|
1526
1556
|
};
|
|
1527
1557
|
}
|
|
1528
1558
|
async function expandSkillsManifest(_rootDir, manifest) {
|
|
@@ -1538,6 +1568,172 @@ async function expandSkillsManifest(_rootDir, manifest) {
|
|
|
1538
1568
|
}
|
|
1539
1569
|
};
|
|
1540
1570
|
}
|
|
1571
|
+
const skillPatch_execFileAsync = promisify(execFile);
|
|
1572
|
+
const PATCH_EDIT_STATE_FILE = '.skills-pm-patch.json';
|
|
1573
|
+
async function writePatchEditState(editDir, state) {
|
|
1574
|
+
const filePath = node_path.join(editDir, PATCH_EDIT_STATE_FILE);
|
|
1575
|
+
await writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
1576
|
+
}
|
|
1577
|
+
async function readPatchEditState(editDir) {
|
|
1578
|
+
const filePath = node_path.join(editDir, PATCH_EDIT_STATE_FILE);
|
|
1579
|
+
let raw;
|
|
1580
|
+
try {
|
|
1581
|
+
raw = await readFile(filePath, 'utf8');
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
throw convertNodeError(error, {
|
|
1584
|
+
operation: 'read',
|
|
1585
|
+
path: filePath
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
try {
|
|
1589
|
+
return JSON.parse(raw);
|
|
1590
|
+
} catch (error) {
|
|
1591
|
+
throw new ParseError({
|
|
1592
|
+
code: codes_ErrorCode.JSON_PARSE_ERROR,
|
|
1593
|
+
filePath,
|
|
1594
|
+
content: raw,
|
|
1595
|
+
message: `Failed to parse patch edit state: ${error.message}`,
|
|
1596
|
+
cause: error
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
async function clearDirectoryExceptGit(rootDir) {
|
|
1601
|
+
const entries = await readdir(rootDir, {
|
|
1602
|
+
withFileTypes: true
|
|
1603
|
+
});
|
|
1604
|
+
await Promise.all(entries.filter((entry)=>'.git' !== entry.name).map((entry)=>rm(node_path.join(rootDir, entry.name), {
|
|
1605
|
+
recursive: true,
|
|
1606
|
+
force: true
|
|
1607
|
+
})));
|
|
1608
|
+
}
|
|
1609
|
+
async function copySkillDir(from, to) {
|
|
1610
|
+
await cp(from, to, {
|
|
1611
|
+
recursive: true,
|
|
1612
|
+
filter: (source)=>{
|
|
1613
|
+
const baseName = node_path.basename(source);
|
|
1614
|
+
return baseName !== PATCH_EDIT_STATE_FILE && '.git' !== baseName && '.hg' !== baseName;
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
async function runGitCommand(args, options) {
|
|
1619
|
+
try {
|
|
1620
|
+
return await skillPatch_execFileAsync('git', args, {
|
|
1621
|
+
cwd: options.cwd,
|
|
1622
|
+
...options.maxBuffer ? {
|
|
1623
|
+
maxBuffer: options.maxBuffer
|
|
1624
|
+
} : {}
|
|
1625
|
+
});
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
const nodeError = error;
|
|
1628
|
+
if ('ENOENT' === nodeError.code) throw new GitError({
|
|
1629
|
+
code: codes_ErrorCode.GIT_NOT_INSTALLED,
|
|
1630
|
+
operation: options.operation,
|
|
1631
|
+
message: 'git is required to create and apply skill patches',
|
|
1632
|
+
cause: error
|
|
1633
|
+
});
|
|
1634
|
+
throw new GitError({
|
|
1635
|
+
code: codes_ErrorCode.GIT_FETCH_FAILED,
|
|
1636
|
+
operation: options.operation,
|
|
1637
|
+
message: options.message,
|
|
1638
|
+
cause: error
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
async function applySkillPatch(targetDir, patchFilePath) {
|
|
1643
|
+
try {
|
|
1644
|
+
await access(patchFilePath);
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
throw convertNodeError(error, {
|
|
1647
|
+
operation: 'read',
|
|
1648
|
+
path: patchFilePath
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
await runGitCommand([
|
|
1652
|
+
'apply',
|
|
1653
|
+
'--whitespace=nowarn',
|
|
1654
|
+
patchFilePath
|
|
1655
|
+
], {
|
|
1656
|
+
cwd: targetDir,
|
|
1657
|
+
operation: 'apply',
|
|
1658
|
+
message: `Failed to apply patch ${patchFilePath}`
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
async function generateSkillPatch(baseDir, editedDir) {
|
|
1662
|
+
const repoRoot = await mkdtemp(node_path.join(tmpdir(), 'skills-pm-patch-commit-'));
|
|
1663
|
+
try {
|
|
1664
|
+
await runGitCommand([
|
|
1665
|
+
'init',
|
|
1666
|
+
'--quiet'
|
|
1667
|
+
], {
|
|
1668
|
+
cwd: repoRoot,
|
|
1669
|
+
operation: 'init',
|
|
1670
|
+
message: 'Failed to initialize git repository for patch generation'
|
|
1671
|
+
});
|
|
1672
|
+
await runGitCommand([
|
|
1673
|
+
'config',
|
|
1674
|
+
'user.email',
|
|
1675
|
+
'skills-package-manager@example.com'
|
|
1676
|
+
], {
|
|
1677
|
+
cwd: repoRoot,
|
|
1678
|
+
operation: 'config',
|
|
1679
|
+
message: 'Failed to configure git user email for patch generation'
|
|
1680
|
+
});
|
|
1681
|
+
await runGitCommand([
|
|
1682
|
+
'config',
|
|
1683
|
+
'user.name',
|
|
1684
|
+
'skills-package-manager'
|
|
1685
|
+
], {
|
|
1686
|
+
cwd: repoRoot,
|
|
1687
|
+
operation: 'config',
|
|
1688
|
+
message: 'Failed to configure git user name for patch generation'
|
|
1689
|
+
});
|
|
1690
|
+
await copySkillDir(baseDir, repoRoot);
|
|
1691
|
+
await runGitCommand([
|
|
1692
|
+
'add',
|
|
1693
|
+
'--all'
|
|
1694
|
+
], {
|
|
1695
|
+
cwd: repoRoot,
|
|
1696
|
+
operation: 'add',
|
|
1697
|
+
message: 'Failed to stage base skill files for patch generation'
|
|
1698
|
+
});
|
|
1699
|
+
await runGitCommand([
|
|
1700
|
+
'commit',
|
|
1701
|
+
'--quiet',
|
|
1702
|
+
'--allow-empty',
|
|
1703
|
+
'--no-gpg-sign',
|
|
1704
|
+
'-m',
|
|
1705
|
+
'base'
|
|
1706
|
+
], {
|
|
1707
|
+
cwd: repoRoot,
|
|
1708
|
+
operation: 'commit',
|
|
1709
|
+
message: 'Failed to create base commit for patch generation'
|
|
1710
|
+
});
|
|
1711
|
+
await clearDirectoryExceptGit(repoRoot);
|
|
1712
|
+
await copySkillDir(editedDir, repoRoot);
|
|
1713
|
+
const { stdout } = await runGitCommand([
|
|
1714
|
+
'diff',
|
|
1715
|
+
'--binary',
|
|
1716
|
+
'--full-index',
|
|
1717
|
+
'--no-ext-diff',
|
|
1718
|
+
'--src-prefix=a/',
|
|
1719
|
+
'--dst-prefix=b/',
|
|
1720
|
+
'HEAD',
|
|
1721
|
+
'--',
|
|
1722
|
+
'.'
|
|
1723
|
+
], {
|
|
1724
|
+
cwd: repoRoot,
|
|
1725
|
+
operation: 'diff',
|
|
1726
|
+
message: 'Failed to generate skill patch',
|
|
1727
|
+
maxBuffer: 10485760
|
|
1728
|
+
});
|
|
1729
|
+
return stdout;
|
|
1730
|
+
} finally{
|
|
1731
|
+
await rm(repoRoot, {
|
|
1732
|
+
recursive: true,
|
|
1733
|
+
force: true
|
|
1734
|
+
}).catch(()=>{});
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1541
1737
|
async function ensureDir(dirPath) {
|
|
1542
1738
|
await mkdir(dirPath, {
|
|
1543
1739
|
recursive: true
|
|
@@ -1586,7 +1782,7 @@ async function linkSkill(rootDir, installDir, linkTarget, skillName) {
|
|
|
1586
1782
|
await ensureDir(node_path.dirname(absoluteLink));
|
|
1587
1783
|
await replaceSymlink(absoluteTarget, absoluteLink);
|
|
1588
1784
|
}
|
|
1589
|
-
async function
|
|
1785
|
+
async function copyLocalSkillToDir(sourceRoot, sourcePath, targetDir) {
|
|
1590
1786
|
const relativeSkillPath = sourcePath.replace(/^\//, '');
|
|
1591
1787
|
const absoluteSkillPath = node_path.join(sourceRoot, relativeSkillPath);
|
|
1592
1788
|
const skillDocPath = node_path.join(absoluteSkillPath, 'SKILL.md');
|
|
@@ -1597,15 +1793,21 @@ async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath,
|
|
|
1597
1793
|
throw new Error(`Invalid skill at ${absoluteSkillPath}: missing SKILL.md`);
|
|
1598
1794
|
}
|
|
1599
1795
|
if (!skillDoc) throw new Error(`Invalid skill at ${absoluteSkillPath}: missing SKILL.md`);
|
|
1600
|
-
const targetDir = node_path.join(rootDir, installDir, skillName);
|
|
1601
1796
|
await ensureDir(node_path.dirname(targetDir));
|
|
1602
1797
|
await replaceDir(absoluteSkillPath, targetDir);
|
|
1798
|
+
}
|
|
1799
|
+
async function writeInstalledSkillMarker(targetDir, skillName) {
|
|
1603
1800
|
await writeJson(node_path.join(targetDir, '.skills-pm.json'), {
|
|
1604
1801
|
name: skillName,
|
|
1605
1802
|
installedBy: 'skills-package-manager',
|
|
1606
1803
|
version: '0.1.0'
|
|
1607
1804
|
});
|
|
1608
1805
|
}
|
|
1806
|
+
async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath, installDir) {
|
|
1807
|
+
const targetDir = node_path.join(rootDir, installDir, skillName);
|
|
1808
|
+
await copyLocalSkillToDir(sourceRoot, sourcePath, targetDir);
|
|
1809
|
+
await writeInstalledSkillMarker(targetDir, skillName);
|
|
1810
|
+
}
|
|
1609
1811
|
const materializeGitSkill_execFileAsync = promisify(execFile);
|
|
1610
1812
|
async function checkoutCommit(checkoutRoot, commit) {
|
|
1611
1813
|
try {
|
|
@@ -1665,7 +1867,7 @@ async function fetchCommitFallback(checkoutRoot, commit, _repoUrl) {
|
|
|
1665
1867
|
});
|
|
1666
1868
|
}
|
|
1667
1869
|
}
|
|
1668
|
-
async function
|
|
1870
|
+
async function extractGitSkillToDir(repoUrl, commit, sourcePath, targetDir) {
|
|
1669
1871
|
const checkoutRoot = await mkdtemp(node_path.join(tmpdir(), 'skills-pm-git-checkout-'));
|
|
1670
1872
|
try {
|
|
1671
1873
|
try {
|
|
@@ -1698,7 +1900,7 @@ async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePa
|
|
|
1698
1900
|
}
|
|
1699
1901
|
const skillDocPath = node_path.join(checkoutRoot, sourcePath.replace(/^\//, ''), 'SKILL.md');
|
|
1700
1902
|
await readFile(skillDocPath, 'utf8');
|
|
1701
|
-
await
|
|
1903
|
+
await copyLocalSkillToDir(checkoutRoot, sourcePath, targetDir);
|
|
1702
1904
|
} finally{
|
|
1703
1905
|
await rm(checkoutRoot, {
|
|
1704
1906
|
recursive: true,
|
|
@@ -1706,7 +1908,12 @@ async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePa
|
|
|
1706
1908
|
});
|
|
1707
1909
|
}
|
|
1708
1910
|
}
|
|
1709
|
-
async function
|
|
1911
|
+
async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePath, installDir) {
|
|
1912
|
+
const targetDir = node_path.join(rootDir, installDir, skillName);
|
|
1913
|
+
await extractGitSkillToDir(repoUrl, commit, sourcePath, targetDir);
|
|
1914
|
+
await writeInstalledSkillMarker(targetDir, skillName);
|
|
1915
|
+
}
|
|
1916
|
+
async function extractPackedSkillToDir(tarballPath, sourcePath, targetDir) {
|
|
1710
1917
|
const extractRoot = await mkdtemp(node_path.join(tmpdir(), 'skills-pm-packed-skill-'));
|
|
1711
1918
|
try {
|
|
1712
1919
|
await mkdir(node_path.join(extractRoot, 'package'), {
|
|
@@ -1719,7 +1926,7 @@ async function materializePackedSkill(rootDir, skillName, tarballPath, sourcePat
|
|
|
1719
1926
|
preservePaths: false,
|
|
1720
1927
|
strict: true
|
|
1721
1928
|
});
|
|
1722
|
-
await
|
|
1929
|
+
await copyLocalSkillToDir(node_path.join(extractRoot, 'package'), sourcePath, targetDir);
|
|
1723
1930
|
} finally{
|
|
1724
1931
|
await rm(extractRoot, {
|
|
1725
1932
|
recursive: true,
|
|
@@ -1727,6 +1934,11 @@ async function materializePackedSkill(rootDir, skillName, tarballPath, sourcePat
|
|
|
1727
1934
|
}).catch(()=>{});
|
|
1728
1935
|
}
|
|
1729
1936
|
}
|
|
1937
|
+
async function materializePackedSkill(rootDir, skillName, tarballPath, sourcePath, installDir) {
|
|
1938
|
+
const targetDir = node_path.join(rootDir, installDir, skillName);
|
|
1939
|
+
await extractPackedSkillToDir(tarballPath, sourcePath, targetDir);
|
|
1940
|
+
await writeInstalledSkillMarker(targetDir, skillName);
|
|
1941
|
+
}
|
|
1730
1942
|
function pruneManagedSkills_resolveTargetPath(rootDir, targetPath) {
|
|
1731
1943
|
return node_path.isAbsolute(targetPath) ? targetPath : node_path.join(rootDir, targetPath);
|
|
1732
1944
|
}
|
|
@@ -1805,6 +2017,7 @@ async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
|
|
|
1805
2017
|
for (const [skillName, entry] of Object.entries(lockfile.skills)){
|
|
1806
2018
|
if ('link' === entry.resolution.type) {
|
|
1807
2019
|
await materializeLocalSkill(rootDir, skillName, node_path.resolve(rootDir, entry.resolution.path), '/', installDir);
|
|
2020
|
+
if (entry.patch) await applySkillPatch(node_path.join(rootDir, installDir, skillName), node_path.resolve(rootDir, entry.patch.path));
|
|
1808
2021
|
options?.onProgress?.({
|
|
1809
2022
|
type: 'added',
|
|
1810
2023
|
skillName
|
|
@@ -1813,6 +2026,7 @@ async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
|
|
|
1813
2026
|
}
|
|
1814
2027
|
if ('file' === entry.resolution.type) {
|
|
1815
2028
|
await materializePackedSkill(rootDir, skillName, node_path.resolve(rootDir, entry.resolution.tarball), entry.resolution.path, installDir);
|
|
2029
|
+
if (entry.patch) await applySkillPatch(node_path.join(rootDir, installDir, skillName), node_path.resolve(rootDir, entry.patch.path));
|
|
1816
2030
|
options?.onProgress?.({
|
|
1817
2031
|
type: 'added',
|
|
1818
2032
|
skillName
|
|
@@ -1821,6 +2035,7 @@ async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
|
|
|
1821
2035
|
}
|
|
1822
2036
|
if ('git' === entry.resolution.type) {
|
|
1823
2037
|
await materializeGitSkill(rootDir, skillName, entry.resolution.url, entry.resolution.commit, entry.resolution.path, installDir);
|
|
2038
|
+
if (entry.patch) await applySkillPatch(node_path.join(rootDir, installDir, skillName), node_path.resolve(rootDir, entry.patch.path));
|
|
1824
2039
|
options?.onProgress?.({
|
|
1825
2040
|
type: 'added',
|
|
1826
2041
|
skillName
|
|
@@ -1836,13 +2051,14 @@ async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
|
|
|
1836
2051
|
}
|
|
1837
2052
|
const tarballPath = await tarballPathPromise;
|
|
1838
2053
|
await materializePackedSkill(rootDir, skillName, tarballPath, entry.resolution.path, installDir);
|
|
2054
|
+
if (entry.patch) await applySkillPatch(node_path.join(rootDir, installDir, skillName), node_path.resolve(rootDir, entry.patch.path));
|
|
1839
2055
|
options?.onProgress?.({
|
|
1840
2056
|
type: 'added',
|
|
1841
2057
|
skillName
|
|
1842
2058
|
});
|
|
1843
2059
|
continue;
|
|
1844
2060
|
}
|
|
1845
|
-
throw new Error(
|
|
2061
|
+
throw new Error('Unsupported resolution type in 0.1.0 core flow');
|
|
1846
2062
|
}
|
|
1847
2063
|
await writeInstallState(rootDir, installDir, {
|
|
1848
2064
|
lockDigest,
|
|
@@ -1888,7 +2104,7 @@ async function installSkills(rootDir, options) {
|
|
|
1888
2104
|
let lockfile;
|
|
1889
2105
|
if (options?.frozenLockfile) {
|
|
1890
2106
|
if (!currentLock) throw new Error('Lockfile is required in frozen mode but none was found');
|
|
1891
|
-
if (!isLockInSync(manifest, currentLock)) throw new Error('Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.');
|
|
2107
|
+
if (!await isLockInSync(rootDir, manifest, currentLock)) throw new Error('Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.');
|
|
1892
2108
|
lockfile = currentLock;
|
|
1893
2109
|
for (const skillName of Object.keys(lockfile.skills))options?.onProgress?.({
|
|
1894
2110
|
type: 'resolved',
|
|
@@ -2500,7 +2716,7 @@ async function installCommand(options) {
|
|
|
2500
2716
|
filePath: `${options.cwd}/skills-lock.yaml`,
|
|
2501
2717
|
message: 'Lockfile is required in frozen mode but none was found. Run "spm install" first.'
|
|
2502
2718
|
});
|
|
2503
|
-
if (!isLockInSync(manifest, currentLock)) throw new ManifestError({
|
|
2719
|
+
if (!await isLockInSync(options.cwd, manifest, currentLock)) throw new ManifestError({
|
|
2504
2720
|
code: codes_ErrorCode.LOCKFILE_OUTDATED,
|
|
2505
2721
|
filePath: `${options.cwd}/skills-lock.yaml`,
|
|
2506
2722
|
message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
|
|
@@ -2536,6 +2752,193 @@ async function installCommand(options) {
|
|
|
2536
2752
|
throw error;
|
|
2537
2753
|
}
|
|
2538
2754
|
}
|
|
2755
|
+
async function extractSkillToDir(rootDir, entry, targetDir) {
|
|
2756
|
+
if ('link' === entry.resolution.type) return void await copyLocalSkillToDir(node_path.resolve(rootDir, entry.resolution.path), '/', targetDir);
|
|
2757
|
+
if ('file' === entry.resolution.type) return void await extractPackedSkillToDir(node_path.resolve(rootDir, entry.resolution.tarball), entry.resolution.path, targetDir);
|
|
2758
|
+
if ('git' === entry.resolution.type) return void await extractGitSkillToDir(entry.resolution.url, entry.resolution.commit, entry.resolution.path, targetDir);
|
|
2759
|
+
if ('npm' === entry.resolution.type) {
|
|
2760
|
+
const tarballPath = await downloadNpmPackageTarball(rootDir, entry.resolution.tarball, entry.resolution.integrity);
|
|
2761
|
+
try {
|
|
2762
|
+
await extractPackedSkillToDir(tarballPath, entry.resolution.path, targetDir);
|
|
2763
|
+
} finally{
|
|
2764
|
+
await cleanupPackedNpmPackage(tarballPath);
|
|
2765
|
+
}
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
throw new Error('Unsupported resolution type in 0.1.0 core flow');
|
|
2769
|
+
}
|
|
2770
|
+
async function ensureEditDirDoesNotExist(editDir) {
|
|
2771
|
+
try {
|
|
2772
|
+
await access(editDir);
|
|
2773
|
+
} catch (error) {
|
|
2774
|
+
if ('ENOENT' === error.code) return;
|
|
2775
|
+
throw convertNodeError(error, {
|
|
2776
|
+
operation: 'access',
|
|
2777
|
+
path: editDir
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
throw new FileSystemError({
|
|
2781
|
+
code: codes_ErrorCode.FILE_EXISTS,
|
|
2782
|
+
operation: 'mkdir',
|
|
2783
|
+
path: editDir,
|
|
2784
|
+
message: `Patch edit directory already exists: ${editDir}`
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
async function createBaseLock(cwd, manifest, currentLock) {
|
|
2788
|
+
if (currentLock && await isLockInSync(cwd, manifest, currentLock)) return {
|
|
2789
|
+
...currentLock,
|
|
2790
|
+
skills: {
|
|
2791
|
+
...currentLock.skills
|
|
2792
|
+
}
|
|
2793
|
+
};
|
|
2794
|
+
return syncSkillsLock(cwd, manifest, currentLock);
|
|
2795
|
+
}
|
|
2796
|
+
function getUnpatchedBaseEntry(entry) {
|
|
2797
|
+
if (!entry.patch) return entry;
|
|
2798
|
+
const { patch: _patch, ...baseEntry } = entry;
|
|
2799
|
+
return baseEntry;
|
|
2800
|
+
}
|
|
2801
|
+
async function resolveEditDir(cwd, skillName, editDir) {
|
|
2802
|
+
if (editDir) return node_path.resolve(cwd, editDir);
|
|
2803
|
+
const sanitizedSkillName = skillName.replace(/[^a-zA-Z0-9._-]+/g, '-');
|
|
2804
|
+
return mkdtemp(node_path.join(tmpdir(), `skills-pm-patch-${sanitizedSkillName}-`));
|
|
2805
|
+
}
|
|
2806
|
+
async function patchCommand(options) {
|
|
2807
|
+
const manifest = await readSkillsManifest(options.cwd);
|
|
2808
|
+
if (!manifest) throw new ManifestError({
|
|
2809
|
+
code: codes_ErrorCode.MANIFEST_NOT_FOUND,
|
|
2810
|
+
filePath: `${options.cwd}/skills.json`,
|
|
2811
|
+
message: 'No skills.json found in the current directory. Run "spm init" to create one.'
|
|
2812
|
+
});
|
|
2813
|
+
if (!(options.skillName in manifest.skills)) throw new SkillError({
|
|
2814
|
+
code: codes_ErrorCode.SKILL_NOT_FOUND,
|
|
2815
|
+
skillName: options.skillName,
|
|
2816
|
+
message: `Unknown skill: ${options.skillName}`
|
|
2817
|
+
});
|
|
2818
|
+
const currentLock = await readSkillsLock(options.cwd);
|
|
2819
|
+
const baseLock = await createBaseLock(options.cwd, manifest, currentLock);
|
|
2820
|
+
const currentEntry = baseLock.skills[options.skillName];
|
|
2821
|
+
if (!currentEntry) throw new SkillError({
|
|
2822
|
+
code: codes_ErrorCode.SKILL_NOT_FOUND,
|
|
2823
|
+
skillName: options.skillName,
|
|
2824
|
+
message: `Skill "${options.skillName}" is missing from the resolved lockfile state`
|
|
2825
|
+
});
|
|
2826
|
+
const editDir = await resolveEditDir(options.cwd, options.skillName, options.editDir);
|
|
2827
|
+
if (options.editDir) await ensureEditDirDoesNotExist(editDir);
|
|
2828
|
+
const baseEntry = getUnpatchedBaseEntry(currentEntry);
|
|
2829
|
+
await extractSkillToDir(options.cwd, baseEntry, editDir);
|
|
2830
|
+
const existingPatchPath = manifest.patchedSkills?.[options.skillName];
|
|
2831
|
+
if (existingPatchPath && !options.ignoreExisting) await applySkillPatch(editDir, node_path.resolve(options.cwd, existingPatchPath));
|
|
2832
|
+
await writePatchEditState(editDir, {
|
|
2833
|
+
version: 1,
|
|
2834
|
+
skillName: options.skillName,
|
|
2835
|
+
originalSpecifier: manifest.skills[options.skillName],
|
|
2836
|
+
baseEntry
|
|
2837
|
+
});
|
|
2838
|
+
console.info(editDir);
|
|
2839
|
+
return {
|
|
2840
|
+
status: 'patched',
|
|
2841
|
+
skillName: options.skillName,
|
|
2842
|
+
editDir,
|
|
2843
|
+
originalSpecifier: manifest.skills[options.skillName]
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
async function patchCommit_createBaseLock(cwd, manifest, currentLock) {
|
|
2847
|
+
if (currentLock && await isLockInSync(cwd, manifest, currentLock)) return {
|
|
2848
|
+
...currentLock,
|
|
2849
|
+
skills: {
|
|
2850
|
+
...currentLock.skills
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
return syncSkillsLock(cwd, manifest, currentLock);
|
|
2854
|
+
}
|
|
2855
|
+
function resolvePatchFilePath(cwd, skillName, existingPatchPath, patchesDir) {
|
|
2856
|
+
if (patchesDir) return node_path.resolve(cwd, patchesDir, `${skillName}.patch`);
|
|
2857
|
+
if (existingPatchPath) return node_path.resolve(cwd, existingPatchPath);
|
|
2858
|
+
return node_path.resolve(cwd, 'patches', `${skillName}.patch`);
|
|
2859
|
+
}
|
|
2860
|
+
async function patchCommitCommand(options) {
|
|
2861
|
+
const manifest = await readSkillsManifest(options.cwd);
|
|
2862
|
+
if (!manifest) throw new ManifestError({
|
|
2863
|
+
code: codes_ErrorCode.MANIFEST_NOT_FOUND,
|
|
2864
|
+
filePath: `${options.cwd}/skills.json`,
|
|
2865
|
+
message: 'No skills.json found in the current directory. Run "spm init" to create one.'
|
|
2866
|
+
});
|
|
2867
|
+
const editDir = node_path.resolve(options.cwd, options.editDir);
|
|
2868
|
+
const editState = await readPatchEditState(editDir);
|
|
2869
|
+
if (!(editState.skillName in manifest.skills)) throw new SkillError({
|
|
2870
|
+
code: codes_ErrorCode.SKILL_NOT_FOUND,
|
|
2871
|
+
skillName: editState.skillName,
|
|
2872
|
+
message: `Unknown skill: ${editState.skillName}`
|
|
2873
|
+
});
|
|
2874
|
+
if (manifest.skills[editState.skillName] !== editState.originalSpecifier) throw new SkillError({
|
|
2875
|
+
code: codes_ErrorCode.VALIDATION_ERROR,
|
|
2876
|
+
skillName: editState.skillName,
|
|
2877
|
+
message: `Skill "${editState.skillName}" changed since "spm patch" created ${editDir}`
|
|
2878
|
+
});
|
|
2879
|
+
const baseDir = await mkdtemp(node_path.join(tmpdir(), `skills-pm-patch-base-${editState.skillName}-`));
|
|
2880
|
+
try {
|
|
2881
|
+
await extractSkillToDir(options.cwd, editState.baseEntry, baseDir);
|
|
2882
|
+
const patchContent = await generateSkillPatch(baseDir, editDir);
|
|
2883
|
+
if (!patchContent.trim()) throw new SkillError({
|
|
2884
|
+
code: codes_ErrorCode.VALIDATION_ERROR,
|
|
2885
|
+
skillName: editState.skillName,
|
|
2886
|
+
message: `No changes found in ${editDir}`
|
|
2887
|
+
});
|
|
2888
|
+
const patchFilePath = resolvePatchFilePath(options.cwd, editState.skillName, manifest.patchedSkills?.[editState.skillName], options.patchesDir);
|
|
2889
|
+
await mkdir(node_path.dirname(patchFilePath), {
|
|
2890
|
+
recursive: true
|
|
2891
|
+
});
|
|
2892
|
+
await writeFile(patchFilePath, patchContent, 'utf8');
|
|
2893
|
+
const relativePatchPath = toPortableRelativePath(options.cwd, patchFilePath);
|
|
2894
|
+
const nextManifest = {
|
|
2895
|
+
...manifest,
|
|
2896
|
+
patchedSkills: {
|
|
2897
|
+
...manifest.patchedSkills ?? {},
|
|
2898
|
+
[editState.skillName]: relativePatchPath
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
const currentLock = await readSkillsLock(options.cwd);
|
|
2902
|
+
const baseLock = await patchCommit_createBaseLock(options.cwd, manifest, currentLock);
|
|
2903
|
+
const patchedEntry = await attachManifestPatchToEntry(options.cwd, nextManifest, editState.skillName, editState.baseEntry);
|
|
2904
|
+
const nextLock = {
|
|
2905
|
+
...baseLock,
|
|
2906
|
+
installDir: nextManifest.installDir ?? '.agents/skills',
|
|
2907
|
+
linkTargets: nextManifest.linkTargets ?? [],
|
|
2908
|
+
skills: {
|
|
2909
|
+
...baseLock.skills,
|
|
2910
|
+
[editState.skillName]: patchedEntry
|
|
2911
|
+
}
|
|
2912
|
+
};
|
|
2913
|
+
const runtimeLock = await withBundledSelfSkillLock(options.cwd, nextManifest, nextLock);
|
|
2914
|
+
await fetchSkillsFromLock(options.cwd, nextManifest, runtimeLock);
|
|
2915
|
+
await linkSkillsFromLock(options.cwd, nextManifest, runtimeLock);
|
|
2916
|
+
await writeSkillsManifest(options.cwd, nextManifest);
|
|
2917
|
+
await writeSkillsLock(options.cwd, nextLock);
|
|
2918
|
+
console.info(relativePatchPath);
|
|
2919
|
+
return {
|
|
2920
|
+
status: 'patched',
|
|
2921
|
+
skillName: editState.skillName,
|
|
2922
|
+
patchFile: patchFilePath
|
|
2923
|
+
};
|
|
2924
|
+
} finally{
|
|
2925
|
+
await rm(baseDir, {
|
|
2926
|
+
recursive: true,
|
|
2927
|
+
force: true
|
|
2928
|
+
}).catch(()=>{});
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
function normalizeValue(value) {
|
|
2932
|
+
if (Array.isArray(value)) return value.map((item)=>normalizeValue(item));
|
|
2933
|
+
if (value && 'object' == typeof value) return Object.fromEntries(Object.entries(value).sort(([leftKey], [rightKey])=>leftKey.localeCompare(rightKey)).map(([key, nestedValue])=>[
|
|
2934
|
+
key,
|
|
2935
|
+
normalizeValue(nestedValue)
|
|
2936
|
+
]));
|
|
2937
|
+
return value;
|
|
2938
|
+
}
|
|
2939
|
+
function stableStringify(value) {
|
|
2940
|
+
return JSON.stringify(normalizeValue(value));
|
|
2941
|
+
}
|
|
2539
2942
|
function createEmptyResult() {
|
|
2540
2943
|
return {
|
|
2541
2944
|
status: 'skipped',
|
|
@@ -2545,7 +2948,7 @@ function createEmptyResult() {
|
|
|
2545
2948
|
failed: []
|
|
2546
2949
|
};
|
|
2547
2950
|
}
|
|
2548
|
-
function
|
|
2951
|
+
function update_createBaseLock(_cwd, currentLock) {
|
|
2549
2952
|
if (currentLock) return {
|
|
2550
2953
|
...currentLock,
|
|
2551
2954
|
skills: {
|
|
@@ -2574,7 +2977,7 @@ async function updateCommand(options) {
|
|
|
2574
2977
|
message: `Unknown skill: ${skillName}`
|
|
2575
2978
|
});
|
|
2576
2979
|
const result = createEmptyResult();
|
|
2577
|
-
const candidateLock =
|
|
2980
|
+
const candidateLock = update_createBaseLock(options.cwd, currentLock);
|
|
2578
2981
|
candidateLock.installDir = manifest.installDir ?? '.agents/skills';
|
|
2579
2982
|
candidateLock.linkTargets = manifest.linkTargets ?? [];
|
|
2580
2983
|
for (const skillName of targetSkills){
|
|
@@ -2589,20 +2992,13 @@ async function updateCommand(options) {
|
|
|
2589
2992
|
continue;
|
|
2590
2993
|
}
|
|
2591
2994
|
const { entry } = await resolveLockEntry(options.cwd, specifier);
|
|
2995
|
+
const nextEntry = await attachManifestPatchToEntry(options.cwd, manifest, skillName, entry);
|
|
2592
2996
|
const previous = currentLock?.skills[skillName];
|
|
2593
|
-
if (previous
|
|
2997
|
+
if (previous && stableStringify(previous) === stableStringify(nextEntry)) {
|
|
2594
2998
|
result.unchanged.push(skillName);
|
|
2595
2999
|
continue;
|
|
2596
3000
|
}
|
|
2597
|
-
|
|
2598
|
-
result.unchanged.push(skillName);
|
|
2599
|
-
continue;
|
|
2600
|
-
}
|
|
2601
|
-
if (previous?.resolution.type === 'file' && 'file' === entry.resolution.type && previous.specifier === entry.specifier && previous.digest === entry.digest) {
|
|
2602
|
-
result.unchanged.push(skillName);
|
|
2603
|
-
continue;
|
|
2604
|
-
}
|
|
2605
|
-
candidateLock.skills[skillName] = entry;
|
|
3001
|
+
candidateLock.skills[skillName] = nextEntry;
|
|
2606
3002
|
result.updated.push(skillName);
|
|
2607
3003
|
} catch (error) {
|
|
2608
3004
|
result.failed.push({
|
|
@@ -2626,6 +3022,8 @@ function createHandlers(overrides) {
|
|
|
2626
3022
|
return {
|
|
2627
3023
|
addCommand: addCommand,
|
|
2628
3024
|
installCommand: installCommand,
|
|
3025
|
+
patchCommitCommand: patchCommitCommand,
|
|
3026
|
+
patchCommand: patchCommand,
|
|
2629
3027
|
updateCommand: updateCommand,
|
|
2630
3028
|
initCommand: initCommand,
|
|
2631
3029
|
...overrides
|
|
@@ -2661,6 +3059,17 @@ async function runCli(argv, context = {}) {
|
|
|
2661
3059
|
cwd,
|
|
2662
3060
|
frozenLockfile: options.frozenLockfile
|
|
2663
3061
|
}));
|
|
3062
|
+
cli.command('patch <skill>').option('--edit-dir <dir>', 'Directory to extract the editable skill into').option('--ignore-existing', 'Ignore an existing committed patch while preparing the edit dir').action(async (skill, options)=>handlers.patchCommand({
|
|
3063
|
+
cwd,
|
|
3064
|
+
skillName: skill,
|
|
3065
|
+
editDir: options.editDir,
|
|
3066
|
+
ignoreExisting: options.ignoreExisting
|
|
3067
|
+
}));
|
|
3068
|
+
cli.command('patch-commit <editDir>').option('--patches-dir <dir>', 'Directory to save the generated patch file into').action(async (editDir, options)=>handlers.patchCommitCommand({
|
|
3069
|
+
cwd,
|
|
3070
|
+
editDir,
|
|
3071
|
+
patchesDir: options.patchesDir
|
|
3072
|
+
}));
|
|
2664
3073
|
cli.command('update [...skills]').action(async (skills = [])=>handlers.updateCommand({
|
|
2665
3074
|
cwd,
|
|
2666
3075
|
skills: skills.length > 0 ? skills : void 0
|
|
@@ -2696,4 +3105,4 @@ async function runCli(argv, context = {}) {
|
|
|
2696
3105
|
throw error;
|
|
2697
3106
|
}
|
|
2698
3107
|
}
|
|
2699
|
-
export { FileSystemError, GitError, ManifestError, NetworkError, ParseError, SkillError, SpmError, addCommand, cloneAndDiscover, codes_ErrorCode as ErrorCode, convertNodeError, createInstallProgressReporter, discoverSkillsInDir, expandSkillsManifest, fetchSkillsFromLock, formatErrorForDisplay, getExitCode, initCommand, installCommand, installSkills, installStageHooks, isLockInSync, isSpmError, linkSkillsFromLock, listRepoSkills, normalizeSkillsManifest, normalizeSpecifier, parseGitHubUrl, parseOwnerRepo, parseSpecifier, readSkillsLock, readSkillsManifest, resolveLockEntry, runCli, updateCommand, writeSkillsLock, writeSkillsManifest };
|
|
3108
|
+
export { FileSystemError, GitError, ManifestError, NetworkError, ParseError, SkillError, SpmError, addCommand, cloneAndDiscover, codes_ErrorCode as ErrorCode, convertNodeError, createInstallProgressReporter, discoverSkillsInDir, expandSkillsManifest, fetchSkillsFromLock, formatErrorForDisplay, getExitCode, initCommand, installCommand, installSkills, installStageHooks, isLockInSync, isSpmError, linkSkillsFromLock, listRepoSkills, normalizeSkillsManifest, normalizeSpecifier, parseGitHubUrl, parseOwnerRepo, parseSpecifier, patchCommand, patchCommitCommand, 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.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,10 +20,12 @@
|
|
|
20
20
|
"skills.schema.json"
|
|
21
21
|
],
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"@microsoft/api-extractor": "^7.58.5",
|
|
23
24
|
"@clack/prompts": "^1.1.0",
|
|
24
25
|
"cac": "^7.0.0",
|
|
25
26
|
"picocolors": "^1.1.1",
|
|
26
27
|
"semver": "^7.7.2",
|
|
28
|
+
"@types/semver": "^7.7.1",
|
|
27
29
|
"tar": "^7.4.3",
|
|
28
30
|
"yaml": "^2.8.1",
|
|
29
31
|
"zod": "^4.3.6",
|
package/skills.schema.json
CHANGED
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
"additionalProperties": {
|
|
34
34
|
"type": "string"
|
|
35
35
|
}
|
|
36
|
+
},
|
|
37
|
+
"patchedSkills": {
|
|
38
|
+
"description": "Map of skill names to their patch file paths",
|
|
39
|
+
"type": "object",
|
|
40
|
+
"propertyNames": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"additionalProperties": {
|
|
44
|
+
"type": "string"
|
|
45
|
+
}
|
|
36
46
|
}
|
|
37
47
|
},
|
|
38
48
|
"required": [
|