kungeskill 0.3.1 → 0.4.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 +13 -0
- package/package.json +1 -1
- package/src/commands/add.js +5 -11
- package/src/commands/list.js +5 -12
- package/src/commands/shared.js +20 -9
- package/src/core/cache.js +21 -1
- package/src/core/registry.js +65 -11
package/README.md
CHANGED
|
@@ -37,6 +37,19 @@ kungeskill add openspec-explore
|
|
|
37
37
|
| `kungeskill update` | Update marketplace cache to latest |
|
|
38
38
|
| `kungeskill doctor` | Check symlink health and detect broken links |
|
|
39
39
|
|
|
40
|
+
### Uninstall
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Remove a skill from your project
|
|
44
|
+
kungeskill remove openspec-explore
|
|
45
|
+
|
|
46
|
+
# Uninstall kungeskill CLI globally
|
|
47
|
+
npm uninstall -g kungeskill
|
|
48
|
+
|
|
49
|
+
# (Optional) Clean up marketplace cache
|
|
50
|
+
rm -rf ~/.kungeskills
|
|
51
|
+
```
|
|
52
|
+
|
|
40
53
|
### Option 2: Claude Code Plugin Marketplace
|
|
41
54
|
|
|
42
55
|
Install skills via Claude Code's built-in plugin system.
|
package/package.json
CHANGED
package/src/commands/add.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { getMarketplaceSourceDir, isCacheValid, hasBundledPlugins } = require('../core/cache');
|
|
6
|
-
const {
|
|
5
|
+
const { getMarketplaceSourceDir, isCacheValid, hasBundledPlugins, getAllMarketplaceDirs } = require('../core/cache');
|
|
6
|
+
const { findSkillMerged } = require('../core/registry');
|
|
7
7
|
const { createSkillSymlink, canCreateJunction, copyDirRecursive } = require('../core/symlink');
|
|
8
8
|
const { findProjectSkillsDir } = require('./shared');
|
|
9
9
|
const logger = require('../utils/logger');
|
|
@@ -18,16 +18,10 @@ async function cmdAdd(skillName, opts) {
|
|
|
18
18
|
await cmdInit();
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
const
|
|
21
|
+
// Find the skill across all sources (git cache + bundled)
|
|
22
|
+
const sourceDirs = getAllMarketplaceDirs();
|
|
23
|
+
const skill = findSkillMerged(sourceDirs, skillName);
|
|
23
24
|
|
|
24
|
-
if (!marketplace) {
|
|
25
|
-
logger.error('Invalid marketplace manifest.');
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Find the skill in the marketplace
|
|
30
|
-
const skill = findSkill(marketplace, cacheDir, skillName);
|
|
31
25
|
if (!skill) {
|
|
32
26
|
logger.error(`Skill '${skillName}' not found in marketplace.`);
|
|
33
27
|
logger.info('Run "kungeskill list" to see available skills.');
|
package/src/commands/list.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const { getMarketplaceSourceDir, isCacheValid, hasBundledPlugins } = require('../core/cache');
|
|
6
|
-
const { parseMarketplace,
|
|
5
|
+
const { getMarketplaceSourceDir, isCacheValid, hasBundledPlugins, getAllMarketplaceDirs } = require('../core/cache');
|
|
6
|
+
const { parseMarketplace, listAllSkillsMerged } = require('../core/registry');
|
|
7
7
|
const { findProjectSkillsDir } = require('./shared');
|
|
8
8
|
const logger = require('../utils/logger');
|
|
9
9
|
|
|
@@ -13,14 +13,6 @@ function cmdList(opts) {
|
|
|
13
13
|
process.exit(1);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const cacheDir = getMarketplaceSourceDir();
|
|
17
|
-
const marketplace = parseMarketplace(cacheDir);
|
|
18
|
-
|
|
19
|
-
if (!marketplace) {
|
|
20
|
-
logger.error('Invalid marketplace manifest. Run "kungeskill init" to reinitialize.');
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
16
|
const showInstalled = opts.installed;
|
|
25
17
|
|
|
26
18
|
if (showInstalled) {
|
|
@@ -48,8 +40,9 @@ function cmdList(opts) {
|
|
|
48
40
|
return;
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
// Show all available skills
|
|
52
|
-
const
|
|
43
|
+
// Show all available skills merged from all sources (git cache + bundled)
|
|
44
|
+
const sourceDirs = getAllMarketplaceDirs();
|
|
45
|
+
const skills = listAllSkillsMerged(sourceDirs);
|
|
53
46
|
|
|
54
47
|
if (skills.length === 0) {
|
|
55
48
|
logger.info('No skills found in marketplace.');
|
package/src/commands/shared.js
CHANGED
|
@@ -5,7 +5,8 @@ const path = require('path');
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Find the project's .claude/skills/ directory.
|
|
8
|
-
* Walks up from cwd until it finds
|
|
8
|
+
* Walks up from cwd until it finds a .git boundary or existing .claude/skills/.
|
|
9
|
+
* Will not cross project boundaries (stops at .git root).
|
|
9
10
|
*
|
|
10
11
|
* @param {string} [cwd] - Starting directory (defaults to process.cwd())
|
|
11
12
|
* @returns {string} Absolute path to .claude/skills/
|
|
@@ -15,17 +16,27 @@ function findProjectSkillsDir(cwd) {
|
|
|
15
16
|
let dir = path.resolve(cwd);
|
|
16
17
|
const root = path.parse(dir).root;
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// First pass: find the project root (directory with .git)
|
|
20
|
+
let projectRoot = null;
|
|
21
|
+
let searchDir = dir;
|
|
22
|
+
while (searchDir !== root) {
|
|
23
|
+
if (fs.existsSync(path.join(searchDir, '.git'))) {
|
|
24
|
+
projectRoot = searchDir;
|
|
25
|
+
break;
|
|
22
26
|
}
|
|
23
|
-
|
|
27
|
+
searchDir = path.dirname(searchDir);
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
// If no .git found, use cwd as project root
|
|
31
|
+
if (!projectRoot) {
|
|
32
|
+
projectRoot = cwd;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Use .claude/skills/ at the project root
|
|
36
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
37
|
+
if (!fs.existsSync(skillsDir)) {
|
|
38
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
39
|
+
}
|
|
29
40
|
return skillsDir;
|
|
30
41
|
}
|
|
31
42
|
|
package/src/core/cache.js
CHANGED
|
@@ -57,11 +57,31 @@ function getMarketplaceSourceDir() {
|
|
|
57
57
|
return getCacheDir();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get all marketplace source directories (for merging skills from multiple sources).
|
|
62
|
+
* Returns both git cache and bundled if both exist.
|
|
63
|
+
* @returns {string[]}
|
|
64
|
+
*/
|
|
65
|
+
function getAllMarketplaceDirs() {
|
|
66
|
+
const dirs = [];
|
|
67
|
+
if (isCacheValid()) {
|
|
68
|
+
dirs.push(getCacheDir());
|
|
69
|
+
}
|
|
70
|
+
if (hasBundledPlugins()) {
|
|
71
|
+
const bundled = getBundledDir();
|
|
72
|
+
if (!dirs.includes(bundled)) {
|
|
73
|
+
dirs.push(bundled);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return dirs;
|
|
77
|
+
}
|
|
78
|
+
|
|
60
79
|
module.exports = {
|
|
61
80
|
getCacheDir,
|
|
62
81
|
ensureCacheDir,
|
|
63
82
|
isCacheValid,
|
|
64
83
|
hasBundledPlugins,
|
|
65
84
|
getBundledDir,
|
|
66
|
-
getMarketplaceSourceDir
|
|
85
|
+
getMarketplaceSourceDir,
|
|
86
|
+
getAllMarketplaceDirs
|
|
67
87
|
};
|
package/src/core/registry.js
CHANGED
|
@@ -4,12 +4,12 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Parse marketplace.json from
|
|
8
|
-
* @param {string}
|
|
7
|
+
* Parse marketplace.json from a source directory.
|
|
8
|
+
* @param {string} sourceDir - Path to the marketplace source root
|
|
9
9
|
* @returns {object|null} Parsed marketplace data or null
|
|
10
10
|
*/
|
|
11
|
-
function parseMarketplace(
|
|
12
|
-
const manifestPath = path.join(
|
|
11
|
+
function parseMarketplace(sourceDir) {
|
|
12
|
+
const manifestPath = path.join(sourceDir, '.claude-plugin', 'marketplace.json');
|
|
13
13
|
if (!fs.existsSync(manifestPath)) {
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
@@ -18,24 +18,23 @@ function parseMarketplace(cacheDir) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* List all skills from a
|
|
21
|
+
* List all skills from a single source directory.
|
|
22
22
|
* @param {object} marketplace - Parsed marketplace.json
|
|
23
|
-
* @param {string}
|
|
23
|
+
* @param {string} sourceDir - Path to the marketplace source root
|
|
24
24
|
* @returns {Array<{skillName: string, pluginName: string, sourcePath: string}>}
|
|
25
25
|
*/
|
|
26
|
-
function
|
|
26
|
+
function listSkillsFromSource(marketplace, sourceDir) {
|
|
27
27
|
if (!marketplace || !marketplace.plugins) return [];
|
|
28
28
|
|
|
29
29
|
const skills = [];
|
|
30
30
|
for (const plugin of marketplace.plugins) {
|
|
31
|
-
const skillsDir = path.resolve(
|
|
31
|
+
const skillsDir = path.resolve(sourceDir, plugin.source, 'skills');
|
|
32
32
|
if (!fs.existsSync(skillsDir)) continue;
|
|
33
33
|
|
|
34
34
|
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
35
35
|
for (const entry of entries) {
|
|
36
36
|
if (entry.isDirectory()) {
|
|
37
37
|
const skillPath = path.join(skillsDir, entry.name);
|
|
38
|
-
// Verify it has SKILL.md
|
|
39
38
|
if (fs.existsSync(path.join(skillPath, 'SKILL.md'))) {
|
|
40
39
|
skills.push({
|
|
41
40
|
skillName: entry.name,
|
|
@@ -50,7 +49,60 @@ function listAllSkills(marketplace, cacheDir) {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
/**
|
|
53
|
-
*
|
|
52
|
+
* List all skills from a parsed marketplace manifest (single source).
|
|
53
|
+
* @param {object} marketplace - Parsed marketplace.json
|
|
54
|
+
* @param {string} cacheDir - Path to the marketplace cache root (for resolving source paths)
|
|
55
|
+
* @returns {Array<{skillName: string, pluginName: string, sourcePath: string}>}
|
|
56
|
+
*/
|
|
57
|
+
function listAllSkills(marketplace, cacheDir) {
|
|
58
|
+
return listSkillsFromSource(marketplace, cacheDir);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* List all skills merged from multiple source directories.
|
|
63
|
+
* Later sources add skills not already found in earlier sources (no duplicates).
|
|
64
|
+
* @param {string[]} sourceDirs - Array of marketplace source directories
|
|
65
|
+
* @returns {Array<{skillName: string, pluginName: string, sourcePath: string}>}
|
|
66
|
+
*/
|
|
67
|
+
function listAllSkillsMerged(sourceDirs) {
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const merged = [];
|
|
70
|
+
|
|
71
|
+
for (const dir of sourceDirs) {
|
|
72
|
+
const marketplace = parseMarketplace(dir);
|
|
73
|
+
if (!marketplace) continue;
|
|
74
|
+
|
|
75
|
+
const skills = listSkillsFromSource(marketplace, dir);
|
|
76
|
+
for (const skill of skills) {
|
|
77
|
+
if (!seen.has(skill.skillName)) {
|
|
78
|
+
seen.add(skill.skillName);
|
|
79
|
+
merged.push(skill);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Find a specific skill by name across multiple sources.
|
|
88
|
+
* @param {string[]} sourceDirs - Array of marketplace source directories
|
|
89
|
+
* @param {string} skillName - Skill name to find
|
|
90
|
+
* @returns {object|null} { skillName, pluginName, sourcePath } or null
|
|
91
|
+
*/
|
|
92
|
+
function findSkillMerged(sourceDirs, skillName) {
|
|
93
|
+
for (const dir of sourceDirs) {
|
|
94
|
+
const marketplace = parseMarketplace(dir);
|
|
95
|
+
if (!marketplace) continue;
|
|
96
|
+
|
|
97
|
+
const skills = listSkillsFromSource(marketplace, dir);
|
|
98
|
+
const found = skills.find(s => s.skillName === skillName);
|
|
99
|
+
if (found) return found;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Find a specific skill by name (single source).
|
|
54
106
|
* @param {object} marketplace - Parsed marketplace.json
|
|
55
107
|
* @param {string} cacheDir - Path to the marketplace cache root
|
|
56
108
|
* @param {string} skillName - Skill name to find
|
|
@@ -64,5 +116,7 @@ function findSkill(marketplace, cacheDir, skillName) {
|
|
|
64
116
|
module.exports = {
|
|
65
117
|
parseMarketplace,
|
|
66
118
|
listAllSkills,
|
|
67
|
-
|
|
119
|
+
listAllSkillsMerged,
|
|
120
|
+
findSkill,
|
|
121
|
+
findSkillMerged
|
|
68
122
|
};
|