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 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.4.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
- const json = JSON.parse(raw);
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
- const rootSkills = await scanForSkills(baseDir, '');
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 fetchSkillsFromLock(rootDir, manifest, lockfile, {
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, lockfile, {
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(lockfile.skills)
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 manifest = options.yes ? createDefaultManifest() : {
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, manifest);
1663
- return manifest;
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
- reporter.start(totalSkills);
1801
- started = true;
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, lockfile, {
1913
+ await fetchSkillsFromLock(options.cwd, manifest, runtimeLock, {
1828
1914
  onProgress
1829
1915
  });
1830
1916
  reporter.setPhase('linking');
1831
- await linkSkillsFromLock(options.cwd, manifest, lockfile, {
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(lockfile.skills)
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 fetchSkillsFromLock(options.cwd, manifest, candidateLock);
1926
- await linkSkillsFromLock(options.cwd, manifest, candidateLock);
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.4.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
+ }