skills-package-manager 0.4.0 → 0.5.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 +3 -1
- package/dist/index.js +150 -63
- package/package.json +6 -3
- package/skills/skills-package-manager-cli/SKILL.md +70 -0
- package/skills.schema.json +44 -0
package/README.md
CHANGED
|
@@ -72,6 +72,7 @@ Default `skills.json` written by `spm init --yes`:
|
|
|
72
72
|
{
|
|
73
73
|
"installDir": ".agents/skills",
|
|
74
74
|
"linkTargets": [],
|
|
75
|
+
"selfSkill": false,
|
|
75
76
|
"skills": {}
|
|
76
77
|
}
|
|
77
78
|
```
|
|
@@ -85,6 +86,7 @@ spm install
|
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
This resolves each skill from its specifier, materializes it into `installDir` (default `.agents/skills/`), and creates symlinks for each `linkTarget`.
|
|
89
|
+
When `selfSkill` is `true`, `spm install` also installs the bundled `skills-package-manager-cli` skill so users get guidance for `skills.json`, `skills-lock.yaml`, and the `spm` workflow. This helper skill is not written to `skills-lock.yaml`.
|
|
88
90
|
|
|
89
91
|
### `spm update`
|
|
90
92
|
|
|
@@ -99,7 +101,7 @@ Behavior:
|
|
|
99
101
|
|
|
100
102
|
- Uses `skills.json` as the source of truth
|
|
101
103
|
- Re-resolves git refs and npm package targets
|
|
102
|
-
- Skips `link:` skills
|
|
104
|
+
- Skips `link:` skills, including the bundled self skill
|
|
103
105
|
- Fails immediately for unknown skill names
|
|
104
106
|
- Writes `skills-lock.yaml` only after fetch and link succeed
|
|
105
107
|
|
package/dist/index.js
CHANGED
|
@@ -3,16 +3,18 @@ import picocolors from "picocolors";
|
|
|
3
3
|
import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat as promises_stat, symlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import node_path, { basename, join } from "node:path";
|
|
5
5
|
import yaml from "yaml";
|
|
6
|
+
import { z } from "zod";
|
|
6
7
|
import { execFile } from "node:child_process";
|
|
7
8
|
import { homedir, tmpdir } from "node:os";
|
|
8
9
|
import { promisify } from "node:util";
|
|
9
10
|
import { createHash } from "node:crypto";
|
|
10
11
|
import semver from "semver";
|
|
11
|
-
import { createReadStream } from "node:fs";
|
|
12
|
+
import { accessSync, createReadStream } from "node:fs";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
12
14
|
import { x } from "tar";
|
|
13
15
|
import * as __rspack_external__clack_prompts_3cae1695 from "@clack/prompts";
|
|
14
16
|
var package_namespaceObject = {
|
|
15
|
-
rE: "0.
|
|
17
|
+
rE: "0.5.0"
|
|
16
18
|
};
|
|
17
19
|
const UNIVERSAL_AGENT_NAMES = [
|
|
18
20
|
'Amp',
|
|
@@ -133,6 +135,7 @@ var codes_ErrorCode = /*#__PURE__*/ function(ErrorCode) {
|
|
|
133
135
|
ErrorCode["LOCKFILE_NOT_FOUND"] = "ELOCKFILE";
|
|
134
136
|
ErrorCode["LOCKFILE_OUTDATED"] = "ELOCKOUTDATED";
|
|
135
137
|
ErrorCode["MANIFEST_EXISTS"] = "EMANIFESTEXISTS";
|
|
138
|
+
ErrorCode["MANIFEST_VALIDATION_ERROR"] = "EMANIFESTVAL";
|
|
136
139
|
ErrorCode["NETWORK_ERROR"] = "ENETWORK";
|
|
137
140
|
ErrorCode["REPO_NOT_FOUND"] = "EREPONOTFOUND";
|
|
138
141
|
ErrorCode["UNKNOWN_ERROR"] = "EUNKNOWN";
|
|
@@ -239,7 +242,8 @@ class ManifestError extends SpmError {
|
|
|
239
242
|
[codes_ErrorCode.MANIFEST_NOT_FOUND]: `Manifest not found: ${options.filePath}`,
|
|
240
243
|
[codes_ErrorCode.LOCKFILE_NOT_FOUND]: `Lockfile not found: ${options.filePath}`,
|
|
241
244
|
[codes_ErrorCode.LOCKFILE_OUTDATED]: `Lockfile is out of date: ${options.filePath}`,
|
|
242
|
-
[codes_ErrorCode.MANIFEST_EXISTS]: `Manifest already exists: ${options.filePath}
|
|
245
|
+
[codes_ErrorCode.MANIFEST_EXISTS]: `Manifest already exists: ${options.filePath}`,
|
|
246
|
+
[codes_ErrorCode.MANIFEST_VALIDATION_ERROR]: `Invalid skills.json: ${options.filePath}`
|
|
243
247
|
};
|
|
244
248
|
const message = options.message ?? defaultMessages[options.code] ?? `Manifest error: ${options.filePath}`;
|
|
245
249
|
super({
|
|
@@ -379,6 +383,9 @@ function formatErrorForDisplay(error) {
|
|
|
379
383
|
if (error.code === codes_ErrorCode.LOCKFILE_OUTDATED) {
|
|
380
384
|
output += `\n\nThe lockfile is out of sync with skills.json.`;
|
|
381
385
|
output += `\nRun "spm install" to update the lockfile.`;
|
|
386
|
+
} else if (error.code === codes_ErrorCode.MANIFEST_VALIDATION_ERROR) {
|
|
387
|
+
output += `\n\nPlease fix the validation errors in "${error.filePath}".`;
|
|
388
|
+
output += `\nRefer to the JSON Schema at: https://unpkg.com/skills-package-manager@latest/skills.schema.json`;
|
|
382
389
|
}
|
|
383
390
|
}
|
|
384
391
|
if (error.cause && !(error instanceof GitError || error instanceof FileSystemError)) output += `\n\nCaused by: ${error.cause.message}`;
|
|
@@ -425,17 +432,20 @@ async function readSkillsLock(rootDir) {
|
|
|
425
432
|
});
|
|
426
433
|
}
|
|
427
434
|
}
|
|
435
|
+
const skillsManifestSchema = z.object({
|
|
436
|
+
$schema: z.string().optional().describe('JSON Schema URL for editor autocompletion'),
|
|
437
|
+
installDir: z.string().default('.agents/skills').describe('Directory where skills will be installed'),
|
|
438
|
+
linkTargets: z.array(z.string()).default([]).describe('Directories where skill symlinks will be created'),
|
|
439
|
+
selfSkill: z.boolean().optional().describe('Whether this project is itself a skill'),
|
|
440
|
+
skills: z.record(z.string(), z.string()).default({}).describe('Map of skill names to their specifiers')
|
|
441
|
+
}).strict();
|
|
428
442
|
async function readSkillsManifest(rootDir) {
|
|
429
443
|
const filePath = node_path.join(rootDir, 'skills.json');
|
|
430
444
|
try {
|
|
431
445
|
const raw = await readFile(filePath, 'utf8');
|
|
446
|
+
let parsedJson;
|
|
432
447
|
try {
|
|
433
|
-
|
|
434
|
-
return {
|
|
435
|
-
installDir: json.installDir ?? '.agents/skills',
|
|
436
|
-
linkTargets: json.linkTargets ?? [],
|
|
437
|
-
skills: json.skills ?? {}
|
|
438
|
-
};
|
|
448
|
+
parsedJson = JSON.parse(raw);
|
|
439
449
|
} catch (parseError) {
|
|
440
450
|
throw new ParseError({
|
|
441
451
|
code: codes_ErrorCode.JSON_PARSE_ERROR,
|
|
@@ -445,9 +455,22 @@ async function readSkillsManifest(rootDir) {
|
|
|
445
455
|
cause: parseError
|
|
446
456
|
});
|
|
447
457
|
}
|
|
458
|
+
const result = skillsManifestSchema.safeParse(parsedJson);
|
|
459
|
+
if (!result.success) {
|
|
460
|
+
const issues = result.error.issues.map((issue)=>{
|
|
461
|
+
const pathStr = issue.path.length > 0 ? issue.path.join('.') : '(root)';
|
|
462
|
+
return `${pathStr}: ${issue.message}`;
|
|
463
|
+
}).join('\n - ');
|
|
464
|
+
throw new ManifestError({
|
|
465
|
+
code: codes_ErrorCode.MANIFEST_VALIDATION_ERROR,
|
|
466
|
+
filePath,
|
|
467
|
+
message: `Invalid skills.json:\n - ${issues}`
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return result.data;
|
|
448
471
|
} catch (error) {
|
|
449
472
|
if ('ENOENT' === error.code) return null;
|
|
450
|
-
if (error instanceof ParseError) throw error;
|
|
473
|
+
if (error instanceof ParseError || error instanceof ManifestError) throw error;
|
|
451
474
|
throw convertNodeError(error, {
|
|
452
475
|
operation: 'read',
|
|
453
476
|
path: filePath
|
|
@@ -969,13 +992,16 @@ async function writeSkillsLock(rootDir, lockfile) {
|
|
|
969
992
|
});
|
|
970
993
|
}
|
|
971
994
|
}
|
|
995
|
+
const DEFAULT_SCHEMA_URL = `https://unpkg.com/skills-package-manager@${package_namespaceObject.rE}/skills.schema.json`;
|
|
972
996
|
async function writeSkillsManifest(rootDir, manifest) {
|
|
973
997
|
const filePath = node_path.join(rootDir, 'skills.json');
|
|
974
998
|
const nextManifest = {
|
|
999
|
+
$schema: manifest.$schema ?? DEFAULT_SCHEMA_URL,
|
|
975
1000
|
installDir: manifest.installDir ?? '.agents/skills',
|
|
976
1001
|
linkTargets: manifest.linkTargets ?? [],
|
|
977
1002
|
skills: manifest.skills
|
|
978
1003
|
};
|
|
1004
|
+
if (void 0 !== manifest.selfSkill) nextManifest.selfSkill = manifest.selfSkill;
|
|
979
1005
|
try {
|
|
980
1006
|
await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
|
|
981
1007
|
} catch (error) {
|
|
@@ -1050,6 +1076,23 @@ async function scanForSkills(baseDir, subDir) {
|
|
|
1050
1076
|
} catch {}
|
|
1051
1077
|
return skills;
|
|
1052
1078
|
}
|
|
1079
|
+
async function discoverSkillsInDirs(baseDir, commonDirs, options) {
|
|
1080
|
+
if (options?.includeRoot ?? true) {
|
|
1081
|
+
const rootSkills = await scanForSkills(baseDir, '');
|
|
1082
|
+
if (rootSkills.length > 0) {
|
|
1083
|
+
rootSkills.sort((a, b)=>a.name.localeCompare(b.name));
|
|
1084
|
+
return rootSkills;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
for (const dir of commonDirs){
|
|
1088
|
+
const skills = await scanForSkills(baseDir, dir);
|
|
1089
|
+
if (skills.length > 0) {
|
|
1090
|
+
skills.sort((a, b)=>a.name.localeCompare(b.name));
|
|
1091
|
+
return skills;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return [];
|
|
1095
|
+
}
|
|
1053
1096
|
async function cloneAndDiscover(gitUrl, ref) {
|
|
1054
1097
|
const tempDir = await mkdtemp(join(tmpdir(), 'skills-pm-discover-'));
|
|
1055
1098
|
try {
|
|
@@ -1094,25 +1137,12 @@ async function cloneAndDiscover(gitUrl, ref) {
|
|
|
1094
1137
|
}
|
|
1095
1138
|
}
|
|
1096
1139
|
async function discoverSkillsInDir(baseDir) {
|
|
1097
|
-
|
|
1098
|
-
if (rootSkills.length > 0) {
|
|
1099
|
-
rootSkills.sort((a, b)=>a.name.localeCompare(b.name));
|
|
1100
|
-
return rootSkills;
|
|
1101
|
-
}
|
|
1102
|
-
const commonDirs = [
|
|
1140
|
+
return discoverSkillsInDirs(baseDir, [
|
|
1103
1141
|
'skills',
|
|
1104
1142
|
'.agents/skills',
|
|
1105
1143
|
'.claude/skills',
|
|
1106
1144
|
'.github/skills'
|
|
1107
|
-
];
|
|
1108
|
-
for (const dir of commonDirs){
|
|
1109
|
-
const skills = await scanForSkills(baseDir, dir);
|
|
1110
|
-
if (skills.length > 0) {
|
|
1111
|
-
skills.sort((a, b)=>a.name.localeCompare(b.name));
|
|
1112
|
-
return skills;
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
return [];
|
|
1145
|
+
]);
|
|
1116
1146
|
}
|
|
1117
1147
|
async function listRepoSkills(owner, repo, ref) {
|
|
1118
1148
|
const gitUrl = `https://github.com/${owner}/${repo}.git`;
|
|
@@ -1177,6 +1207,60 @@ function isLockInSync(manifest, lock) {
|
|
|
1177
1207
|
}
|
|
1178
1208
|
return true;
|
|
1179
1209
|
}
|
|
1210
|
+
const SELF_SKILL_NAME = 'skills-package-manager-cli';
|
|
1211
|
+
const SELF_SKILL_CANDIDATE_PATHS = [
|
|
1212
|
+
'../skills/skills-package-manager-cli',
|
|
1213
|
+
'../../skills/skills-package-manager-cli'
|
|
1214
|
+
];
|
|
1215
|
+
const MODULE_DIR = node_path.dirname(fileURLToPath(import.meta.url));
|
|
1216
|
+
function resolveBundledSelfSkillDir() {
|
|
1217
|
+
for (const relativePath of SELF_SKILL_CANDIDATE_PATHS){
|
|
1218
|
+
const candidate = node_path.resolve(MODULE_DIR, relativePath);
|
|
1219
|
+
try {
|
|
1220
|
+
accessSync(node_path.join(candidate, 'SKILL.md'));
|
|
1221
|
+
return candidate;
|
|
1222
|
+
} catch {}
|
|
1223
|
+
}
|
|
1224
|
+
throw new Error('Unable to locate bundled skills-package-manager-cli skill');
|
|
1225
|
+
}
|
|
1226
|
+
function getBundledSelfSkillSpecifier() {
|
|
1227
|
+
return normalizeLinkSource(`link:${resolveBundledSelfSkillDir()}`);
|
|
1228
|
+
}
|
|
1229
|
+
function shouldInjectBundledSelfSkill(manifest) {
|
|
1230
|
+
return true === normalizeSkillsManifest(manifest).selfSkill;
|
|
1231
|
+
}
|
|
1232
|
+
function hasEquivalentSkillSpecifier(manifest, specifier) {
|
|
1233
|
+
const normalizedTarget = normalizeSpecifier(specifier).normalized;
|
|
1234
|
+
return Object.values(manifest.skills).some((existingSpecifier)=>{
|
|
1235
|
+
try {
|
|
1236
|
+
return normalizeSpecifier(existingSpecifier).normalized === normalizedTarget;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
function normalizeSkillsManifest(manifest) {
|
|
1243
|
+
return {
|
|
1244
|
+
$schema: manifest.$schema,
|
|
1245
|
+
installDir: manifest.installDir ?? '.agents/skills',
|
|
1246
|
+
linkTargets: manifest.linkTargets ?? [],
|
|
1247
|
+
selfSkill: manifest.selfSkill ?? false,
|
|
1248
|
+
skills: manifest.skills ?? {}
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
async function expandSkillsManifest(_rootDir, manifest) {
|
|
1252
|
+
const normalized = normalizeSkillsManifest(manifest);
|
|
1253
|
+
if (!normalized.selfSkill) return normalized;
|
|
1254
|
+
const selfSpecifier = getBundledSelfSkillSpecifier();
|
|
1255
|
+
if (SELF_SKILL_NAME in normalized.skills || hasEquivalentSkillSpecifier(normalized, selfSpecifier)) return normalized;
|
|
1256
|
+
return {
|
|
1257
|
+
...normalized,
|
|
1258
|
+
skills: {
|
|
1259
|
+
...normalized.skills,
|
|
1260
|
+
[SELF_SKILL_NAME]: selfSpecifier
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1180
1264
|
async function ensureDir(dirPath) {
|
|
1181
1265
|
await mkdir(dirPath, {
|
|
1182
1266
|
recursive: true
|
|
@@ -1411,6 +1495,17 @@ async function areManagedSkillsInstalled(rootDir, installDir, skillNames) {
|
|
|
1411
1495
|
}
|
|
1412
1496
|
return true;
|
|
1413
1497
|
}
|
|
1498
|
+
async function withBundledSelfSkillLock(rootDir, manifest, lockfile) {
|
|
1499
|
+
if (!shouldInjectBundledSelfSkill(manifest) || lockfile.skills[SELF_SKILL_NAME]) return lockfile;
|
|
1500
|
+
const { entry } = await resolveLockEntry(rootDir, getBundledSelfSkillSpecifier(), SELF_SKILL_NAME);
|
|
1501
|
+
return {
|
|
1502
|
+
...lockfile,
|
|
1503
|
+
skills: {
|
|
1504
|
+
...lockfile.skills,
|
|
1505
|
+
[SELF_SKILL_NAME]: entry
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1414
1509
|
async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
|
|
1415
1510
|
await installStageHooks.beforeFetch(rootDir, manifest, lockfile);
|
|
1416
1511
|
const installDir = manifest.installDir ?? '.agents/skills';
|
|
@@ -1519,16 +1614,17 @@ async function installSkills(rootDir, options) {
|
|
|
1519
1614
|
} else lockfile = await syncSkillsLock(rootDir, manifest, currentLock, {
|
|
1520
1615
|
onProgress: options?.onProgress
|
|
1521
1616
|
});
|
|
1522
|
-
await
|
|
1617
|
+
const runtimeLock = await withBundledSelfSkillLock(rootDir, manifest, lockfile);
|
|
1618
|
+
await fetchSkillsFromLock(rootDir, manifest, runtimeLock, {
|
|
1523
1619
|
onProgress: options?.onProgress
|
|
1524
1620
|
});
|
|
1525
|
-
await linkSkillsFromLock(rootDir, manifest,
|
|
1621
|
+
await linkSkillsFromLock(rootDir, manifest, runtimeLock, {
|
|
1526
1622
|
onProgress: options?.onProgress
|
|
1527
1623
|
});
|
|
1528
1624
|
if (!options?.frozenLockfile) await writeSkillsLock(rootDir, lockfile);
|
|
1529
1625
|
return {
|
|
1530
1626
|
status: 'installed',
|
|
1531
|
-
installed: Object.keys(
|
|
1627
|
+
installed: Object.keys(runtimeLock.skills)
|
|
1532
1628
|
};
|
|
1533
1629
|
}
|
|
1534
1630
|
function buildGitHubSpecifier(owner, repo, skillPath) {
|
|
@@ -1625,6 +1721,7 @@ async function addCommand(options) {
|
|
|
1625
1721
|
spinner.stop('Installed skills');
|
|
1626
1722
|
return result;
|
|
1627
1723
|
}
|
|
1724
|
+
const init_DEFAULT_SCHEMA_URL = `https://unpkg.com/skills-package-manager@${package_namespaceObject.rE}/skills.schema.json`;
|
|
1628
1725
|
async function assertManifestMissing(cwd) {
|
|
1629
1726
|
const filePath = node_path.join(cwd, 'skills.json');
|
|
1630
1727
|
try {
|
|
@@ -1648,19 +1745,23 @@ async function assertManifestMissing(cwd) {
|
|
|
1648
1745
|
}
|
|
1649
1746
|
function createDefaultManifest() {
|
|
1650
1747
|
return {
|
|
1748
|
+
$schema: init_DEFAULT_SCHEMA_URL,
|
|
1651
1749
|
installDir: '.agents/skills',
|
|
1652
1750
|
linkTargets: [],
|
|
1751
|
+
selfSkill: false,
|
|
1653
1752
|
skills: {}
|
|
1654
1753
|
};
|
|
1655
1754
|
}
|
|
1656
1755
|
async function initCommand(options, promptInit = promptInitManifestOptions) {
|
|
1657
1756
|
await assertManifestMissing(options.cwd);
|
|
1658
|
-
const
|
|
1757
|
+
const baseManifest = options.yes ? createDefaultManifest() : {
|
|
1758
|
+
$schema: init_DEFAULT_SCHEMA_URL,
|
|
1659
1759
|
...await promptInit(),
|
|
1760
|
+
selfSkill: false,
|
|
1660
1761
|
skills: {}
|
|
1661
1762
|
};
|
|
1662
|
-
await writeSkillsManifest(options.cwd,
|
|
1663
|
-
return
|
|
1763
|
+
await writeSkillsManifest(options.cwd, baseManifest);
|
|
1764
|
+
return baseManifest;
|
|
1664
1765
|
}
|
|
1665
1766
|
const phaseLabelMap = {
|
|
1666
1767
|
resolving: 'Resolving',
|
|
@@ -1781,11 +1882,11 @@ async function installCommand(options) {
|
|
|
1781
1882
|
message: 'No skills.json found in the current directory. Run "spm init" to create one.'
|
|
1782
1883
|
});
|
|
1783
1884
|
const currentLock = await readSkillsLock(options.cwd);
|
|
1784
|
-
const totalSkills = Object.keys(manifest.skills).length;
|
|
1785
1885
|
const reporter = createInstallProgressReporter();
|
|
1786
1886
|
const onProgress = (event)=>reporter.onProgress(event);
|
|
1787
1887
|
let started = false;
|
|
1788
1888
|
try {
|
|
1889
|
+
let lockfile;
|
|
1789
1890
|
if (options.frozenLockfile) {
|
|
1790
1891
|
if (!currentLock) throw new ManifestError({
|
|
1791
1892
|
code: codes_ErrorCode.LOCKFILE_NOT_FOUND,
|
|
@@ -1797,46 +1898,31 @@ async function installCommand(options) {
|
|
|
1797
1898
|
filePath: `${options.cwd}/skills-lock.yaml`,
|
|
1798
1899
|
message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
|
|
1799
1900
|
});
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
for (const skillName of Object.keys(currentLock.skills))onProgress({
|
|
1803
|
-
type: 'resolved',
|
|
1804
|
-
skillName
|
|
1805
|
-
});
|
|
1806
|
-
reporter.setPhase('fetching');
|
|
1807
|
-
await fetchSkillsFromLock(options.cwd, manifest, currentLock, {
|
|
1808
|
-
onProgress
|
|
1809
|
-
});
|
|
1810
|
-
reporter.setPhase('linking');
|
|
1811
|
-
await linkSkillsFromLock(options.cwd, manifest, currentLock, {
|
|
1812
|
-
onProgress
|
|
1813
|
-
});
|
|
1814
|
-
reporter.setPhase('finalizing');
|
|
1815
|
-
reporter.complete();
|
|
1816
|
-
return {
|
|
1817
|
-
status: 'installed',
|
|
1818
|
-
installed: Object.keys(currentLock.skills)
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
reporter.start(totalSkills);
|
|
1822
|
-
started = true;
|
|
1823
|
-
const lockfile = await syncSkillsLock(options.cwd, manifest, currentLock, {
|
|
1901
|
+
lockfile = currentLock;
|
|
1902
|
+
} else lockfile = await syncSkillsLock(options.cwd, manifest, currentLock, {
|
|
1824
1903
|
onProgress
|
|
1825
1904
|
});
|
|
1905
|
+
const runtimeLock = await withBundledSelfSkillLock(options.cwd, manifest, lockfile);
|
|
1906
|
+
reporter.start(Object.keys(runtimeLock.skills).length);
|
|
1907
|
+
started = true;
|
|
1908
|
+
for (const skillName of Object.keys(lockfile.skills))onProgress({
|
|
1909
|
+
type: 'resolved',
|
|
1910
|
+
skillName
|
|
1911
|
+
});
|
|
1826
1912
|
reporter.setPhase('fetching');
|
|
1827
|
-
await fetchSkillsFromLock(options.cwd, manifest,
|
|
1913
|
+
await fetchSkillsFromLock(options.cwd, manifest, runtimeLock, {
|
|
1828
1914
|
onProgress
|
|
1829
1915
|
});
|
|
1830
1916
|
reporter.setPhase('linking');
|
|
1831
|
-
await linkSkillsFromLock(options.cwd, manifest,
|
|
1917
|
+
await linkSkillsFromLock(options.cwd, manifest, runtimeLock, {
|
|
1832
1918
|
onProgress
|
|
1833
1919
|
});
|
|
1834
1920
|
reporter.setPhase('finalizing');
|
|
1835
|
-
await writeSkillsLock(options.cwd, lockfile);
|
|
1921
|
+
if (!options.frozenLockfile) await writeSkillsLock(options.cwd, lockfile);
|
|
1836
1922
|
reporter.complete();
|
|
1837
1923
|
return {
|
|
1838
1924
|
status: 'installed',
|
|
1839
|
-
installed: Object.keys(
|
|
1925
|
+
installed: Object.keys(runtimeLock.skills)
|
|
1840
1926
|
};
|
|
1841
1927
|
} catch (error) {
|
|
1842
1928
|
if (started) reporter.fail();
|
|
@@ -1922,8 +2008,9 @@ async function updateCommand(options) {
|
|
|
1922
2008
|
result.status = 'failed';
|
|
1923
2009
|
return result;
|
|
1924
2010
|
}
|
|
1925
|
-
await
|
|
1926
|
-
await
|
|
2011
|
+
const runtimeLock = await withBundledSelfSkillLock(options.cwd, manifest, candidateLock);
|
|
2012
|
+
await fetchSkillsFromLock(options.cwd, manifest, runtimeLock);
|
|
2013
|
+
await linkSkillsFromLock(options.cwd, manifest, runtimeLock);
|
|
1927
2014
|
await writeSkillsLock(options.cwd, candidateLock);
|
|
1928
2015
|
result.status = result.updated.length > 0 ? 'updated' : 'skipped';
|
|
1929
2016
|
return result;
|
|
@@ -1996,4 +2083,4 @@ async function runCli(argv, context = {}) {
|
|
|
1996
2083
|
throw error;
|
|
1997
2084
|
}
|
|
1998
2085
|
}
|
|
1999
|
-
export { FileSystemError, GitError, ManifestError, NetworkError, ParseError, SkillError, SpmError, addCommand, cloneAndDiscover, codes_ErrorCode as ErrorCode, convertNodeError, createInstallProgressReporter, discoverSkillsInDir, fetchSkillsFromLock, formatErrorForDisplay, getExitCode, initCommand, installCommand, installSkills, installStageHooks, isLockInSync, isSpmError, linkSkillsFromLock, listRepoSkills, normalizeSpecifier, parseGitHubUrl, parseOwnerRepo, parseSpecifier, readSkillsLock, readSkillsManifest, resolveLockEntry, runCli, updateCommand, writeSkillsLock, writeSkillsManifest };
|
|
2086
|
+
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skills-package-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin",
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"skills",
|
|
20
|
+
"skills.schema.json"
|
|
19
21
|
],
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"@clack/prompts": "^1.1.0",
|
|
@@ -23,7 +25,8 @@
|
|
|
23
25
|
"picocolors": "^1.1.1",
|
|
24
26
|
"semver": "^7.7.2",
|
|
25
27
|
"tar": "^7.4.3",
|
|
26
|
-
"yaml": "^2.8.1"
|
|
28
|
+
"yaml": "^2.8.1",
|
|
29
|
+
"zod": "^4.3.6"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@rslib/core": "^0.20.0",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skills-package-manager-cli
|
|
3
|
+
description: Help users work in repositories that use skills-package-manager. Use when requests mention `skills.json`, `skills-lock.yaml`, `selfSkill`, `npx skills-package-manager init`, `add`, `install`, `update`, skill specifiers, install directories like `.agents/skills`, or linked skill directories like `.claude/skills`
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# skills-package-manager
|
|
7
|
+
|
|
8
|
+
Use this skill for repositories that already use `skills-package-manager`, or when a user needs help understanding and editing its manifest, lockfile, and CLI workflow.
|
|
9
|
+
|
|
10
|
+
## Core Model
|
|
11
|
+
|
|
12
|
+
- `skills.json` is the source of truth.
|
|
13
|
+
It declares which skills a repo wants, where to materialize them, where to link them, and whether to include the bundled helper skill.
|
|
14
|
+
- `skills-lock.yaml` is resolved state.
|
|
15
|
+
It pins commits, versions, paths, and digests so installs are reproducible.
|
|
16
|
+
- Installed directories such as `.agents/skills` and linked directories such as `.claude/skills` are outputs.
|
|
17
|
+
They are produced from the manifest and lockfile; they are not the canonical config.
|
|
18
|
+
|
|
19
|
+
## What `selfSkill` Means
|
|
20
|
+
|
|
21
|
+
- `selfSkill: true` adds the bundled `skills-package-manager-cli` skill during install.
|
|
22
|
+
- It is meant to help users who see `skills.json`, `skills-lock.yaml`, and `spm` commands but do not yet know how they fit together.
|
|
23
|
+
- The bundled skill is injected automatically. It should not be added manually under `skills` unless there is a very specific reason.
|
|
24
|
+
|
|
25
|
+
## Command Guide
|
|
26
|
+
|
|
27
|
+
1. `npx skills-package-manager init`
|
|
28
|
+
- Creates `skills.json`.
|
|
29
|
+
- `npx skills-package-manager init --yes` writes the default manifest immediately.
|
|
30
|
+
|
|
31
|
+
2. `npx skills-package-manager add <specifier> [--skill <name>]`
|
|
32
|
+
- Adds a skill to `skills.json`.
|
|
33
|
+
- Resolves it into `skills-lock.yaml`.
|
|
34
|
+
- Installs it into `installDir` and links it into each `linkTarget`.
|
|
35
|
+
|
|
36
|
+
3. `npx skills-package-manager install`
|
|
37
|
+
- Reconciles the manifest, lockfile, and installed output.
|
|
38
|
+
- Without `--frozen-lockfile`, it updates `skills-lock.yaml` when needed.
|
|
39
|
+
- With `--frozen-lockfile`, it requires the lockfile to already match the manifest.
|
|
40
|
+
|
|
41
|
+
4. `npx skills-package-manager update [skill...]`
|
|
42
|
+
- Refreshes resolvable entries in `skills-lock.yaml`.
|
|
43
|
+
- Skips `link:` skills, including the bundled `skills-package-manager-cli` self skill.
|
|
44
|
+
|
|
45
|
+
## How To Triage User Questions
|
|
46
|
+
|
|
47
|
+
1. If the user wants to change which skills a repo uses:
|
|
48
|
+
Edit `skills.json`, then run `npx skills-package-manager install`.
|
|
49
|
+
|
|
50
|
+
2. If the user wants to understand pinned versions or why a change happened:
|
|
51
|
+
Inspect `skills-lock.yaml`.
|
|
52
|
+
|
|
53
|
+
3. If the user says a skill is missing in their agent:
|
|
54
|
+
Check `installDir`, `linkTargets`, generated skill directories, and symlinks.
|
|
55
|
+
|
|
56
|
+
4. If the user is confused about `selfSkill`:
|
|
57
|
+
Explain that it enables the bundled `skills-package-manager-cli` helper skill, not an arbitrary repo-local skill.
|
|
58
|
+
|
|
59
|
+
## Specifier Reminders
|
|
60
|
+
|
|
61
|
+
- `link:./path/to/skill-dir` points to a local skill directory.
|
|
62
|
+
- `file:./pkg.tgz#path:/skills/name` points to a packaged tarball plus skill path.
|
|
63
|
+
- `npm:@scope/pkg#path:/skills/name` resolves a package from the configured registry.
|
|
64
|
+
- GitHub shorthand or Git URLs resolve remote repositories and may need `--skill` when multiple skills are available.
|
|
65
|
+
|
|
66
|
+
## Validation Checklist
|
|
67
|
+
|
|
68
|
+
- Keep `manifest`, `lockfile`, `installDir`, `linkTargets`, `skills`, and `specifier` terminology exact.
|
|
69
|
+
- Treat `skills-lock.yaml` as generated state unless the task is specifically about lockfile internals or checked-in examples.
|
|
70
|
+
- If you change this bundled skill inside the `skills-package-manager` repo, revalidate the skill folder and update any checked-in lockfile digest that refers to it.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"$schema": {
|
|
6
|
+
"description": "JSON Schema URL for editor autocompletion",
|
|
7
|
+
"type": "string"
|
|
8
|
+
},
|
|
9
|
+
"installDir": {
|
|
10
|
+
"default": ".agents/skills",
|
|
11
|
+
"description": "Directory where skills will be installed",
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"linkTargets": {
|
|
15
|
+
"default": [],
|
|
16
|
+
"description": "Directories where skill symlinks will be created",
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"selfSkill": {
|
|
23
|
+
"description": "Whether this project is itself a skill",
|
|
24
|
+
"type": "boolean"
|
|
25
|
+
},
|
|
26
|
+
"skills": {
|
|
27
|
+
"default": {},
|
|
28
|
+
"description": "Map of skill names to their specifiers",
|
|
29
|
+
"type": "object",
|
|
30
|
+
"propertyNames": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"additionalProperties": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"required": [
|
|
39
|
+
"installDir",
|
|
40
|
+
"linkTargets",
|
|
41
|
+
"skills"
|
|
42
|
+
],
|
|
43
|
+
"additionalProperties": false
|
|
44
|
+
}
|