skills-package-manager 0.1.1

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.
Files changed (66) hide show
  1. package/README.md +105 -0
  2. package/dist/113.js +355 -0
  3. package/dist/bin/skills-pm.js +6 -0
  4. package/dist/bin/skills.js +6 -0
  5. package/dist/index.js +1 -0
  6. package/dist/src/bin/skills-pm.d.ts +2 -0
  7. package/dist/src/bin/skills.d.ts +2 -0
  8. package/dist/src/cli/runCli.d.ts +16 -0
  9. package/dist/src/commands/add.d.ts +5 -0
  10. package/dist/src/commands/install.d.ts +15 -0
  11. package/dist/src/config/readSkillsLock.d.ts +2 -0
  12. package/dist/src/config/readSkillsManifest.d.ts +2 -0
  13. package/dist/src/config/syncSkillsLock.d.ts +2 -0
  14. package/dist/src/config/types.d.ts +43 -0
  15. package/dist/src/config/writeSkillsLock.d.ts +2 -0
  16. package/dist/src/config/writeSkillsManifest.d.ts +2 -0
  17. package/dist/src/index.d.ts +3 -0
  18. package/dist/src/install/installSkills.d.ts +13 -0
  19. package/dist/src/install/installState.d.ts +2 -0
  20. package/dist/src/install/links.d.ts +1 -0
  21. package/dist/src/install/materializeGitSkill.d.ts +1 -0
  22. package/dist/src/install/materializeLocalSkill.d.ts +1 -0
  23. package/dist/src/install/pruneManagedSkills.d.ts +1 -0
  24. package/dist/src/specifiers/normalizeSpecifier.d.ts +2 -0
  25. package/dist/src/specifiers/parseSpecifier.d.ts +5 -0
  26. package/dist/src/utils/fs.d.ts +4 -0
  27. package/dist/src/utils/hash.d.ts +1 -0
  28. package/dist/test/add.test.d.ts +1 -0
  29. package/dist/test/install.test.d.ts +1 -0
  30. package/dist/test/manifest.test.d.ts +1 -0
  31. package/dist/test/specifiers.test.d.ts +1 -0
  32. package/package.json +25 -0
  33. package/rslib.config.ts +21 -0
  34. package/src/bin/skills-pm.ts +7 -0
  35. package/src/bin/skills.ts +7 -0
  36. package/src/cli/prompt.ts +36 -0
  37. package/src/cli/runCli.ts +45 -0
  38. package/src/commands/add.ts +110 -0
  39. package/src/commands/install.ts +5 -0
  40. package/src/config/readSkillsLock.ts +18 -0
  41. package/src/config/readSkillsManifest.ts +22 -0
  42. package/src/config/syncSkillsLock.ts +75 -0
  43. package/src/config/types.ts +37 -0
  44. package/src/config/writeSkillsLock.ts +9 -0
  45. package/src/config/writeSkillsManifest.ts +14 -0
  46. package/src/github/listSkills.ts +170 -0
  47. package/src/github/types.ts +5 -0
  48. package/src/index.ts +5 -0
  49. package/src/install/installSkills.ts +78 -0
  50. package/src/install/installState.ts +20 -0
  51. package/src/install/links.ts +9 -0
  52. package/src/install/materializeGitSkill.ts +33 -0
  53. package/src/install/materializeLocalSkill.ts +35 -0
  54. package/src/install/pruneManagedSkills.ts +50 -0
  55. package/src/specifiers/normalizeSpecifier.ts +29 -0
  56. package/src/specifiers/parseSpecifier.ts +45 -0
  57. package/src/utils/fs.ts +19 -0
  58. package/src/utils/hash.ts +5 -0
  59. package/test/add.test.ts +75 -0
  60. package/test/fixtures/local-source/skills/hello-skill/SKILL.md +3 -0
  61. package/test/fixtures/local-source/skills/hello-skill/references/example.md +1 -0
  62. package/test/github.test.ts +120 -0
  63. package/test/install.test.ts +169 -0
  64. package/test/manifest.test.ts +19 -0
  65. package/test/specifiers.test.ts +43 -0
  66. package/tsconfig.json +8 -0
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # skills-pm
2
+
3
+ Core library and CLI for managing agent skills.
4
+
5
+ ## CLI Usage
6
+
7
+ ### `skills add`
8
+
9
+ Add skills to your project.
10
+
11
+ ```bash
12
+ # Interactive — clone repo, discover skills, select via multiselect prompt
13
+ skills add owner/repo
14
+ skills add https://github.com/owner/repo
15
+
16
+ # Non-interactive — add a specific skill by name
17
+ skills add owner/repo --skill find-skills
18
+
19
+ # Direct specifier — skip discovery
20
+ skills add https://github.com/owner/repo.git#path:/skills/my-skill
21
+ skills add file:./local-source#path:/skills/my-skill
22
+ ```
23
+
24
+ #### How it works
25
+
26
+ When given `owner/repo` or a GitHub URL:
27
+
28
+ 1. Shallow-clones the repository into a temp directory
29
+ 2. Scans for `SKILL.md` files (checks root, then `skills/`, `.agents/skills/`, etc.)
30
+ 3. Presents an interactive multiselect prompt (powered by [@clack/prompts](https://github.com/bombshell-dev/clack))
31
+ 4. Writes selected skills to `skills.json` and resolves `skills-lock.yaml`
32
+ 5. Cleans up the temp directory
33
+
34
+ ### `skills install`
35
+
36
+ Install all skills declared in `skills.json`:
37
+
38
+ ```bash
39
+ skills install
40
+ ```
41
+
42
+ This resolves each skill from its specifier, materializes it into `installDir` (default `.agents/skills/`), and creates symlinks for each `linkTarget`.
43
+
44
+ ## Programmatic API
45
+
46
+ ```typescript
47
+ import { addCommand, installCommand, listRepoSkills } from 'skills-pm'
48
+
49
+ // Add a skill
50
+ await addCommand({
51
+ cwd: process.cwd(),
52
+ specifier: 'vercel-labs/skills',
53
+ skill: 'find-skills',
54
+ })
55
+
56
+ // Install all skills from skills.json
57
+ await installCommand({ cwd: process.cwd() })
58
+
59
+ // List skills in a GitHub repo (clone + scan)
60
+ const skills = await listRepoSkills('vercel-labs', 'skills')
61
+ // => [{ name: 'find-skills', description: '...', path: '/skills/find-skills' }]
62
+ ```
63
+
64
+ ## Specifier Format
65
+
66
+ ```
67
+ <source>#[ref&]path:<skill-path>
68
+ ```
69
+
70
+ | Part | Description | Example |
71
+ |------|-------------|---------|
72
+ | `source` | Git URL or `file:` path | `https://github.com/o/r.git`, `file:./local` |
73
+ | `ref` | Optional git ref | `main`, `v1.0.0`, `HEAD` |
74
+ | `path` | Path to skill directory within source | `/skills/my-skill` |
75
+
76
+ ### Resolution Types
77
+
78
+ - **`git`** — Clones the repo, resolves commit hash, copies skill files
79
+ - **`file`** — Reads from local filesystem, computes content digest
80
+
81
+ ## Architecture
82
+
83
+ ```
84
+ src/
85
+ ├── bin/ # CLI entry points (skills-pm, skills)
86
+ ├── cli/ # CLI runner and interactive prompts
87
+ ├── commands/ # add, install command implementations
88
+ ├── config/ # skills.json / skills-lock.yaml read/write
89
+ ├── github/ # Git clone + skill discovery (listSkills)
90
+ ├── install/ # Skill materialization, linking, pruning
91
+ ├── specifiers/ # Specifier parsing and normalization
92
+ └── utils/ # Hashing, filesystem helpers
93
+ ```
94
+
95
+ ## Build
96
+
97
+ ```bash
98
+ pnpm build # Builds with Rslib (ESM output + DTS)
99
+ ```
100
+
101
+ ## Test
102
+
103
+ ```bash
104
+ pnpm test # Runs tests with Rstest
105
+ ```
package/dist/113.js ADDED
@@ -0,0 +1,355 @@
1
+ import { cp as promises_cp, lstat, mkdir, mkdtemp, readFile, readdir, rm as promises_rm, symlink, writeFile } from "node:fs/promises";
2
+ import node_path from "node:path";
3
+ import yaml from "yaml";
4
+ import { createHash } from "node:crypto";
5
+ import { execFile } from "node:child_process";
6
+ import { promisify } from "node:util";
7
+ import { tmpdir } from "node:os";
8
+ async function readSkillsLock(rootDir) {
9
+ const filePath = node_path.join(rootDir, 'skills-lock.yaml');
10
+ try {
11
+ const raw = await readFile(filePath, 'utf8');
12
+ return yaml.parse(raw);
13
+ } catch (error) {
14
+ if ('ENOENT' === error.code) return null;
15
+ throw error;
16
+ }
17
+ }
18
+ async function readSkillsManifest(rootDir) {
19
+ const filePath = node_path.join(rootDir, 'skills.json');
20
+ try {
21
+ const raw = await readFile(filePath, 'utf8');
22
+ const json = JSON.parse(raw);
23
+ return {
24
+ installDir: json.installDir ?? '.agents/skills',
25
+ linkTargets: json.linkTargets ?? [],
26
+ skills: json.skills ?? {}
27
+ };
28
+ } catch (error) {
29
+ if ('ENOENT' === error.code) return null;
30
+ throw error;
31
+ }
32
+ }
33
+ function parseSpecifier(specifier) {
34
+ const firstHashIndex = specifier.indexOf('#');
35
+ const secondHashIndex = firstHashIndex >= 0 ? specifier.indexOf('#', firstHashIndex + 1) : -1;
36
+ if (secondHashIndex >= 0) throw new Error('Invalid specifier: multiple # fragments are not supported');
37
+ const hashIndex = firstHashIndex;
38
+ const sourcePart = hashIndex >= 0 ? specifier.slice(0, hashIndex) : specifier;
39
+ const fragment = hashIndex >= 0 ? specifier.slice(hashIndex + 1) : '';
40
+ if (!sourcePart) throw new Error('Specifier source is required');
41
+ if (!fragment) return {
42
+ sourcePart,
43
+ ref: null,
44
+ path: ''
45
+ };
46
+ const parts = fragment.split('&').filter(Boolean);
47
+ let ref = null;
48
+ let parsedPath = '';
49
+ for (const part of parts){
50
+ if (part.startsWith('path:')) {
51
+ parsedPath = part.slice(5);
52
+ continue;
53
+ }
54
+ if (null === ref) ref = part;
55
+ }
56
+ return {
57
+ sourcePart,
58
+ ref,
59
+ path: parsedPath
60
+ };
61
+ }
62
+ function normalizeSpecifier(specifier) {
63
+ const parsed = parseSpecifier(specifier);
64
+ const type = parsed.sourcePart.startsWith('file:') ? 'file' : parsed.sourcePart.startsWith('npm:') ? 'npm' : 'git';
65
+ const skillPath = parsed.path || '/';
66
+ const skillName = node_path.posix.basename(skillPath);
67
+ const normalized = parsed.ref ? `${parsed.sourcePart}#${parsed.ref}&path:${skillPath}` : parsed.path ? `${parsed.sourcePart}#path:${skillPath}` : parsed.sourcePart;
68
+ return {
69
+ type,
70
+ source: parsed.sourcePart,
71
+ ref: parsed.ref,
72
+ path: skillPath,
73
+ normalized,
74
+ skillName
75
+ };
76
+ }
77
+ function sha256(content) {
78
+ return `sha256-${createHash('sha256').update(content).digest('hex')}`;
79
+ }
80
+ const execFileAsync = promisify(execFile);
81
+ async function resolveGitCommit(url, ref) {
82
+ const target = ref ?? 'HEAD';
83
+ const { stdout } = await execFileAsync('git', [
84
+ 'ls-remote',
85
+ url,
86
+ target
87
+ ]);
88
+ const line = stdout.trim().split('\n')[0];
89
+ const commit = line?.split('\t')[0]?.trim();
90
+ if (!commit) throw new Error(`Unable to resolve git ref ${target} for ${url}`);
91
+ return commit;
92
+ }
93
+ async function createLockEntry(cwd, specifier) {
94
+ const normalized = normalizeSpecifier(specifier);
95
+ if ('file' === normalized.type) {
96
+ const sourceRoot = node_path.resolve(cwd, normalized.source.slice(5));
97
+ return {
98
+ skillName: normalized.skillName,
99
+ entry: {
100
+ specifier: normalized.normalized,
101
+ resolution: {
102
+ type: 'file',
103
+ path: sourceRoot
104
+ },
105
+ digest: sha256(`${sourceRoot}:${normalized.path}`)
106
+ }
107
+ };
108
+ }
109
+ if ('git' === normalized.type) {
110
+ const commit = await resolveGitCommit(normalized.source, normalized.ref);
111
+ return {
112
+ skillName: normalized.skillName,
113
+ entry: {
114
+ specifier: normalized.normalized,
115
+ resolution: {
116
+ type: 'git',
117
+ url: normalized.source,
118
+ commit,
119
+ path: normalized.path
120
+ },
121
+ digest: sha256(`${normalized.source}:${commit}:${normalized.path}`)
122
+ }
123
+ };
124
+ }
125
+ throw new Error(`Unsupported specifier type in 0.1.0 core flow: ${normalized.type}`);
126
+ }
127
+ async function syncSkillsLock(cwd, manifest, existingLock) {
128
+ const nextSkills = {};
129
+ for (const specifier of Object.values(manifest.skills)){
130
+ const { skillName, entry } = await createLockEntry(cwd, specifier);
131
+ nextSkills[skillName] = entry;
132
+ }
133
+ return {
134
+ lockfileVersion: '0.1',
135
+ installDir: manifest.installDir ?? '.agents/skills',
136
+ linkTargets: manifest.linkTargets ?? [],
137
+ skills: nextSkills
138
+ };
139
+ }
140
+ async function writeSkillsLock(rootDir, lockfile) {
141
+ const filePath = node_path.join(rootDir, 'skills-lock.yaml');
142
+ await writeFile(filePath, yaml.stringify(lockfile), 'utf8');
143
+ }
144
+ async function writeSkillsManifest(rootDir, manifest) {
145
+ const filePath = node_path.join(rootDir, 'skills.json');
146
+ const nextManifest = {
147
+ installDir: manifest.installDir ?? '.agents/skills',
148
+ linkTargets: manifest.linkTargets ?? [],
149
+ skills: manifest.skills
150
+ };
151
+ await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
152
+ }
153
+ async function addCommand(options) {
154
+ const normalized = normalizeSpecifier(options.specifier);
155
+ const existingManifest = await readSkillsManifest(options.cwd) ?? {
156
+ installDir: '.agents/skills',
157
+ linkTargets: [],
158
+ skills: {}
159
+ };
160
+ const existing = existingManifest.skills[normalized.skillName];
161
+ if (existing && existing !== normalized.normalized) throw new Error(`Skill ${normalized.skillName} already exists with a different specifier`);
162
+ existingManifest.skills[normalized.skillName] = normalized.normalized;
163
+ await writeSkillsManifest(options.cwd, existingManifest);
164
+ const existingLock = await readSkillsLock(options.cwd);
165
+ const lockfile = await syncSkillsLock(options.cwd, existingManifest, existingLock);
166
+ await writeSkillsLock(options.cwd, lockfile);
167
+ return {
168
+ skillName: normalized.skillName,
169
+ specifier: normalized.normalized
170
+ };
171
+ }
172
+ async function ensureDir(dirPath) {
173
+ await mkdir(dirPath, {
174
+ recursive: true
175
+ });
176
+ }
177
+ async function replaceSymlink(target, linkPath) {
178
+ await promises_rm(linkPath, {
179
+ recursive: true,
180
+ force: true
181
+ });
182
+ await symlink(target, linkPath);
183
+ }
184
+ async function writeJson(filePath, value) {
185
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
186
+ }
187
+ async function linkSkill(rootDir, installDir, linkTarget, skillName) {
188
+ const absoluteTarget = node_path.join(rootDir, installDir, skillName);
189
+ const absoluteLink = node_path.join(rootDir, linkTarget, skillName);
190
+ await ensureDir(node_path.dirname(absoluteLink));
191
+ await replaceSymlink(absoluteTarget, absoluteLink);
192
+ }
193
+ async function readInstallState(rootDir) {
194
+ const filePath = node_path.join(rootDir, '.agents/skills/.skills-pm-install-state.json');
195
+ try {
196
+ return JSON.parse(await readFile(filePath, 'utf8'));
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+ async function writeInstallState(rootDir, value) {
202
+ const dirPath = node_path.join(rootDir, '.agents/skills');
203
+ await ensureDir(dirPath);
204
+ const filePath = node_path.join(dirPath, '.skills-pm-install-state.json');
205
+ await writeJson(filePath, value);
206
+ }
207
+ async function materializeLocalSkill(rootDir, skillName, sourceRoot, sourcePath, installDir) {
208
+ const relativeSkillPath = sourcePath.replace(/^\//, '');
209
+ const absoluteSkillPath = node_path.join(sourceRoot, relativeSkillPath);
210
+ const skillDocPath = node_path.join(absoluteSkillPath, 'SKILL.md');
211
+ let skillDoc = '';
212
+ try {
213
+ skillDoc = await readFile(skillDocPath, 'utf8');
214
+ } catch {
215
+ throw new Error(`Invalid skill at ${absoluteSkillPath}: missing SKILL.md`);
216
+ }
217
+ if (!skillDoc) throw new Error(`Invalid skill at ${absoluteSkillPath}: missing SKILL.md`);
218
+ const targetDir = node_path.join(rootDir, installDir, skillName);
219
+ await ensureDir(node_path.dirname(targetDir));
220
+ await promises_cp(absoluteSkillPath, targetDir, {
221
+ recursive: true,
222
+ force: true
223
+ });
224
+ await writeJson(node_path.join(targetDir, '.skills-pm.json'), {
225
+ name: skillName,
226
+ installedBy: 'skills-pm',
227
+ version: '0.1.0'
228
+ });
229
+ }
230
+ const materializeGitSkill_execFileAsync = promisify(execFile);
231
+ async function materializeGitSkill(rootDir, skillName, repoUrl, commit, sourcePath, installDir) {
232
+ const checkoutRoot = await mkdtemp(node_path.join(tmpdir(), 'skills-pm-git-checkout-'));
233
+ try {
234
+ await materializeGitSkill_execFileAsync('git', [
235
+ 'clone',
236
+ '--depth',
237
+ '1',
238
+ repoUrl,
239
+ checkoutRoot
240
+ ]);
241
+ if (commit && 'HEAD' !== commit) await materializeGitSkill_execFileAsync('git', [
242
+ 'checkout',
243
+ commit
244
+ ], {
245
+ cwd: checkoutRoot
246
+ });
247
+ const skillDocPath = node_path.join(checkoutRoot, sourcePath.replace(/^\//, ''), 'SKILL.md');
248
+ await readFile(skillDocPath, 'utf8');
249
+ await materializeLocalSkill(rootDir, skillName, checkoutRoot, sourcePath, installDir);
250
+ } finally{
251
+ await promises_rm(checkoutRoot, {
252
+ recursive: true,
253
+ force: true
254
+ });
255
+ }
256
+ }
257
+ async function isManagedSkillDir(dirPath) {
258
+ try {
259
+ const marker = JSON.parse(await readFile(node_path.join(dirPath, '.skills-pm.json'), 'utf8'));
260
+ return marker?.installedBy === 'skills-pm';
261
+ } catch {
262
+ return false;
263
+ }
264
+ }
265
+ async function pruneManagedSkills(rootDir, installDir, linkTargets, wantedSkillNames) {
266
+ const wanted = new Set(wantedSkillNames);
267
+ const absoluteInstallDir = node_path.join(rootDir, installDir);
268
+ try {
269
+ const entries = await readdir(absoluteInstallDir);
270
+ for (const entry of entries){
271
+ if (entry.startsWith('.')) continue;
272
+ const skillDir = node_path.join(absoluteInstallDir, entry);
273
+ if (await isManagedSkillDir(skillDir)) {
274
+ if (!wanted.has(entry)) {
275
+ await promises_rm(skillDir, {
276
+ recursive: true,
277
+ force: true
278
+ });
279
+ for (const linkTarget of linkTargets){
280
+ const linkPath = node_path.join(rootDir, linkTarget, entry);
281
+ try {
282
+ const stat = await lstat(linkPath);
283
+ if (stat.isSymbolicLink() || stat.isDirectory() || stat.isFile()) await promises_rm(linkPath, {
284
+ recursive: true,
285
+ force: true
286
+ });
287
+ } catch {}
288
+ }
289
+ }
290
+ }
291
+ }
292
+ } catch {}
293
+ }
294
+ function extractSkillPath(specifier, skillName) {
295
+ const marker = '#path:';
296
+ const index = specifier.indexOf(marker);
297
+ if (index >= 0) return specifier.slice(index + marker.length);
298
+ return `/${skillName}`;
299
+ }
300
+ async function installSkills(rootDir) {
301
+ const manifest = await readSkillsManifest(rootDir);
302
+ if (!manifest) return {
303
+ status: 'skipped',
304
+ reason: 'manifest-missing'
305
+ };
306
+ const currentLock = await readSkillsLock(rootDir);
307
+ const lockfile = await syncSkillsLock(rootDir, manifest, currentLock);
308
+ await writeSkillsLock(rootDir, lockfile);
309
+ const lockDigest = sha256(JSON.stringify(lockfile));
310
+ const state = await readInstallState(rootDir);
311
+ if (state?.lockDigest === lockDigest) return {
312
+ status: 'skipped',
313
+ reason: 'up-to-date'
314
+ };
315
+ const installDir = manifest.installDir ?? '.agents/skills';
316
+ const linkTargets = manifest.linkTargets ?? [];
317
+ await pruneManagedSkills(rootDir, installDir, linkTargets, Object.keys(lockfile.skills));
318
+ for (const [skillName, entry] of Object.entries(lockfile.skills)){
319
+ if ('file' === entry.resolution.type) await materializeLocalSkill(rootDir, skillName, entry.resolution.path, extractSkillPath(entry.specifier, skillName), installDir);
320
+ else if ('git' === entry.resolution.type) await materializeGitSkill(rootDir, skillName, entry.resolution.url, entry.resolution.commit, entry.resolution.path, installDir);
321
+ else throw new Error(`Unsupported resolution type in 0.1.0 core flow: ${entry.resolution.type}`);
322
+ for (const linkTarget of linkTargets)await linkSkill(rootDir, installDir, linkTarget, skillName);
323
+ }
324
+ await writeInstallState(rootDir, {
325
+ lockDigest,
326
+ installDir,
327
+ linkTargets,
328
+ installerVersion: '0.1.0',
329
+ installedAt: new Date().toISOString()
330
+ });
331
+ return {
332
+ status: 'installed',
333
+ installed: Object.keys(lockfile.skills)
334
+ };
335
+ }
336
+ async function installCommand(options) {
337
+ return installSkills(options.cwd);
338
+ }
339
+ async function runCli(argv) {
340
+ const [, , command, ...rest] = argv;
341
+ const cwd = process.cwd();
342
+ if ('add' === command) {
343
+ const specifier = rest[0];
344
+ if (!specifier) throw new Error('Missing required specifier');
345
+ return addCommand({
346
+ cwd,
347
+ specifier
348
+ });
349
+ }
350
+ if ('install' === command) return installCommand({
351
+ cwd
352
+ });
353
+ throw new Error(`Unknown command: ${command}`);
354
+ }
355
+ export { addCommand, installCommand, runCli };
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../113.js";
3
+ runCli(process.argv).catch((error)=>{
4
+ console.error(error instanceof Error ? error.message : error);
5
+ process.exitCode = 1;
6
+ });
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../113.js";
3
+ runCli(process.argv).catch((error)=>{
4
+ console.error(error instanceof Error ? error.message : error);
5
+ process.exitCode = 1;
6
+ });
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { addCommand, installCommand, runCli } from "./113.js";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,16 @@
1
+ export declare function runCli(argv: string[]): Promise<{
2
+ skillName: string;
3
+ specifier: string;
4
+ } | {
5
+ readonly status: "skipped";
6
+ readonly reason: "manifest-missing";
7
+ readonly installed?: undefined;
8
+ } | {
9
+ readonly status: "skipped";
10
+ readonly reason: "up-to-date";
11
+ readonly installed?: undefined;
12
+ } | {
13
+ readonly status: "installed";
14
+ readonly installed: string[];
15
+ readonly reason?: undefined;
16
+ }>;
@@ -0,0 +1,5 @@
1
+ import type { AddCommandOptions } from '../config/types';
2
+ export declare function addCommand(options: AddCommandOptions): Promise<{
3
+ skillName: string;
4
+ specifier: string;
5
+ }>;
@@ -0,0 +1,15 @@
1
+ export declare function installCommand(options: {
2
+ cwd: string;
3
+ }): Promise<{
4
+ readonly status: "skipped";
5
+ readonly reason: "manifest-missing";
6
+ readonly installed?: undefined;
7
+ } | {
8
+ readonly status: "skipped";
9
+ readonly reason: "up-to-date";
10
+ readonly installed?: undefined;
11
+ } | {
12
+ readonly status: "installed";
13
+ readonly installed: string[];
14
+ readonly reason?: undefined;
15
+ }>;
@@ -0,0 +1,2 @@
1
+ import type { SkillsLock } from './types';
2
+ export declare function readSkillsLock(rootDir: string): Promise<SkillsLock | null>;
@@ -0,0 +1,2 @@
1
+ import type { SkillsManifest } from './types';
2
+ export declare function readSkillsManifest(rootDir: string): Promise<SkillsManifest | null>;
@@ -0,0 +1,2 @@
1
+ import type { SkillsLock, SkillsManifest } from './types';
2
+ export declare function syncSkillsLock(cwd: string, manifest: SkillsManifest, existingLock: SkillsLock | null): Promise<SkillsLock>;
@@ -0,0 +1,43 @@
1
+ export type SkillsManifest = {
2
+ $schema?: string;
3
+ installDir?: string;
4
+ linkTargets?: string[];
5
+ skills: Record<string, string>;
6
+ };
7
+ export type NormalizedSpecifier = {
8
+ type: 'git' | 'file' | 'npm';
9
+ source: string;
10
+ ref: string | null;
11
+ path: string;
12
+ normalized: string;
13
+ skillName: string;
14
+ };
15
+ export type SkillsLockEntry = {
16
+ specifier: string;
17
+ resolution: {
18
+ type: 'file';
19
+ path: string;
20
+ } | {
21
+ type: 'git';
22
+ url: string;
23
+ commit: string;
24
+ path: string;
25
+ } | {
26
+ type: 'npm';
27
+ packageName: string;
28
+ version: string;
29
+ path: string;
30
+ integrity?: string;
31
+ };
32
+ digest: string;
33
+ };
34
+ export type SkillsLock = {
35
+ lockfileVersion: '0.1';
36
+ installDir: string;
37
+ linkTargets: string[];
38
+ skills: Record<string, SkillsLockEntry>;
39
+ };
40
+ export type AddCommandOptions = {
41
+ cwd: string;
42
+ specifier: string;
43
+ };
@@ -0,0 +1,2 @@
1
+ import type { SkillsLock } from './types';
2
+ export declare function writeSkillsLock(rootDir: string, lockfile: SkillsLock): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { SkillsManifest } from './types';
2
+ export declare function writeSkillsManifest(rootDir: string, manifest: SkillsManifest): Promise<void>;
@@ -0,0 +1,3 @@
1
+ export { addCommand } from './commands/add';
2
+ export { installCommand } from './commands/install';
3
+ export { runCli } from './cli/runCli';
@@ -0,0 +1,13 @@
1
+ export declare function installSkills(rootDir: string): Promise<{
2
+ readonly status: "skipped";
3
+ readonly reason: "manifest-missing";
4
+ readonly installed?: undefined;
5
+ } | {
6
+ readonly status: "skipped";
7
+ readonly reason: "up-to-date";
8
+ readonly installed?: undefined;
9
+ } | {
10
+ readonly status: "installed";
11
+ readonly installed: string[];
12
+ readonly reason?: undefined;
13
+ }>;
@@ -0,0 +1,2 @@
1
+ export declare function readInstallState(rootDir: string): Promise<any>;
2
+ export declare function writeInstallState(rootDir: string, value: unknown): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function linkSkill(rootDir: string, installDir: string, linkTarget: string, skillName: string): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function materializeGitSkill(rootDir: string, skillName: string, repoUrl: string, commit: string, sourcePath: string, installDir: string): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function materializeLocalSkill(rootDir: string, skillName: string, sourceRoot: string, sourcePath: string, installDir: string): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function pruneManagedSkills(rootDir: string, installDir: string, linkTargets: string[], wantedSkillNames: string[]): Promise<void>;
@@ -0,0 +1,2 @@
1
+ import type { NormalizedSpecifier } from '../config/types';
2
+ export declare function normalizeSpecifier(specifier: string): NormalizedSpecifier;
@@ -0,0 +1,5 @@
1
+ export declare function parseSpecifier(specifier: string): {
2
+ sourcePart: string;
3
+ ref: string | null;
4
+ path: string;
5
+ };
@@ -0,0 +1,4 @@
1
+ export declare function ensureDir(dirPath: string): Promise<void>;
2
+ export declare function replaceDir(from: string, to: string): Promise<void>;
3
+ export declare function replaceSymlink(target: string, linkPath: string): Promise<void>;
4
+ export declare function writeJson(filePath: string, value: unknown): Promise<void>;
@@ -0,0 +1 @@
1
+ export declare function sha256(content: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};