@supercorks/skills-installer 1.7.0 → 1.8.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 +16 -5
- package/bin/install.js +14 -4
- package/lib/prompts.js +43 -8
- package/lib/skills.js +17 -20
- package/lib/subagents.js +18 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @supercorks/skills-installer
|
|
2
2
|
|
|
3
|
-
Interactive CLI installer for AI agent skills. Selectively install
|
|
3
|
+
Interactive CLI installer for AI agent skills and subagents. Selectively install resources for GitHub Copilot, Codex, Claude, and other AI assistants using Git sparse-checkout.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -16,21 +16,32 @@ npx @supercorks/skills-installer install
|
|
|
16
16
|
|
|
17
17
|
## What it does
|
|
18
18
|
|
|
19
|
-
1. **Choose installation
|
|
19
|
+
1. **Choose installation type** - Install skills, subagents, or both.
|
|
20
|
+
|
|
21
|
+
2. **Choose installation path(s)** - Select one or more locations where resources should be installed:
|
|
20
22
|
- `.github/skills/` (Copilot)
|
|
21
23
|
- `~/.codex/skills/` (Codex)
|
|
22
24
|
- `.claude/skills/` (Claude)
|
|
25
|
+
- `.github/agents/` (Copilot)
|
|
26
|
+
- `.agents/agents/` (Codex)
|
|
27
|
+
- `.claude/agents/` (Claude)
|
|
23
28
|
- Custom path of your choice
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
3. **Gitignore option** - Optionally add the installation path to `.gitignore`
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
4. **Select skills/subagents** - Interactive checkbox to pick what to install:
|
|
28
33
|
- Use `↑`/`↓` to navigate
|
|
29
34
|
- Use `SPACE` to toggle selection
|
|
35
|
+
- Use `→` to expand and lazy-load descriptions
|
|
30
36
|
- Use `A` to toggle all
|
|
31
37
|
- Press `ENTER` to confirm
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
5. **Sparse clone** - Only downloads selected skills/subagents using Git sparse-checkout, keeping the download minimal while preserving full git functionality.
|
|
40
|
+
|
|
41
|
+
## Installed repositories
|
|
42
|
+
|
|
43
|
+
- Skills repo: [https://github.com/supercorks/agent-skills](https://github.com/supercorks/agent-skills)
|
|
44
|
+
- Subagents repo: [https://github.com/supercorks/subagents](https://github.com/supercorks/subagents)
|
|
34
45
|
|
|
35
46
|
## Features
|
|
36
47
|
|
package/bin/install.js
CHANGED
|
@@ -22,8 +22,8 @@ import {
|
|
|
22
22
|
showSubagentSuccess,
|
|
23
23
|
showError
|
|
24
24
|
} from '../lib/prompts.js';
|
|
25
|
-
import { fetchAvailableSkills } from '../lib/skills.js';
|
|
26
|
-
import { fetchAvailableSubagents } from '../lib/subagents.js';
|
|
25
|
+
import { fetchAvailableSkills, fetchSkillMetadata } from '../lib/skills.js';
|
|
26
|
+
import { fetchAvailableSubagents, fetchSubagentMetadata } from '../lib/subagents.js';
|
|
27
27
|
import {
|
|
28
28
|
sparseCloneSkills,
|
|
29
29
|
isGitAvailable,
|
|
@@ -279,7 +279,12 @@ async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
// Select skills (pre-select installed skills in manage mode)
|
|
282
|
-
const selectedSkills = await promptSkillSelection(
|
|
282
|
+
const selectedSkills = await promptSkillSelection(
|
|
283
|
+
skills,
|
|
284
|
+
installedSkills,
|
|
285
|
+
skillsNeedingUpdate,
|
|
286
|
+
(skillFolder) => fetchSkillMetadata(skillFolder)
|
|
287
|
+
);
|
|
283
288
|
|
|
284
289
|
// Perform installation or update
|
|
285
290
|
console.log('');
|
|
@@ -426,7 +431,12 @@ async function runSubagentsInstallForTarget(subagents, existingInstalls, target)
|
|
|
426
431
|
}
|
|
427
432
|
|
|
428
433
|
// Select subagents (pre-select installed ones in manage mode)
|
|
429
|
-
const selectedAgents = await promptSubagentSelection(
|
|
434
|
+
const selectedAgents = await promptSubagentSelection(
|
|
435
|
+
subagents,
|
|
436
|
+
installedAgents,
|
|
437
|
+
subagentsNeedingUpdate,
|
|
438
|
+
(filename) => fetchSubagentMetadata(filename)
|
|
439
|
+
);
|
|
430
440
|
|
|
431
441
|
// Perform installation or update
|
|
432
442
|
console.log('');
|
package/lib/prompts.js
CHANGED
|
@@ -247,14 +247,16 @@ export async function promptGitignore(installPath) {
|
|
|
247
247
|
* @param {Array<{name: string, description: string, folder: string}>} skills - Available skills
|
|
248
248
|
* @param {string[]} installedSkills - Already installed skill folder names (will be pre-selected)
|
|
249
249
|
* @param {Set<string>} skillsNeedingUpdate - Skill folder names that have updates available
|
|
250
|
+
* @param {(skillFolder: string) => Promise<{name?: string, description?: string}>} metadataLoader - Lazy metadata loader
|
|
250
251
|
* @returns {Promise<string[]>} Selected skill folder names
|
|
251
252
|
*/
|
|
252
|
-
export async function promptSkillSelection(skills, installedSkills = [], skillsNeedingUpdate = new Set()) {
|
|
253
|
+
export async function promptSkillSelection(skills, installedSkills = [], skillsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
253
254
|
return promptItemSelection(
|
|
254
255
|
skills.map(s => ({ id: s.folder, name: s.name, description: s.description })),
|
|
255
256
|
installedSkills,
|
|
256
257
|
'📦 Available Skills',
|
|
257
|
-
skillsNeedingUpdate
|
|
258
|
+
skillsNeedingUpdate,
|
|
259
|
+
metadataLoader
|
|
258
260
|
);
|
|
259
261
|
}
|
|
260
262
|
|
|
@@ -263,14 +265,16 @@ export async function promptSkillSelection(skills, installedSkills = [], skillsN
|
|
|
263
265
|
* @param {Array<{name: string, description: string, filename: string}>} subagents - Available subagents
|
|
264
266
|
* @param {string[]} installedSubagents - Already installed subagent filenames (will be pre-selected)
|
|
265
267
|
* @param {Set<string>} subagentsNeedingUpdate - Subagent filenames that have updates available
|
|
268
|
+
* @param {(agentFilename: string) => Promise<{name?: string, description?: string}>} metadataLoader - Lazy metadata loader
|
|
266
269
|
* @returns {Promise<string[]>} Selected subagent filenames
|
|
267
270
|
*/
|
|
268
|
-
export async function promptSubagentSelection(subagents, installedSubagents = [], subagentsNeedingUpdate = new Set()) {
|
|
271
|
+
export async function promptSubagentSelection(subagents, installedSubagents = [], subagentsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
269
272
|
return promptItemSelection(
|
|
270
273
|
subagents.map(s => ({ id: s.filename, name: s.name, description: s.description })),
|
|
271
274
|
installedSubagents,
|
|
272
275
|
'🤖 Available Subagents',
|
|
273
|
-
subagentsNeedingUpdate
|
|
276
|
+
subagentsNeedingUpdate,
|
|
277
|
+
metadataLoader
|
|
274
278
|
);
|
|
275
279
|
}
|
|
276
280
|
|
|
@@ -280,9 +284,10 @@ export async function promptSubagentSelection(subagents, installedSubagents = []
|
|
|
280
284
|
* @param {string[]} installedItems - Already installed item IDs (will be pre-selected)
|
|
281
285
|
* @param {string} title - Title to display
|
|
282
286
|
* @param {Set<string>} itemsNeedingUpdate - Item IDs that have updates available
|
|
287
|
+
* @param {(itemId: string) => Promise<{name?: string, description?: string}>} metadataLoader - Lazy metadata loader
|
|
283
288
|
* @returns {Promise<string[]>} Selected item IDs
|
|
284
289
|
*/
|
|
285
|
-
function promptItemSelection(items, installedItems = [], title = '📦 Available Items', itemsNeedingUpdate = new Set()) {
|
|
290
|
+
function promptItemSelection(items, installedItems = [], title = '📦 Available Items', itemsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
286
291
|
return new Promise((resolve, reject) => {
|
|
287
292
|
const rl = readline.createInterface({
|
|
288
293
|
input: process.stdin,
|
|
@@ -301,6 +306,8 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
301
306
|
? new Set(installedItems)
|
|
302
307
|
: new Set(items.map(item => item.id));
|
|
303
308
|
const expanded = new Set();
|
|
309
|
+
const metadataCache = new Set();
|
|
310
|
+
const metadataLoading = new Set();
|
|
304
311
|
|
|
305
312
|
const render = () => {
|
|
306
313
|
// Clear screen and move to top
|
|
@@ -308,7 +315,7 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
308
315
|
|
|
309
316
|
console.log(`\n${title}`);
|
|
310
317
|
console.log('─'.repeat(60));
|
|
311
|
-
console.log('↑↓ navigate SPACE toggle → expand ← collapse A all ENTER confirm\n');
|
|
318
|
+
console.log('↑↓ navigate SPACE toggle → expand/load ← collapse A all ENTER confirm\n');
|
|
312
319
|
console.log('Select items to install:\n');
|
|
313
320
|
|
|
314
321
|
items.forEach((item, i) => {
|
|
@@ -331,7 +338,9 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
331
338
|
if (isExpanded) {
|
|
332
339
|
console.log(`${highlight}${pointer} ${checkbox} ${item.name}${reset}${updateFlag}`);
|
|
333
340
|
// Show full description indented
|
|
334
|
-
const fullDesc = item.
|
|
341
|
+
const fullDesc = metadataLoading.has(item.id)
|
|
342
|
+
? 'Loading description...'
|
|
343
|
+
: (item.description || 'No description available');
|
|
335
344
|
const lines = fullDesc.match(/.{1,55}/g) || [fullDesc];
|
|
336
345
|
lines.forEach(line => {
|
|
337
346
|
console.log(` ${highlight}${line}${reset}`);
|
|
@@ -374,7 +383,33 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
374
383
|
render();
|
|
375
384
|
break;
|
|
376
385
|
case 'right':
|
|
377
|
-
|
|
386
|
+
const currentItem = items[cursor];
|
|
387
|
+
expanded.add(currentItem.id);
|
|
388
|
+
if (metadataLoader && !metadataCache.has(currentItem.id) && !metadataLoading.has(currentItem.id)) {
|
|
389
|
+
metadataLoading.add(currentItem.id);
|
|
390
|
+
render();
|
|
391
|
+
metadataLoader(currentItem.id)
|
|
392
|
+
.then((metadata) => {
|
|
393
|
+
if (metadata?.name) {
|
|
394
|
+
currentItem.name = metadata.name;
|
|
395
|
+
}
|
|
396
|
+
if (metadata?.description) {
|
|
397
|
+
currentItem.description = metadata.description;
|
|
398
|
+
} else if (!currentItem.description || currentItem.description === 'Press right arrow to load description') {
|
|
399
|
+
currentItem.description = 'Description unavailable';
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
.catch(() => {
|
|
403
|
+
if (!currentItem.description || currentItem.description === 'Press right arrow to load description') {
|
|
404
|
+
currentItem.description = 'Description unavailable (metadata fetch failed)';
|
|
405
|
+
}
|
|
406
|
+
})
|
|
407
|
+
.finally(() => {
|
|
408
|
+
metadataLoading.delete(currentItem.id);
|
|
409
|
+
metadataCache.add(currentItem.id);
|
|
410
|
+
render();
|
|
411
|
+
});
|
|
412
|
+
}
|
|
378
413
|
render();
|
|
379
414
|
break;
|
|
380
415
|
case 'left':
|
package/lib/skills.js
CHANGED
|
@@ -9,6 +9,14 @@ const GITHUB_API = 'https://api.github.com';
|
|
|
9
9
|
// Folders to exclude from skill detection (not actual skills)
|
|
10
10
|
const EXCLUDED_FOLDERS = ['.github', '.claude', 'node_modules'];
|
|
11
11
|
|
|
12
|
+
function humanizeSkillName(folder) {
|
|
13
|
+
return folder
|
|
14
|
+
.split(/[-_]/g)
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
17
|
+
.join(' ');
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
21
|
* Fetch the list of skill directories from the repository
|
|
14
22
|
* Skills are at the repo root level, each folder with a SKILL.md is a skill
|
|
@@ -35,23 +43,12 @@ export async function fetchAvailableSkills() {
|
|
|
35
43
|
item => item.type === 'dir' && !EXCLUDED_FOLDERS.includes(item.name) && !item.name.startsWith('.')
|
|
36
44
|
);
|
|
37
45
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
folder: dir.name,
|
|
46
|
-
name: metadata.name || dir.name,
|
|
47
|
-
description: metadata.description || 'No description available'
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
})
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return skillChecks.filter(Boolean);
|
|
46
|
+
// Keep listing lightweight: metadata is loaded lazily on expand in the UI
|
|
47
|
+
return potentialSkillDirs.map(dir => ({
|
|
48
|
+
folder: dir.name,
|
|
49
|
+
name: humanizeSkillName(dir.name),
|
|
50
|
+
description: 'Press right arrow to load description'
|
|
51
|
+
}));
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
/**
|
|
@@ -59,7 +56,7 @@ export async function fetchAvailableSkills() {
|
|
|
59
56
|
* @param {string} skillFolder - The skill folder name
|
|
60
57
|
* @returns {Promise<{name: string, description: string}>}
|
|
61
58
|
*/
|
|
62
|
-
async function fetchSkillMetadata(skillFolder) {
|
|
59
|
+
export async function fetchSkillMetadata(skillFolder) {
|
|
63
60
|
const skillMdUrl = `${GITHUB_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${skillFolder}/SKILL.md`;
|
|
64
61
|
|
|
65
62
|
try {
|
|
@@ -71,7 +68,7 @@ async function fetchSkillMetadata(skillFolder) {
|
|
|
71
68
|
});
|
|
72
69
|
|
|
73
70
|
if (!response.ok) {
|
|
74
|
-
|
|
71
|
+
throw new Error(`Failed to fetch skill metadata: ${response.status} ${response.statusText}`);
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
const data = await response.json();
|
|
@@ -79,7 +76,7 @@ async function fetchSkillMetadata(skillFolder) {
|
|
|
79
76
|
|
|
80
77
|
return parseSkillFrontmatter(content);
|
|
81
78
|
} catch (error) {
|
|
82
|
-
|
|
79
|
+
throw error;
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
82
|
|
package/lib/subagents.js
CHANGED
|
@@ -6,6 +6,15 @@ const SUBAGENTS_REPO_OWNER = 'supercorks';
|
|
|
6
6
|
const SUBAGENTS_REPO_NAME = 'subagents';
|
|
7
7
|
const GITHUB_API = 'https://api.github.com';
|
|
8
8
|
|
|
9
|
+
function humanizeAgentName(filename) {
|
|
10
|
+
const base = filename.replace('.agent.md', '');
|
|
11
|
+
return base
|
|
12
|
+
.split(/[-_]/g)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
15
|
+
.join(' ');
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
/**
|
|
10
19
|
* Fetch the list of subagent files from the repository
|
|
11
20
|
* Subagents are .agent.md files at the repo root
|
|
@@ -32,19 +41,12 @@ export async function fetchAvailableSubagents() {
|
|
|
32
41
|
item => item.type === 'file' && item.name.endsWith('.agent.md')
|
|
33
42
|
);
|
|
34
43
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
name: metadata.name || file.name.replace('.agent.md', ''),
|
|
42
|
-
description: metadata.description || 'No description available'
|
|
43
|
-
};
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return agentChecks;
|
|
44
|
+
// Keep listing lightweight: metadata is loaded lazily on expand in the UI
|
|
45
|
+
return agentFiles.map(file => ({
|
|
46
|
+
filename: file.name,
|
|
47
|
+
name: humanizeAgentName(file.name),
|
|
48
|
+
description: 'Press right arrow to load description'
|
|
49
|
+
}));
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -52,7 +54,7 @@ export async function fetchAvailableSubagents() {
|
|
|
52
54
|
* @param {string} filename - The agent filename
|
|
53
55
|
* @returns {Promise<{name: string, description: string}>}
|
|
54
56
|
*/
|
|
55
|
-
async function fetchSubagentMetadata(filename) {
|
|
57
|
+
export async function fetchSubagentMetadata(filename) {
|
|
56
58
|
const fileUrl = `${GITHUB_API}/repos/${SUBAGENTS_REPO_OWNER}/${SUBAGENTS_REPO_NAME}/contents/${filename}`;
|
|
57
59
|
|
|
58
60
|
try {
|
|
@@ -64,7 +66,7 @@ async function fetchSubagentMetadata(filename) {
|
|
|
64
66
|
});
|
|
65
67
|
|
|
66
68
|
if (!response.ok) {
|
|
67
|
-
|
|
69
|
+
throw new Error(`Failed to fetch subagent metadata: ${response.status} ${response.statusText}`);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const data = await response.json();
|
|
@@ -72,7 +74,7 @@ async function fetchSubagentMetadata(filename) {
|
|
|
72
74
|
|
|
73
75
|
return parseSubagentFrontmatter(content);
|
|
74
76
|
} catch (error) {
|
|
75
|
-
|
|
77
|
+
throw error;
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|