@trendai-crem/claude-skills 0.10.0 → 0.10.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/cli.js +100 -6
- package/marketplace.json +1 -2
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -13,6 +13,8 @@ const EXTERNAL_SOURCES = [
|
|
|
13
13
|
{ repo: 'obra/superpowers', flags: ['--all'], label: 'superpowers' },
|
|
14
14
|
];
|
|
15
15
|
|
|
16
|
+
const MANIFEST_PATH = join(homedir(), '.claude', 'claude-skills-manifest.json');
|
|
17
|
+
|
|
16
18
|
const run = (args, label) => {
|
|
17
19
|
try {
|
|
18
20
|
execFileSync('npx', args, { stdio: 'inherit' });
|
|
@@ -25,19 +27,36 @@ const run = (args, label) => {
|
|
|
25
27
|
|
|
26
28
|
console.log('Installing team skills...\n');
|
|
27
29
|
|
|
28
|
-
// 1. External skills — failures are non-fatal
|
|
29
|
-
const externalResults = EXTERNAL_SOURCES.map(({ repo, flags, label }) =>
|
|
30
|
-
({ label, ok: run(['skills', 'add', repo, ...flags, '-g', '-y'], label) })
|
|
31
|
-
);
|
|
32
|
-
|
|
33
30
|
// 2. Team skills — required, overrides same-named externals
|
|
34
31
|
const teamSkills = readdirSync(join(__dir, 'skills'), { withFileTypes: true })
|
|
35
32
|
.filter(e => e.isDirectory())
|
|
36
33
|
.map(e => e.name)
|
|
37
34
|
.sort();
|
|
38
35
|
|
|
36
|
+
// Compute desired skills for reconcile (query external sources before installing)
|
|
37
|
+
const externalSkillNames = EXTERNAL_SOURCES.flatMap(({ repo, flags }) =>
|
|
38
|
+
listExternalSkillNames(repo, flags) ?? []
|
|
39
|
+
);
|
|
40
|
+
const desiredSkills = new Set([...teamSkills, ...externalSkillNames]);
|
|
41
|
+
|
|
42
|
+
// Reconcile stale skills via manifest (before installing, so removals show first)
|
|
43
|
+
const skillManifest = readJsonObject(MANIFEST_PATH, {}, 'claude-skills-manifest.json') ?? {};
|
|
44
|
+
const previousSkills = new Set(Array.isArray(skillManifest.skills) ? skillManifest.skills : []);
|
|
45
|
+
const staleSkillResults = reconcileSkills(desiredSkills, previousSkills);
|
|
46
|
+
|
|
47
|
+
// 1. External skills — failures are non-fatal
|
|
48
|
+
const externalResults = EXTERNAL_SOURCES.map(({ repo, flags, label }) =>
|
|
49
|
+
({ label, ok: run(['skills', 'add', repo, ...flags, '-g', '-y'], label) })
|
|
50
|
+
);
|
|
51
|
+
|
|
39
52
|
const teamOk = run(['skills', 'add', __dir, '--all', '-g', '-y'], 'team skills');
|
|
40
53
|
|
|
54
|
+
// Write skills portion of manifest (merge to preserve plugins field written later)
|
|
55
|
+
const tmpSkillManifest = join(tmpdir(), `claude-skills-manifest-${process.pid}-skills.json`);
|
|
56
|
+
const mergedSkillManifest = { ...skillManifest, skills: [...desiredSkills] };
|
|
57
|
+
writeFileSync(tmpSkillManifest, JSON.stringify(mergedSkillManifest, null, 2) + '\n');
|
|
58
|
+
renameSync(tmpSkillManifest, MANIFEST_PATH);
|
|
59
|
+
|
|
41
60
|
// 3. Marketplace plugins — failures are non-fatal
|
|
42
61
|
let marketplaceResults = [];
|
|
43
62
|
try {
|
|
@@ -48,6 +67,7 @@ try {
|
|
|
48
67
|
|
|
49
68
|
// Summary
|
|
50
69
|
console.log('\nResults:');
|
|
70
|
+
staleSkillResults.forEach(({ skill, action, ok }) => console.log(` ${ok ? '✓' : '✗'} ${skill} (${action})`));
|
|
51
71
|
externalResults.forEach(({ label, ok }) => console.log(` ${ok ? '✓' : '✗'} ${label}`));
|
|
52
72
|
if (teamOk) {
|
|
53
73
|
teamSkills.forEach(name => console.log(` ✓ ${name}`));
|
|
@@ -90,6 +110,39 @@ function readJsonObject(filePath, fallback, label) {
|
|
|
90
110
|
return fallback;
|
|
91
111
|
}
|
|
92
112
|
|
|
113
|
+
// Lists skill names available in an external repo without installing (uses --list flag).
|
|
114
|
+
// Returns null if the query fails — caller should skip reconcile for that source.
|
|
115
|
+
function listExternalSkillNames(repo, flags) {
|
|
116
|
+
try {
|
|
117
|
+
const output = execFileSync('npx', ['skills', 'add', repo, ...flags, '-l'], {
|
|
118
|
+
encoding: 'utf8',
|
|
119
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
|
+
});
|
|
121
|
+
return output.split('\n')
|
|
122
|
+
.filter(l => /^│ [a-z]/.test(l))
|
|
123
|
+
.map(l => l.replace(/^│\s+/, '').trim());
|
|
124
|
+
} catch {
|
|
125
|
+
return null; // can't determine list — reconcile skipped for this source
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Removes skills that claude-skills previously managed but are no longer in any source.
|
|
130
|
+
// Only touches skills in our manifest — leaves user-created skills alone.
|
|
131
|
+
function reconcileSkills(desiredSkills, previousSkills) {
|
|
132
|
+
const staleSkills = [...previousSkills].filter(s => !desiredSkills.has(s));
|
|
133
|
+
if (staleSkills.length === 0) return [];
|
|
134
|
+
|
|
135
|
+
console.log('\nRemoving stale skills (removed from config)...\n');
|
|
136
|
+
try {
|
|
137
|
+
execFileSync('npx', ['skills', 'remove', ...staleSkills, '-g', '-y'], { stdio: 'inherit' });
|
|
138
|
+
return staleSkills.map(skill => ({ skill, action: 'uninstall', ok: true }));
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const exitInfo = error.status != null ? `exit ${error.status}` : error.code ?? error.message;
|
|
141
|
+
console.error(`\nFailed to remove stale skills (${exitInfo})`);
|
|
142
|
+
return staleSkills.map(skill => ({ skill, action: 'uninstall', ok: false }));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
93
146
|
// Extracts the registered source for a marketplace from known_marketplaces.json.
|
|
94
147
|
// Handles: plain string, object with string .source, and GitHub registry format
|
|
95
148
|
// { source: { source: "github", repo: "org/repo" } }.
|
|
@@ -233,7 +286,48 @@ function installMarketplacePlugins() {
|
|
|
233
286
|
? rawInstalled
|
|
234
287
|
: {};
|
|
235
288
|
|
|
236
|
-
|
|
289
|
+
const desiredKeys = new Set(
|
|
290
|
+
marketplaces.flatMap(e => (e?.plugins ?? []).map(p => `${p}@${e.name}`))
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// Reconcile: uninstall plugins WE previously managed that are no longer in marketplace.json.
|
|
294
|
+
// Uses a manifest so we only remove what claude-skills installed — not user-installed plugins.
|
|
295
|
+
const manifest = readJsonObject(MANIFEST_PATH, {}, 'claude-skills-manifest.json');
|
|
296
|
+
const previousKeys = new Set(Array.isArray(manifest.plugins) ? manifest.plugins : []);
|
|
297
|
+
const uninstallResults = reconcileMarketplacePlugins(desiredKeys, previousKeys, installed);
|
|
298
|
+
|
|
299
|
+
// Update manifest — merge to preserve other fields (e.g. skills written by reconcileSkills)
|
|
300
|
+
const tmpManifest = join(tmpdir(), `claude-skills-manifest-${process.pid}.json`);
|
|
301
|
+
const updatedManifest = { ...(readJsonObject(MANIFEST_PATH, {}) ?? {}), plugins: [...desiredKeys] };
|
|
302
|
+
writeFileSync(tmpManifest, JSON.stringify(updatedManifest, null, 2) + '\n');
|
|
303
|
+
renameSync(tmpManifest, MANIFEST_PATH);
|
|
304
|
+
|
|
305
|
+
const installResults = marketplaces.flatMap(entry => installFromMarketplace(entry, known, installed));
|
|
306
|
+
return [...uninstallResults, ...installResults];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Uninstalls plugins that claude-skills previously managed but are no longer in marketplace.json.
|
|
310
|
+
// Only touches plugins in our manifest — leaves user-installed plugins alone.
|
|
311
|
+
function reconcileMarketplacePlugins(desiredKeys, previousKeys, installed) {
|
|
312
|
+
const staleKeys = [...previousKeys].filter(key =>
|
|
313
|
+
!desiredKeys.has(key) && Object.hasOwn(installed, key)
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (staleKeys.length === 0) return [];
|
|
317
|
+
|
|
318
|
+
console.log('\nRemoving stale marketplace plugins (removed from marketplace.json)...\n');
|
|
319
|
+
return staleKeys.map(key => {
|
|
320
|
+
const atIdx = key.lastIndexOf('@');
|
|
321
|
+
const plugin = atIdx !== -1 ? key.slice(0, atIdx) : key;
|
|
322
|
+
try {
|
|
323
|
+
execFileSync('claude', ['plugin', 'uninstall', key], { stdio: 'inherit' });
|
|
324
|
+
return { plugin, action: 'uninstall', ok: true };
|
|
325
|
+
} catch (error) {
|
|
326
|
+
const exitInfo = error.status != null ? `exit ${error.status}` : error.code ?? error.message;
|
|
327
|
+
console.error(`\nFailed to uninstall ${key} (${exitInfo})`);
|
|
328
|
+
return { plugin, action: 'uninstall', ok: false };
|
|
329
|
+
}
|
|
330
|
+
});
|
|
237
331
|
}
|
|
238
332
|
|
|
239
333
|
function setupAutoUpdate() {
|
package/marketplace.json
CHANGED