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.
- package/README.md +105 -0
- package/dist/113.js +355 -0
- package/dist/bin/skills-pm.js +6 -0
- package/dist/bin/skills.js +6 -0
- package/dist/index.js +1 -0
- package/dist/src/bin/skills-pm.d.ts +2 -0
- package/dist/src/bin/skills.d.ts +2 -0
- package/dist/src/cli/runCli.d.ts +16 -0
- package/dist/src/commands/add.d.ts +5 -0
- package/dist/src/commands/install.d.ts +15 -0
- package/dist/src/config/readSkillsLock.d.ts +2 -0
- package/dist/src/config/readSkillsManifest.d.ts +2 -0
- package/dist/src/config/syncSkillsLock.d.ts +2 -0
- package/dist/src/config/types.d.ts +43 -0
- package/dist/src/config/writeSkillsLock.d.ts +2 -0
- package/dist/src/config/writeSkillsManifest.d.ts +2 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/install/installSkills.d.ts +13 -0
- package/dist/src/install/installState.d.ts +2 -0
- package/dist/src/install/links.d.ts +1 -0
- package/dist/src/install/materializeGitSkill.d.ts +1 -0
- package/dist/src/install/materializeLocalSkill.d.ts +1 -0
- package/dist/src/install/pruneManagedSkills.d.ts +1 -0
- package/dist/src/specifiers/normalizeSpecifier.d.ts +2 -0
- package/dist/src/specifiers/parseSpecifier.d.ts +5 -0
- package/dist/src/utils/fs.d.ts +4 -0
- package/dist/src/utils/hash.d.ts +1 -0
- package/dist/test/add.test.d.ts +1 -0
- package/dist/test/install.test.d.ts +1 -0
- package/dist/test/manifest.test.d.ts +1 -0
- package/dist/test/specifiers.test.d.ts +1 -0
- package/package.json +25 -0
- package/rslib.config.ts +21 -0
- package/src/bin/skills-pm.ts +7 -0
- package/src/bin/skills.ts +7 -0
- package/src/cli/prompt.ts +36 -0
- package/src/cli/runCli.ts +45 -0
- package/src/commands/add.ts +110 -0
- package/src/commands/install.ts +5 -0
- package/src/config/readSkillsLock.ts +18 -0
- package/src/config/readSkillsManifest.ts +22 -0
- package/src/config/syncSkillsLock.ts +75 -0
- package/src/config/types.ts +37 -0
- package/src/config/writeSkillsLock.ts +9 -0
- package/src/config/writeSkillsManifest.ts +14 -0
- package/src/github/listSkills.ts +170 -0
- package/src/github/types.ts +5 -0
- package/src/index.ts +5 -0
- package/src/install/installSkills.ts +78 -0
- package/src/install/installState.ts +20 -0
- package/src/install/links.ts +9 -0
- package/src/install/materializeGitSkill.ts +33 -0
- package/src/install/materializeLocalSkill.ts +35 -0
- package/src/install/pruneManagedSkills.ts +50 -0
- package/src/specifiers/normalizeSpecifier.ts +29 -0
- package/src/specifiers/parseSpecifier.ts +45 -0
- package/src/utils/fs.ts +19 -0
- package/src/utils/hash.ts +5 -0
- package/test/add.test.ts +75 -0
- package/test/fixtures/local-source/skills/hello-skill/SKILL.md +3 -0
- package/test/fixtures/local-source/skills/hello-skill/references/example.md +1 -0
- package/test/github.test.ts +120 -0
- package/test/install.test.ts +169 -0
- package/test/manifest.test.ts +19 -0
- package/test/specifiers.test.ts +43 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { describe, expect, it } from '@rstest/core'
|
|
2
|
+
import { mkdtempSync, existsSync, lstatSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import YAML from 'yaml'
|
|
6
|
+
import { installSkills } from '../src/install/installSkills'
|
|
7
|
+
import { writeSkillsManifest } from '../src/config/writeSkillsManifest'
|
|
8
|
+
import { writeSkillsLock } from '../src/config/writeSkillsLock'
|
|
9
|
+
|
|
10
|
+
describe('installSkills', () => {
|
|
11
|
+
it('installs a local skill and creates symlinks', async () => {
|
|
12
|
+
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-install-'))
|
|
13
|
+
await writeSkillsManifest(root, {
|
|
14
|
+
installDir: '.agents/skills',
|
|
15
|
+
linkTargets: ['.cursor/skills'],
|
|
16
|
+
skills: {
|
|
17
|
+
'hello-skill': `file:${path.resolve(__dirname, 'fixtures/local-source')}#path:/skills/hello-skill`,
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
await writeSkillsLock(root, {
|
|
21
|
+
lockfileVersion: '0.1',
|
|
22
|
+
installDir: '.agents/skills',
|
|
23
|
+
linkTargets: ['.cursor/skills'],
|
|
24
|
+
skills: {
|
|
25
|
+
'hello-skill': {
|
|
26
|
+
specifier: `file:${path.resolve(__dirname, 'fixtures/local-source')}#path:/skills/hello-skill`,
|
|
27
|
+
resolution: {
|
|
28
|
+
type: 'file',
|
|
29
|
+
path: path.resolve(__dirname, 'fixtures/local-source'),
|
|
30
|
+
},
|
|
31
|
+
digest: 'test-digest',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await installSkills(root)
|
|
37
|
+
|
|
38
|
+
const installedSkill = path.join(root, '.agents/skills/hello-skill/SKILL.md')
|
|
39
|
+
const linkedSkill = path.join(root, '.cursor/skills/hello-skill')
|
|
40
|
+
expect(existsSync(installedSkill)).toBe(true)
|
|
41
|
+
expect(lstatSync(linkedSkill).isSymbolicLink()).toBe(true)
|
|
42
|
+
expect(readFileSync(installedSkill, 'utf8')).toContain('Hello skill')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('installs a git skill from a local git repository', async () => {
|
|
46
|
+
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-install-git-'))
|
|
47
|
+
const gitRepo = mkdtempSync(path.join(tmpdir(), 'skills-pm-git-source-'))
|
|
48
|
+
|
|
49
|
+
require('node:fs').mkdirSync(path.join(gitRepo, 'skills/hello-git-skill'), { recursive: true })
|
|
50
|
+
require('node:fs').writeFileSync(path.join(gitRepo, 'skills/hello-git-skill/SKILL.md'), '# Hello from git\n')
|
|
51
|
+
require('node:child_process').execSync('git init', { cwd: gitRepo, stdio: 'ignore' })
|
|
52
|
+
require('node:child_process').execSync('git config user.email test@example.com', { cwd: gitRepo, stdio: 'ignore' })
|
|
53
|
+
require('node:child_process').execSync('git config user.name test', { cwd: gitRepo, stdio: 'ignore' })
|
|
54
|
+
require('node:child_process').execSync('git add .', { cwd: gitRepo, stdio: 'ignore' })
|
|
55
|
+
require('node:child_process').execSync('git commit -m init', { cwd: gitRepo, stdio: 'ignore' })
|
|
56
|
+
|
|
57
|
+
await writeSkillsManifest(root, {
|
|
58
|
+
installDir: '.agents/skills',
|
|
59
|
+
linkTargets: [],
|
|
60
|
+
skills: {
|
|
61
|
+
'hello-git-skill': `${gitRepo}#HEAD&path:/skills/hello-git-skill`,
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
await writeSkillsLock(root, {
|
|
65
|
+
lockfileVersion: '0.1',
|
|
66
|
+
installDir: '.agents/skills',
|
|
67
|
+
linkTargets: [],
|
|
68
|
+
skills: {
|
|
69
|
+
'hello-git-skill': {
|
|
70
|
+
specifier: `${gitRepo}#HEAD&path:/skills/hello-git-skill`,
|
|
71
|
+
resolution: {
|
|
72
|
+
type: 'git',
|
|
73
|
+
url: gitRepo,
|
|
74
|
+
commit: 'HEAD',
|
|
75
|
+
path: '/skills/hello-git-skill',
|
|
76
|
+
},
|
|
77
|
+
digest: 'test-git-digest',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await installSkills(root)
|
|
83
|
+
|
|
84
|
+
const installedSkill = path.join(root, '.agents/skills/hello-git-skill/SKILL.md')
|
|
85
|
+
expect(existsSync(installedSkill)).toBe(true)
|
|
86
|
+
expect(readFileSync(installedSkill, 'utf8')).toContain('Hello from git')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('updates stale lock entries from manifest before installing', async () => {
|
|
90
|
+
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-install-stale-lock-'))
|
|
91
|
+
const gitRepo = mkdtempSync(path.join(tmpdir(), 'skills-pm-git-stale-source-'))
|
|
92
|
+
|
|
93
|
+
require('node:fs').mkdirSync(path.join(gitRepo, 'skills/fixed-skill'), { recursive: true })
|
|
94
|
+
require('node:fs').writeFileSync(path.join(gitRepo, 'skills/fixed-skill/SKILL.md'), '# Fixed skill\n')
|
|
95
|
+
require('node:child_process').execSync('git init', { cwd: gitRepo, stdio: 'ignore' })
|
|
96
|
+
require('node:child_process').execSync('git config user.email test@example.com', { cwd: gitRepo, stdio: 'ignore' })
|
|
97
|
+
require('node:child_process').execSync('git config user.name test', { cwd: gitRepo, stdio: 'ignore' })
|
|
98
|
+
require('node:child_process').execSync('git add .', { cwd: gitRepo, stdio: 'ignore' })
|
|
99
|
+
require('node:child_process').execSync('git commit -m init', { cwd: gitRepo, stdio: 'ignore' })
|
|
100
|
+
|
|
101
|
+
await writeSkillsManifest(root, {
|
|
102
|
+
installDir: '.agents/skills',
|
|
103
|
+
linkTargets: [],
|
|
104
|
+
skills: {
|
|
105
|
+
'fixed-skill': `${gitRepo}#HEAD&path:/skills/fixed-skill`,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
writeFileSync(
|
|
110
|
+
path.join(root, 'skills-lock.yaml'),
|
|
111
|
+
YAML.stringify({
|
|
112
|
+
lockfileVersion: '0.1',
|
|
113
|
+
installDir: '.agents/skills',
|
|
114
|
+
linkTargets: [],
|
|
115
|
+
skills: {
|
|
116
|
+
'': {
|
|
117
|
+
specifier: gitRepo,
|
|
118
|
+
resolution: {
|
|
119
|
+
type: 'git',
|
|
120
|
+
url: gitRepo,
|
|
121
|
+
commit: 'HEAD',
|
|
122
|
+
path: '/',
|
|
123
|
+
},
|
|
124
|
+
digest: 'bad-digest',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
await installSkills(root)
|
|
131
|
+
|
|
132
|
+
const installedSkill = path.join(root, '.agents/skills/fixed-skill/SKILL.md')
|
|
133
|
+
const rewrittenLock = YAML.parse(readFileSync(path.join(root, 'skills-lock.yaml'), 'utf8'))
|
|
134
|
+
|
|
135
|
+
expect(existsSync(installedSkill)).toBe(true)
|
|
136
|
+
expect(readFileSync(installedSkill, 'utf8')).toContain('Fixed skill')
|
|
137
|
+
expect(rewrittenLock.skills['fixed-skill'].specifier).toBe(`${gitRepo}#HEAD&path:/skills/fixed-skill`)
|
|
138
|
+
expect(rewrittenLock.skills['']).toBeUndefined()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('removes managed skills that are no longer declared', async () => {
|
|
142
|
+
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-prune-'))
|
|
143
|
+
|
|
144
|
+
await writeSkillsManifest(root, {
|
|
145
|
+
installDir: '.agents/skills',
|
|
146
|
+
linkTargets: ['.cursor/skills'],
|
|
147
|
+
skills: {
|
|
148
|
+
'hello-skill': `file:${path.resolve(__dirname, 'fixtures/local-source')}#path:/skills/hello-skill`,
|
|
149
|
+
'obsolete-skill': `file:${path.resolve(__dirname, 'fixtures/local-source')}#path:/skills/hello-skill`,
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
await installSkills(root)
|
|
154
|
+
|
|
155
|
+
await writeSkillsManifest(root, {
|
|
156
|
+
installDir: '.agents/skills',
|
|
157
|
+
linkTargets: ['.cursor/skills'],
|
|
158
|
+
skills: {
|
|
159
|
+
'hello-skill': `file:${path.resolve(__dirname, 'fixtures/local-source')}#path:/skills/hello-skill`,
|
|
160
|
+
},
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
await installSkills(root)
|
|
164
|
+
|
|
165
|
+
expect(existsSync(path.join(root, '.agents/skills/obsolete-skill'))).toBe(false)
|
|
166
|
+
expect(existsSync(path.join(root, '.cursor/skills/obsolete-skill'))).toBe(false)
|
|
167
|
+
expect(existsSync(path.join(root, '.agents/skills/hello-skill'))).toBe(true)
|
|
168
|
+
})
|
|
169
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from '@rstest/core'
|
|
2
|
+
import { mkdtempSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { writeSkillsManifest } from '../src/config/writeSkillsManifest'
|
|
6
|
+
import { readSkillsManifest } from '../src/config/readSkillsManifest'
|
|
7
|
+
|
|
8
|
+
describe('manifest io', () => {
|
|
9
|
+
it('writes default manifest shape', async () => {
|
|
10
|
+
const root = mkdtempSync(path.join(tmpdir(), 'skills-pm-'))
|
|
11
|
+
await writeSkillsManifest(root, { skills: { hello: 'file:./skills/hello' } })
|
|
12
|
+
const manifest = await readSkillsManifest(root)
|
|
13
|
+
expect(manifest).toEqual({
|
|
14
|
+
installDir: '.agents/skills',
|
|
15
|
+
linkTargets: [],
|
|
16
|
+
skills: { hello: 'file:./skills/hello' },
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from '@rstest/core'
|
|
2
|
+
import { normalizeSpecifier } from '../src/specifiers/normalizeSpecifier'
|
|
3
|
+
|
|
4
|
+
describe('normalizeSpecifier', () => {
|
|
5
|
+
it('parses git path specifier', () => {
|
|
6
|
+
expect(normalizeSpecifier('https://github.com/acme/skills.git#main&path:/skills/hello')).toEqual({
|
|
7
|
+
type: 'git',
|
|
8
|
+
source: 'https://github.com/acme/skills.git',
|
|
9
|
+
ref: 'main',
|
|
10
|
+
path: '/skills/hello',
|
|
11
|
+
normalized: 'https://github.com/acme/skills.git#main&path:/skills/hello',
|
|
12
|
+
skillName: 'hello',
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('parses file path specifier', () => {
|
|
17
|
+
expect(normalizeSpecifier('file:./fixtures/local-source#path:/skills/hello-skill')).toEqual({
|
|
18
|
+
type: 'file',
|
|
19
|
+
source: 'file:./fixtures/local-source',
|
|
20
|
+
ref: null,
|
|
21
|
+
path: '/skills/hello-skill',
|
|
22
|
+
normalized: 'file:./fixtures/local-source#path:/skills/hello-skill',
|
|
23
|
+
skillName: 'hello-skill',
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('parses git specifier without ref', () => {
|
|
28
|
+
expect(normalizeSpecifier('https://github.com/acme/skills.git#path:/skills/world')).toEqual({
|
|
29
|
+
type: 'git',
|
|
30
|
+
source: 'https://github.com/acme/skills.git',
|
|
31
|
+
ref: null,
|
|
32
|
+
path: '/skills/world',
|
|
33
|
+
normalized: 'https://github.com/acme/skills.git#path:/skills/world',
|
|
34
|
+
skillName: 'world',
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('rejects duplicate path fragments', () => {
|
|
39
|
+
expect(() =>
|
|
40
|
+
normalizeSpecifier('https://github.com/acme/skills.git#path:/skills/world#path:/skills/world'),
|
|
41
|
+
).toThrow('Invalid specifier: multiple # fragments are not supported')
|
|
42
|
+
})
|
|
43
|
+
})
|