kungeskill 0.3.0 → 0.4.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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kungeskill",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Manage Claude Code skills via symlinks — install, remove, update skills from a marketplace cache",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -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, findSkill } = require('../core/registry');
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
- const cacheDir = getMarketplaceSourceDir();
22
- const marketplace = parseMarketplace(cacheDir);
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.');
@@ -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, listAllSkills } = require('../core/registry');
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 grouped by plugin
52
- const skills = listAllSkills(marketplace, cacheDir);
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/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
  };
@@ -4,12 +4,12 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  /**
7
- * Parse marketplace.json from the cache directory.
8
- * @param {string} cacheDir - Path to the marketplace cache root
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(cacheDir) {
12
- const manifestPath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
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 parsed marketplace manifest.
21
+ * List all skills from a single source directory.
22
22
  * @param {object} marketplace - Parsed marketplace.json
23
- * @param {string} cacheDir - Path to the marketplace cache root (for resolving source paths)
23
+ * @param {string} sourceDir - Path to the marketplace source root
24
24
  * @returns {Array<{skillName: string, pluginName: string, sourcePath: string}>}
25
25
  */
26
- function listAllSkills(marketplace, cacheDir) {
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(cacheDir, plugin.source, 'skills');
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
- * Find a specific skill by name.
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
- findSkill
119
+ listAllSkillsMerged,
120
+ findSkill,
121
+ findSkillMerged
68
122
  };