skills-package-manager 0.4.0 → 0.6.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.6.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,23 @@ 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
+ pnpmPlugin: z.object({
441
+ removePnpmfileChecksum: z.boolean().optional().describe('Temporarily remove pnpmfileChecksum from pnpm lockfiles in pnpm-plugin-skills afterAllResolved')
442
+ }).optional().describe('pnpm-plugin-skills specific compatibility settings'),
443
+ skills: z.record(z.string(), z.string()).default({}).describe('Map of skill names to their specifiers')
444
+ }).strict();
428
445
  async function readSkillsManifest(rootDir) {
429
446
  const filePath = node_path.join(rootDir, 'skills.json');
430
447
  try {
431
448
  const raw = await readFile(filePath, 'utf8');
449
+ let parsedJson;
432
450
  try {
433
- const json = JSON.parse(raw);
434
- return {
435
- installDir: json.installDir ?? '.agents/skills',
436
- linkTargets: json.linkTargets ?? [],
437
- skills: json.skills ?? {}
438
- };
451
+ parsedJson = JSON.parse(raw);
439
452
  } catch (parseError) {
440
453
  throw new ParseError({
441
454
  code: codes_ErrorCode.JSON_PARSE_ERROR,
@@ -445,9 +458,22 @@ async function readSkillsManifest(rootDir) {
445
458
  cause: parseError
446
459
  });
447
460
  }
461
+ const result = skillsManifestSchema.safeParse(parsedJson);
462
+ if (!result.success) {
463
+ const issues = result.error.issues.map((issue)=>{
464
+ const pathStr = issue.path.length > 0 ? issue.path.join('.') : '(root)';
465
+ return `${pathStr}: ${issue.message}`;
466
+ }).join('\n - ');
467
+ throw new ManifestError({
468
+ code: codes_ErrorCode.MANIFEST_VALIDATION_ERROR,
469
+ filePath,
470
+ message: `Invalid skills.json:\n - ${issues}`
471
+ });
472
+ }
473
+ return result.data;
448
474
  } catch (error) {
449
475
  if ('ENOENT' === error.code) return null;
450
- if (error instanceof ParseError) throw error;
476
+ if (error instanceof ParseError || error instanceof ManifestError) throw error;
451
477
  throw convertNodeError(error, {
452
478
  operation: 'read',
453
479
  path: filePath
@@ -969,13 +995,17 @@ async function writeSkillsLock(rootDir, lockfile) {
969
995
  });
970
996
  }
971
997
  }
998
+ const DEFAULT_SCHEMA_URL = `https://unpkg.com/skills-package-manager@${package_namespaceObject.rE}/skills.schema.json`;
972
999
  async function writeSkillsManifest(rootDir, manifest) {
973
1000
  const filePath = node_path.join(rootDir, 'skills.json');
974
1001
  const nextManifest = {
1002
+ $schema: manifest.$schema ?? DEFAULT_SCHEMA_URL,
975
1003
  installDir: manifest.installDir ?? '.agents/skills',
976
1004
  linkTargets: manifest.linkTargets ?? [],
977
1005
  skills: manifest.skills
978
1006
  };
1007
+ if (void 0 !== manifest.selfSkill) nextManifest.selfSkill = manifest.selfSkill;
1008
+ if (void 0 !== manifest.pnpmPlugin) nextManifest.pnpmPlugin = manifest.pnpmPlugin;
979
1009
  try {
980
1010
  await writeFile(filePath, `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
981
1011
  } catch (error) {
@@ -1050,6 +1080,23 @@ async function scanForSkills(baseDir, subDir) {
1050
1080
  } catch {}
1051
1081
  return skills;
1052
1082
  }
1083
+ async function discoverSkillsInDirs(baseDir, commonDirs, options) {
1084
+ if (options?.includeRoot ?? true) {
1085
+ const rootSkills = await scanForSkills(baseDir, '');
1086
+ if (rootSkills.length > 0) {
1087
+ rootSkills.sort((a, b)=>a.name.localeCompare(b.name));
1088
+ return rootSkills;
1089
+ }
1090
+ }
1091
+ for (const dir of commonDirs){
1092
+ const skills = await scanForSkills(baseDir, dir);
1093
+ if (skills.length > 0) {
1094
+ skills.sort((a, b)=>a.name.localeCompare(b.name));
1095
+ return skills;
1096
+ }
1097
+ }
1098
+ return [];
1099
+ }
1053
1100
  async function cloneAndDiscover(gitUrl, ref) {
1054
1101
  const tempDir = await mkdtemp(join(tmpdir(), 'skills-pm-discover-'));
1055
1102
  try {
@@ -1094,25 +1141,12 @@ async function cloneAndDiscover(gitUrl, ref) {
1094
1141
  }
1095
1142
  }
1096
1143
  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 = [
1144
+ return discoverSkillsInDirs(baseDir, [
1103
1145
  'skills',
1104
1146
  '.agents/skills',
1105
1147
  '.claude/skills',
1106
1148
  '.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 [];
1149
+ ]);
1116
1150
  }
1117
1151
  async function listRepoSkills(owner, repo, ref) {
1118
1152
  const gitUrl = `https://github.com/${owner}/${repo}.git`;
@@ -1177,6 +1211,61 @@ function isLockInSync(manifest, lock) {
1177
1211
  }
1178
1212
  return true;
1179
1213
  }
1214
+ const SELF_SKILL_NAME = 'skills-package-manager-cli';
1215
+ const SELF_SKILL_CANDIDATE_PATHS = [
1216
+ '../skills/skills-package-manager-cli',
1217
+ '../../skills/skills-package-manager-cli'
1218
+ ];
1219
+ const MODULE_DIR = node_path.dirname(fileURLToPath(import.meta.url));
1220
+ function resolveBundledSelfSkillDir() {
1221
+ for (const relativePath of SELF_SKILL_CANDIDATE_PATHS){
1222
+ const candidate = node_path.resolve(MODULE_DIR, relativePath);
1223
+ try {
1224
+ accessSync(node_path.join(candidate, 'SKILL.md'));
1225
+ return candidate;
1226
+ } catch {}
1227
+ }
1228
+ throw new Error('Unable to locate bundled skills-package-manager-cli skill');
1229
+ }
1230
+ function getBundledSelfSkillSpecifier() {
1231
+ return normalizeLinkSource(`link:${resolveBundledSelfSkillDir()}`);
1232
+ }
1233
+ function shouldInjectBundledSelfSkill(manifest) {
1234
+ return true === normalizeSkillsManifest(manifest).selfSkill;
1235
+ }
1236
+ function hasEquivalentSkillSpecifier(manifest, specifier) {
1237
+ const normalizedTarget = normalizeSpecifier(specifier).normalized;
1238
+ return Object.values(manifest.skills).some((existingSpecifier)=>{
1239
+ try {
1240
+ return normalizeSpecifier(existingSpecifier).normalized === normalizedTarget;
1241
+ } catch {
1242
+ return false;
1243
+ }
1244
+ });
1245
+ }
1246
+ function normalizeSkillsManifest(manifest) {
1247
+ return {
1248
+ $schema: manifest.$schema,
1249
+ installDir: manifest.installDir ?? '.agents/skills',
1250
+ linkTargets: manifest.linkTargets ?? [],
1251
+ selfSkill: manifest.selfSkill ?? false,
1252
+ pnpmPlugin: manifest.pnpmPlugin,
1253
+ skills: manifest.skills ?? {}
1254
+ };
1255
+ }
1256
+ async function expandSkillsManifest(_rootDir, manifest) {
1257
+ const normalized = normalizeSkillsManifest(manifest);
1258
+ if (!normalized.selfSkill) return normalized;
1259
+ const selfSpecifier = getBundledSelfSkillSpecifier();
1260
+ if (SELF_SKILL_NAME in normalized.skills || hasEquivalentSkillSpecifier(normalized, selfSpecifier)) return normalized;
1261
+ return {
1262
+ ...normalized,
1263
+ skills: {
1264
+ ...normalized.skills,
1265
+ [SELF_SKILL_NAME]: selfSpecifier
1266
+ }
1267
+ };
1268
+ }
1180
1269
  async function ensureDir(dirPath) {
1181
1270
  await mkdir(dirPath, {
1182
1271
  recursive: true
@@ -1411,6 +1500,17 @@ async function areManagedSkillsInstalled(rootDir, installDir, skillNames) {
1411
1500
  }
1412
1501
  return true;
1413
1502
  }
1503
+ async function withBundledSelfSkillLock(rootDir, manifest, lockfile) {
1504
+ if (!shouldInjectBundledSelfSkill(manifest) || lockfile.skills[SELF_SKILL_NAME]) return lockfile;
1505
+ const { entry } = await resolveLockEntry(rootDir, getBundledSelfSkillSpecifier(), SELF_SKILL_NAME);
1506
+ return {
1507
+ ...lockfile,
1508
+ skills: {
1509
+ ...lockfile.skills,
1510
+ [SELF_SKILL_NAME]: entry
1511
+ }
1512
+ };
1513
+ }
1414
1514
  async function fetchSkillsFromLock(rootDir, manifest, lockfile, options) {
1415
1515
  await installStageHooks.beforeFetch(rootDir, manifest, lockfile);
1416
1516
  const installDir = manifest.installDir ?? '.agents/skills';
@@ -1519,16 +1619,17 @@ async function installSkills(rootDir, options) {
1519
1619
  } else lockfile = await syncSkillsLock(rootDir, manifest, currentLock, {
1520
1620
  onProgress: options?.onProgress
1521
1621
  });
1522
- await fetchSkillsFromLock(rootDir, manifest, lockfile, {
1622
+ const runtimeLock = await withBundledSelfSkillLock(rootDir, manifest, lockfile);
1623
+ await fetchSkillsFromLock(rootDir, manifest, runtimeLock, {
1523
1624
  onProgress: options?.onProgress
1524
1625
  });
1525
- await linkSkillsFromLock(rootDir, manifest, lockfile, {
1626
+ await linkSkillsFromLock(rootDir, manifest, runtimeLock, {
1526
1627
  onProgress: options?.onProgress
1527
1628
  });
1528
1629
  if (!options?.frozenLockfile) await writeSkillsLock(rootDir, lockfile);
1529
1630
  return {
1530
1631
  status: 'installed',
1531
- installed: Object.keys(lockfile.skills)
1632
+ installed: Object.keys(runtimeLock.skills)
1532
1633
  };
1533
1634
  }
1534
1635
  function buildGitHubSpecifier(owner, repo, skillPath) {
@@ -1625,6 +1726,7 @@ async function addCommand(options) {
1625
1726
  spinner.stop('Installed skills');
1626
1727
  return result;
1627
1728
  }
1729
+ const init_DEFAULT_SCHEMA_URL = `https://unpkg.com/skills-package-manager@${package_namespaceObject.rE}/skills.schema.json`;
1628
1730
  async function assertManifestMissing(cwd) {
1629
1731
  const filePath = node_path.join(cwd, 'skills.json');
1630
1732
  try {
@@ -1648,19 +1750,23 @@ async function assertManifestMissing(cwd) {
1648
1750
  }
1649
1751
  function createDefaultManifest() {
1650
1752
  return {
1753
+ $schema: init_DEFAULT_SCHEMA_URL,
1651
1754
  installDir: '.agents/skills',
1652
1755
  linkTargets: [],
1756
+ selfSkill: false,
1653
1757
  skills: {}
1654
1758
  };
1655
1759
  }
1656
1760
  async function initCommand(options, promptInit = promptInitManifestOptions) {
1657
1761
  await assertManifestMissing(options.cwd);
1658
- const manifest = options.yes ? createDefaultManifest() : {
1762
+ const baseManifest = options.yes ? createDefaultManifest() : {
1763
+ $schema: init_DEFAULT_SCHEMA_URL,
1659
1764
  ...await promptInit(),
1765
+ selfSkill: false,
1660
1766
  skills: {}
1661
1767
  };
1662
- await writeSkillsManifest(options.cwd, manifest);
1663
- return manifest;
1768
+ await writeSkillsManifest(options.cwd, baseManifest);
1769
+ return baseManifest;
1664
1770
  }
1665
1771
  const phaseLabelMap = {
1666
1772
  resolving: 'Resolving',
@@ -1781,11 +1887,11 @@ async function installCommand(options) {
1781
1887
  message: 'No skills.json found in the current directory. Run "spm init" to create one.'
1782
1888
  });
1783
1889
  const currentLock = await readSkillsLock(options.cwd);
1784
- const totalSkills = Object.keys(manifest.skills).length;
1785
1890
  const reporter = createInstallProgressReporter();
1786
1891
  const onProgress = (event)=>reporter.onProgress(event);
1787
1892
  let started = false;
1788
1893
  try {
1894
+ let lockfile;
1789
1895
  if (options.frozenLockfile) {
1790
1896
  if (!currentLock) throw new ManifestError({
1791
1897
  code: codes_ErrorCode.LOCKFILE_NOT_FOUND,
@@ -1797,46 +1903,31 @@ async function installCommand(options) {
1797
1903
  filePath: `${options.cwd}/skills-lock.yaml`,
1798
1904
  message: 'Lockfile is out of sync with manifest. Run install without --frozen-lockfile to update.'
1799
1905
  });
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, {
1906
+ lockfile = currentLock;
1907
+ } else lockfile = await syncSkillsLock(options.cwd, manifest, currentLock, {
1824
1908
  onProgress
1825
1909
  });
1910
+ const runtimeLock = await withBundledSelfSkillLock(options.cwd, manifest, lockfile);
1911
+ reporter.start(Object.keys(runtimeLock.skills).length);
1912
+ started = true;
1913
+ for (const skillName of Object.keys(lockfile.skills))onProgress({
1914
+ type: 'resolved',
1915
+ skillName
1916
+ });
1826
1917
  reporter.setPhase('fetching');
1827
- await fetchSkillsFromLock(options.cwd, manifest, lockfile, {
1918
+ await fetchSkillsFromLock(options.cwd, manifest, runtimeLock, {
1828
1919
  onProgress
1829
1920
  });
1830
1921
  reporter.setPhase('linking');
1831
- await linkSkillsFromLock(options.cwd, manifest, lockfile, {
1922
+ await linkSkillsFromLock(options.cwd, manifest, runtimeLock, {
1832
1923
  onProgress
1833
1924
  });
1834
1925
  reporter.setPhase('finalizing');
1835
- await writeSkillsLock(options.cwd, lockfile);
1926
+ if (!options.frozenLockfile) await writeSkillsLock(options.cwd, lockfile);
1836
1927
  reporter.complete();
1837
1928
  return {
1838
1929
  status: 'installed',
1839
- installed: Object.keys(lockfile.skills)
1930
+ installed: Object.keys(runtimeLock.skills)
1840
1931
  };
1841
1932
  } catch (error) {
1842
1933
  if (started) reporter.fail();
@@ -1922,8 +2013,9 @@ async function updateCommand(options) {
1922
2013
  result.status = 'failed';
1923
2014
  return result;
1924
2015
  }
1925
- await fetchSkillsFromLock(options.cwd, manifest, candidateLock);
1926
- await linkSkillsFromLock(options.cwd, manifest, candidateLock);
2016
+ const runtimeLock = await withBundledSelfSkillLock(options.cwd, manifest, candidateLock);
2017
+ await fetchSkillsFromLock(options.cwd, manifest, runtimeLock);
2018
+ await linkSkillsFromLock(options.cwd, manifest, runtimeLock);
1927
2019
  await writeSkillsLock(options.cwd, candidateLock);
1928
2020
  result.status = result.updated.length > 0 ? 'updated' : 'skipped';
1929
2021
  return result;
@@ -1996,4 +2088,4 @@ async function runCli(argv, context = {}) {
1996
2088
  throw error;
1997
2089
  }
1998
2090
  }
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 };
2091
+ 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.6.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,55 @@
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
+ "pnpmPlugin": {
27
+ "description": "pnpm-plugin-skills specific compatibility settings",
28
+ "type": "object",
29
+ "properties": {
30
+ "removePnpmfileChecksum": {
31
+ "description": "Temporarily remove pnpmfileChecksum from pnpm lockfiles in pnpm-plugin-skills afterAllResolved",
32
+ "type": "boolean"
33
+ }
34
+ },
35
+ "additionalProperties": false
36
+ },
37
+ "skills": {
38
+ "default": {},
39
+ "description": "Map of skill names to their specifiers",
40
+ "type": "object",
41
+ "propertyNames": {
42
+ "type": "string"
43
+ },
44
+ "additionalProperties": {
45
+ "type": "string"
46
+ }
47
+ }
48
+ },
49
+ "required": [
50
+ "installDir",
51
+ "linkTargets",
52
+ "skills"
53
+ ],
54
+ "additionalProperties": false
55
+ }