skillpm 0.0.6 → 0.0.7
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 +23 -0
- package/dist/cli.js +1 -1
- package/dist/commands/install.js +4 -2
- package/dist/manifest/schema.d.ts +5 -0
- package/dist/scanner/index.d.ts +2 -0
- package/dist/scanner/index.js +23 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,29 @@ skillpm doesn't reinvent anything. It orchestrates three battle-tested tools: np
|
|
|
70
70
|
|
|
71
71
|
Aliases: `i` for `install`, `rm`/`remove` for `uninstall`, `ls` for `list`.
|
|
72
72
|
|
|
73
|
+
## Monorepo / npm workspace support
|
|
74
|
+
|
|
75
|
+
If your repo is an **npm workspace monorepo** where each skill is a first-party package (e.g. `skills/<name>/` entries in the root `package.json` workspaces field), npm installs them as symlinks inside `node_modules/`:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
node_modules/
|
|
79
|
+
@org/
|
|
80
|
+
my-skill → ../../skills/my-skill ← symlink
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`skillpm sync` (and `skillpm install`) automatically detects these symlinks and treats them as workspace packages:
|
|
84
|
+
|
|
85
|
+
- Configs are copied from the symlinked skill's `configs/` directory into the workspace root, exactly as for externally installed skills.
|
|
86
|
+
- Workspace packages are identified in log output: `Linking workspace package @org/my-skill@1.0.0`.
|
|
87
|
+
|
|
88
|
+
This lets contributors regenerate deployed copies (agent definitions, prompts, rules) by running:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
skillpm sync
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
No manual copy steps needed. Commit the regenerated files as usual.
|
|
95
|
+
|
|
73
96
|
## Creating a skill
|
|
74
97
|
|
|
75
98
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { publish } from './commands/publish.js';
|
|
|
7
7
|
import { sync } from './commands/sync.js';
|
|
8
8
|
import { mcp } from './commands/mcp.js';
|
|
9
9
|
import { log } from './utils/index.js';
|
|
10
|
-
const VERSION = '0.0.
|
|
10
|
+
const VERSION = '0.0.7';
|
|
11
11
|
const HELP = `
|
|
12
12
|
skillpm — Agent Skill package manager
|
|
13
13
|
|
package/dist/commands/install.js
CHANGED
|
@@ -36,7 +36,8 @@ export async function wireSkills(cwd) {
|
|
|
36
36
|
log.info(`Found ${skills.length} skill package(s)`);
|
|
37
37
|
// Wire each skill into agent directories via skills CLI
|
|
38
38
|
for (const skill of skills) {
|
|
39
|
-
|
|
39
|
+
const label = skill.workspace ? `workspace package ${log.skill(skill.name, skill.version)}` : log.skill(skill.name, skill.version);
|
|
40
|
+
log.info(`Linking ${label} into agent directories`);
|
|
40
41
|
try {
|
|
41
42
|
await npx(['skills', 'add', skill.skillDir, '--all', '-y'], { cwd });
|
|
42
43
|
log.success(`Linked ${skill.name}`);
|
|
@@ -68,7 +69,8 @@ export async function wireSkills(cwd) {
|
|
|
68
69
|
// Copy configs/ files (agents, prompts, rules) into workspace
|
|
69
70
|
for (const skill of skills) {
|
|
70
71
|
if (skill.configsDir) {
|
|
71
|
-
|
|
72
|
+
const label = skill.workspace ? `workspace package ${log.skill(skill.name, skill.version)}` : log.skill(skill.name, skill.version);
|
|
73
|
+
log.info(`Copying config files from ${label}`);
|
|
72
74
|
try {
|
|
73
75
|
const copied = await copyConfigs(skill.configsDir, cwd, skill.name, skill.configPrefix);
|
|
74
76
|
log.success(`Copied ${copied.length} config file(s) from ${skill.name}`);
|
|
@@ -29,4 +29,9 @@ export interface SkillInfo {
|
|
|
29
29
|
* Declared via skillpm.configPrefix in package.json.
|
|
30
30
|
*/
|
|
31
31
|
configPrefix?: string;
|
|
32
|
+
/**
|
|
33
|
+
* True when the package was found via a symlink in node_modules/ — typically
|
|
34
|
+
* an npm workspace package (first-party skill in the same monorepo).
|
|
35
|
+
*/
|
|
36
|
+
workspace?: boolean;
|
|
32
37
|
}
|
package/dist/scanner/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SkillInfo } from '../manifest/schema.js';
|
|
2
2
|
/**
|
|
3
3
|
* Scan node_modules/ for packages that contain a skills/<name>/SKILL.md file.
|
|
4
|
+
* Also follows symlinks — npm workspace packages appear as symlinks inside
|
|
5
|
+
* node_modules/ and are flagged with `workspace: true` on the returned SkillInfo.
|
|
4
6
|
* Returns metadata for each discovered skill package.
|
|
5
7
|
*/
|
|
6
8
|
export declare function scanNodeModules(cwd: string): Promise<SkillInfo[]>;
|
package/dist/scanner/index.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { readdir, access } from 'node:fs/promises';
|
|
1
|
+
import { readdir, access, lstat } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { readPackageJson, parseSkillpmField } from '../manifest/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Return true if the given path is a symbolic link (or on Windows, a junction).
|
|
6
|
+
* Does NOT follow the link — uses lstat so the link itself is inspected.
|
|
7
|
+
*/
|
|
8
|
+
async function isSymlink(p) {
|
|
9
|
+
try {
|
|
10
|
+
const s = await lstat(p);
|
|
11
|
+
return s.isSymbolicLink();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
4
17
|
/**
|
|
5
18
|
* Scan node_modules/ for packages that contain a skills/<name>/SKILL.md file.
|
|
19
|
+
* Also follows symlinks — npm workspace packages appear as symlinks inside
|
|
20
|
+
* node_modules/ and are flagged with `workspace: true` on the returned SkillInfo.
|
|
6
21
|
* Returns metadata for each discovered skill package.
|
|
7
22
|
*/
|
|
8
23
|
export async function scanNodeModules(cwd) {
|
|
@@ -30,14 +45,16 @@ export async function scanNodeModules(cwd) {
|
|
|
30
45
|
}
|
|
31
46
|
for (const scopedEntry of scopedEntries) {
|
|
32
47
|
const pkgDir = join(scopeDir, scopedEntry);
|
|
33
|
-
const
|
|
48
|
+
const symlink = await isSymlink(pkgDir);
|
|
49
|
+
const skill = await tryReadSkill(pkgDir, symlink);
|
|
34
50
|
if (skill)
|
|
35
51
|
skills.push(skill);
|
|
36
52
|
}
|
|
37
53
|
}
|
|
38
54
|
else {
|
|
39
55
|
const pkgDir = join(nodeModulesDir, entry);
|
|
40
|
-
const
|
|
56
|
+
const symlink = await isSymlink(pkgDir);
|
|
57
|
+
const skill = await tryReadSkill(pkgDir, symlink);
|
|
41
58
|
if (skill)
|
|
42
59
|
skills.push(skill);
|
|
43
60
|
}
|
|
@@ -53,7 +70,7 @@ async function hasDir(dir) {
|
|
|
53
70
|
return false;
|
|
54
71
|
}
|
|
55
72
|
}
|
|
56
|
-
async function tryReadSkill(pkgDir) {
|
|
73
|
+
async function tryReadSkill(pkgDir, workspace = false) {
|
|
57
74
|
const pkg = await readPackageJson(pkgDir);
|
|
58
75
|
if (!pkg)
|
|
59
76
|
return null;
|
|
@@ -86,6 +103,7 @@ async function tryReadSkill(pkgDir) {
|
|
|
86
103
|
mcpServers: skillpm?.mcpServers ?? [],
|
|
87
104
|
configsDir: hasConfigs ? configsDir : undefined,
|
|
88
105
|
configPrefix: skillpm?.configPrefix,
|
|
106
|
+
workspace: workspace || undefined,
|
|
89
107
|
};
|
|
90
108
|
}
|
|
91
109
|
// Fallback: root SKILL.md (legacy format)
|
|
@@ -105,6 +123,7 @@ async function tryReadSkill(pkgDir) {
|
|
|
105
123
|
legacy: true,
|
|
106
124
|
configsDir: hasConfigs ? configsDir : undefined,
|
|
107
125
|
configPrefix: skillpm?.configPrefix,
|
|
126
|
+
workspace: workspace || undefined,
|
|
108
127
|
};
|
|
109
128
|
}
|
|
110
129
|
/**
|