@supercorks/skills-installer 1.6.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 +18 -8
- package/bin/install.js +29 -12
- package/lib/git.js +9 -1
- package/lib/prompts.js +57 -19
- 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,22 +16,32 @@ npx @supercorks/skills-installer install
|
|
|
16
16
|
|
|
17
17
|
## What it does
|
|
18
18
|
|
|
19
|
-
1. **Choose installation
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
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:
|
|
22
|
+
- `.github/skills/` (Copilot)
|
|
23
|
+
- `~/.codex/skills/` (Codex)
|
|
23
24
|
- `.claude/skills/` (Claude)
|
|
25
|
+
- `.github/agents/` (Copilot)
|
|
26
|
+
- `.agents/agents/` (Codex)
|
|
27
|
+
- `.claude/agents/` (Claude)
|
|
24
28
|
- Custom path of your choice
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
3. **Gitignore option** - Optionally add the installation path to `.gitignore`
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
4. **Select skills/subagents** - Interactive checkbox to pick what to install:
|
|
29
33
|
- Use `↑`/`↓` to navigate
|
|
30
34
|
- Use `SPACE` to toggle selection
|
|
35
|
+
- Use `→` to expand and lazy-load descriptions
|
|
31
36
|
- Use `A` to toggle all
|
|
32
37
|
- Press `ENTER` to confirm
|
|
33
38
|
|
|
34
|
-
|
|
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)
|
|
35
45
|
|
|
36
46
|
## Features
|
|
37
47
|
|
package/bin/install.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, appendFileSync, readFileSync, writeFileSync } from 'fs';
|
|
11
11
|
import { resolve, join } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
12
13
|
import {
|
|
13
14
|
promptInstallType,
|
|
14
15
|
promptInstallPath,
|
|
@@ -21,8 +22,8 @@ import {
|
|
|
21
22
|
showSubagentSuccess,
|
|
22
23
|
showError
|
|
23
24
|
} from '../lib/prompts.js';
|
|
24
|
-
import { fetchAvailableSkills } from '../lib/skills.js';
|
|
25
|
-
import { fetchAvailableSubagents } from '../lib/subagents.js';
|
|
25
|
+
import { fetchAvailableSkills, fetchSkillMetadata } from '../lib/skills.js';
|
|
26
|
+
import { fetchAvailableSubagents, fetchSubagentMetadata } from '../lib/subagents.js';
|
|
26
27
|
import {
|
|
27
28
|
sparseCloneSkills,
|
|
28
29
|
isGitAvailable,
|
|
@@ -40,8 +41,14 @@ const require = createRequire(import.meta.url);
|
|
|
40
41
|
const { version: VERSION } = require('../package.json');
|
|
41
42
|
|
|
42
43
|
// Common installation paths to check for existing installations
|
|
43
|
-
const SKILL_PATHS = ['.github/skills/', '
|
|
44
|
-
const AGENT_PATHS = ['.github/agents/', '.claude/agents/'];
|
|
44
|
+
const SKILL_PATHS = ['.github/skills/', '~/.codex/skills/', '.claude/skills/'];
|
|
45
|
+
const AGENT_PATHS = ['.github/agents/', '.agents/agents/', '.claude/agents/'];
|
|
46
|
+
|
|
47
|
+
function resolveInstallPath(path) {
|
|
48
|
+
if (path === '~') return homedir();
|
|
49
|
+
if (path.startsWith('~/')) return resolve(homedir(), path.slice(2));
|
|
50
|
+
return resolve(process.cwd(), path);
|
|
51
|
+
}
|
|
45
52
|
|
|
46
53
|
/**
|
|
47
54
|
* Detect existing skill installations in common paths
|
|
@@ -51,7 +58,7 @@ async function detectExistingSkillInstallations() {
|
|
|
51
58
|
const installations = [];
|
|
52
59
|
|
|
53
60
|
for (const path of SKILL_PATHS) {
|
|
54
|
-
const absolutePath =
|
|
61
|
+
const absolutePath = resolveInstallPath(path);
|
|
55
62
|
const gitDir = join(absolutePath, '.git');
|
|
56
63
|
|
|
57
64
|
if (existsSync(gitDir)) {
|
|
@@ -79,7 +86,7 @@ async function detectExistingAgentInstallations() {
|
|
|
79
86
|
const installations = [];
|
|
80
87
|
|
|
81
88
|
for (const path of AGENT_PATHS) {
|
|
82
|
-
const absolutePath =
|
|
89
|
+
const absolutePath = resolveInstallPath(path);
|
|
83
90
|
const gitDir = join(absolutePath, '.git');
|
|
84
91
|
|
|
85
92
|
if (existsSync(gitDir)) {
|
|
@@ -227,7 +234,7 @@ async function runSkillsInstall() {
|
|
|
227
234
|
*/
|
|
228
235
|
async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
229
236
|
const { path: installPath, isExisting } = target;
|
|
230
|
-
const absoluteInstallPath =
|
|
237
|
+
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
231
238
|
|
|
232
239
|
// Get currently installed skills if managing existing installation
|
|
233
240
|
let installedSkills = [];
|
|
@@ -266,13 +273,18 @@ async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
|
266
273
|
|
|
267
274
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
268
275
|
let shouldGitignore = false;
|
|
269
|
-
const gitignorePath =
|
|
276
|
+
const gitignorePath = resolveInstallPath('.gitignore');
|
|
270
277
|
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
271
278
|
shouldGitignore = await promptGitignore(installPath);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
// Select skills (pre-select installed skills in manage mode)
|
|
275
|
-
const selectedSkills = await promptSkillSelection(
|
|
282
|
+
const selectedSkills = await promptSkillSelection(
|
|
283
|
+
skills,
|
|
284
|
+
installedSkills,
|
|
285
|
+
skillsNeedingUpdate,
|
|
286
|
+
(skillFolder) => fetchSkillMetadata(skillFolder)
|
|
287
|
+
);
|
|
276
288
|
|
|
277
289
|
// Perform installation or update
|
|
278
290
|
console.log('');
|
|
@@ -374,7 +386,7 @@ async function runSubagentsInstall() {
|
|
|
374
386
|
*/
|
|
375
387
|
async function runSubagentsInstallForTarget(subagents, existingInstalls, target) {
|
|
376
388
|
const { path: installPath, isExisting } = target;
|
|
377
|
-
const absoluteInstallPath =
|
|
389
|
+
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
378
390
|
|
|
379
391
|
// Get currently installed subagents if managing existing installation
|
|
380
392
|
let installedAgents = [];
|
|
@@ -413,13 +425,18 @@ async function runSubagentsInstallForTarget(subagents, existingInstalls, target)
|
|
|
413
425
|
|
|
414
426
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
415
427
|
let shouldGitignore = false;
|
|
416
|
-
const gitignorePath =
|
|
428
|
+
const gitignorePath = resolveInstallPath('.gitignore');
|
|
417
429
|
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
418
430
|
shouldGitignore = await promptGitignore(installPath);
|
|
419
431
|
}
|
|
420
432
|
|
|
421
433
|
// Select subagents (pre-select installed ones in manage mode)
|
|
422
|
-
const selectedAgents = await promptSubagentSelection(
|
|
434
|
+
const selectedAgents = await promptSubagentSelection(
|
|
435
|
+
subagents,
|
|
436
|
+
installedAgents,
|
|
437
|
+
subagentsNeedingUpdate,
|
|
438
|
+
(filename) => fetchSubagentMetadata(filename)
|
|
439
|
+
);
|
|
423
440
|
|
|
424
441
|
// Perform installation or update
|
|
425
442
|
console.log('');
|
package/lib/git.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
|
|
6
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, appendFileSync, readdirSync } from 'fs';
|
|
7
7
|
import { join, resolve } from 'path';
|
|
8
8
|
import { getRepoUrl } from './skills.js';
|
|
9
9
|
import { getSubagentsRepoUrl } from './subagents.js';
|
|
@@ -71,6 +71,10 @@ export async function sparseCloneSkills(targetPath, skillFolders, onProgress = (
|
|
|
71
71
|
if (existsSync(gitDir)) {
|
|
72
72
|
throw new Error(`Directory "${targetPath}" already contains a git repository. Please remove it first or choose a different path.`);
|
|
73
73
|
}
|
|
74
|
+
const entries = readdirSync(absolutePath).filter(name => name !== '.DS_Store');
|
|
75
|
+
if (entries.length > 0) {
|
|
76
|
+
throw new Error(`Directory "${targetPath}" already exists and is not empty. Choose an empty directory or an existing managed installation.`);
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// Create target directory
|
|
@@ -328,6 +332,10 @@ export async function sparseCloneSubagents(targetPath, agentFilenames, onProgres
|
|
|
328
332
|
if (existsSync(gitDir)) {
|
|
329
333
|
throw new Error(`Directory "${targetPath}" already contains a git repository. Please remove it first or choose a different path.`);
|
|
330
334
|
}
|
|
335
|
+
const entries = readdirSync(absolutePath).filter(name => name !== '.DS_Store');
|
|
336
|
+
if (entries.length > 0) {
|
|
337
|
+
throw new Error(`Directory "${targetPath}" already exists and is not empty. Choose an empty directory or an existing managed installation.`);
|
|
338
|
+
}
|
|
331
339
|
}
|
|
332
340
|
|
|
333
341
|
// Create target directory
|
package/lib/prompts.js
CHANGED
|
@@ -7,14 +7,14 @@ import * as readline from 'readline';
|
|
|
7
7
|
|
|
8
8
|
const SKILL_PATH_CHOICES = {
|
|
9
9
|
GITHUB: '.github/skills/',
|
|
10
|
-
|
|
11
|
-
ETC: '/etc/codex/skills/',
|
|
10
|
+
CODEX_HOME: '~/.codex/skills/',
|
|
12
11
|
CLAUDE: '.claude/skills/',
|
|
13
12
|
CUSTOM: '__custom__'
|
|
14
13
|
};
|
|
15
14
|
|
|
16
15
|
const AGENT_PATH_CHOICES = {
|
|
17
16
|
GITHUB: '.github/agents/',
|
|
17
|
+
CODEX: '.agents/agents/',
|
|
18
18
|
CLAUDE: '.claude/agents/',
|
|
19
19
|
CUSTOM: '__custom__'
|
|
20
20
|
};
|
|
@@ -80,15 +80,14 @@ export async function promptInstallPath(existingInstalls = []) {
|
|
|
80
80
|
|
|
81
81
|
// Standard path options
|
|
82
82
|
const standardPaths = [
|
|
83
|
-
SKILL_PATH_CHOICES.GITHUB,
|
|
84
|
-
SKILL_PATH_CHOICES.
|
|
85
|
-
SKILL_PATH_CHOICES.
|
|
86
|
-
SKILL_PATH_CHOICES.CLAUDE
|
|
83
|
+
{ path: SKILL_PATH_CHOICES.GITHUB, label: `${SKILL_PATH_CHOICES.GITHUB} (Copilot)` },
|
|
84
|
+
{ path: SKILL_PATH_CHOICES.CODEX_HOME, label: `${SKILL_PATH_CHOICES.CODEX_HOME} (Codex)` },
|
|
85
|
+
{ path: SKILL_PATH_CHOICES.CLAUDE, label: `${SKILL_PATH_CHOICES.CLAUDE} (Claude)` }
|
|
87
86
|
];
|
|
88
87
|
|
|
89
|
-
standardPaths.forEach(path => {
|
|
88
|
+
standardPaths.forEach(({ path, label }) => {
|
|
90
89
|
if (!existingPaths.includes(path)) {
|
|
91
|
-
choices.push({ name:
|
|
90
|
+
choices.push({ name: label, value: path });
|
|
92
91
|
}
|
|
93
92
|
});
|
|
94
93
|
|
|
@@ -163,11 +162,15 @@ export async function promptAgentInstallPath(existingInstalls = []) {
|
|
|
163
162
|
}
|
|
164
163
|
|
|
165
164
|
// Standard path options
|
|
166
|
-
const standardPaths = [
|
|
165
|
+
const standardPaths = [
|
|
166
|
+
{ path: AGENT_PATH_CHOICES.GITHUB, label: `${AGENT_PATH_CHOICES.GITHUB} (Copilot)` },
|
|
167
|
+
{ path: AGENT_PATH_CHOICES.CODEX, label: `${AGENT_PATH_CHOICES.CODEX} (Codex)` },
|
|
168
|
+
{ path: AGENT_PATH_CHOICES.CLAUDE, label: `${AGENT_PATH_CHOICES.CLAUDE} (Claude)` }
|
|
169
|
+
];
|
|
167
170
|
|
|
168
|
-
standardPaths.forEach(path => {
|
|
171
|
+
standardPaths.forEach(({ path, label }) => {
|
|
169
172
|
if (!existingPaths.includes(path)) {
|
|
170
|
-
choices.push({ name:
|
|
173
|
+
choices.push({ name: label, value: path });
|
|
171
174
|
}
|
|
172
175
|
});
|
|
173
176
|
|
|
@@ -244,14 +247,16 @@ export async function promptGitignore(installPath) {
|
|
|
244
247
|
* @param {Array<{name: string, description: string, folder: string}>} skills - Available skills
|
|
245
248
|
* @param {string[]} installedSkills - Already installed skill folder names (will be pre-selected)
|
|
246
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
|
|
247
251
|
* @returns {Promise<string[]>} Selected skill folder names
|
|
248
252
|
*/
|
|
249
|
-
export async function promptSkillSelection(skills, installedSkills = [], skillsNeedingUpdate = new Set()) {
|
|
253
|
+
export async function promptSkillSelection(skills, installedSkills = [], skillsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
250
254
|
return promptItemSelection(
|
|
251
255
|
skills.map(s => ({ id: s.folder, name: s.name, description: s.description })),
|
|
252
256
|
installedSkills,
|
|
253
257
|
'📦 Available Skills',
|
|
254
|
-
skillsNeedingUpdate
|
|
258
|
+
skillsNeedingUpdate,
|
|
259
|
+
metadataLoader
|
|
255
260
|
);
|
|
256
261
|
}
|
|
257
262
|
|
|
@@ -260,14 +265,16 @@ export async function promptSkillSelection(skills, installedSkills = [], skillsN
|
|
|
260
265
|
* @param {Array<{name: string, description: string, filename: string}>} subagents - Available subagents
|
|
261
266
|
* @param {string[]} installedSubagents - Already installed subagent filenames (will be pre-selected)
|
|
262
267
|
* @param {Set<string>} subagentsNeedingUpdate - Subagent filenames that have updates available
|
|
268
|
+
* @param {(agentFilename: string) => Promise<{name?: string, description?: string}>} metadataLoader - Lazy metadata loader
|
|
263
269
|
* @returns {Promise<string[]>} Selected subagent filenames
|
|
264
270
|
*/
|
|
265
|
-
export async function promptSubagentSelection(subagents, installedSubagents = [], subagentsNeedingUpdate = new Set()) {
|
|
271
|
+
export async function promptSubagentSelection(subagents, installedSubagents = [], subagentsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
266
272
|
return promptItemSelection(
|
|
267
273
|
subagents.map(s => ({ id: s.filename, name: s.name, description: s.description })),
|
|
268
274
|
installedSubagents,
|
|
269
275
|
'🤖 Available Subagents',
|
|
270
|
-
subagentsNeedingUpdate
|
|
276
|
+
subagentsNeedingUpdate,
|
|
277
|
+
metadataLoader
|
|
271
278
|
);
|
|
272
279
|
}
|
|
273
280
|
|
|
@@ -277,9 +284,10 @@ export async function promptSubagentSelection(subagents, installedSubagents = []
|
|
|
277
284
|
* @param {string[]} installedItems - Already installed item IDs (will be pre-selected)
|
|
278
285
|
* @param {string} title - Title to display
|
|
279
286
|
* @param {Set<string>} itemsNeedingUpdate - Item IDs that have updates available
|
|
287
|
+
* @param {(itemId: string) => Promise<{name?: string, description?: string}>} metadataLoader - Lazy metadata loader
|
|
280
288
|
* @returns {Promise<string[]>} Selected item IDs
|
|
281
289
|
*/
|
|
282
|
-
function promptItemSelection(items, installedItems = [], title = '📦 Available Items', itemsNeedingUpdate = new Set()) {
|
|
290
|
+
function promptItemSelection(items, installedItems = [], title = '📦 Available Items', itemsNeedingUpdate = new Set(), metadataLoader = null) {
|
|
283
291
|
return new Promise((resolve, reject) => {
|
|
284
292
|
const rl = readline.createInterface({
|
|
285
293
|
input: process.stdin,
|
|
@@ -298,6 +306,8 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
298
306
|
? new Set(installedItems)
|
|
299
307
|
: new Set(items.map(item => item.id));
|
|
300
308
|
const expanded = new Set();
|
|
309
|
+
const metadataCache = new Set();
|
|
310
|
+
const metadataLoading = new Set();
|
|
301
311
|
|
|
302
312
|
const render = () => {
|
|
303
313
|
// Clear screen and move to top
|
|
@@ -305,7 +315,7 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
305
315
|
|
|
306
316
|
console.log(`\n${title}`);
|
|
307
317
|
console.log('─'.repeat(60));
|
|
308
|
-
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');
|
|
309
319
|
console.log('Select items to install:\n');
|
|
310
320
|
|
|
311
321
|
items.forEach((item, i) => {
|
|
@@ -328,7 +338,9 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
328
338
|
if (isExpanded) {
|
|
329
339
|
console.log(`${highlight}${pointer} ${checkbox} ${item.name}${reset}${updateFlag}`);
|
|
330
340
|
// Show full description indented
|
|
331
|
-
const fullDesc = item.
|
|
341
|
+
const fullDesc = metadataLoading.has(item.id)
|
|
342
|
+
? 'Loading description...'
|
|
343
|
+
: (item.description || 'No description available');
|
|
332
344
|
const lines = fullDesc.match(/.{1,55}/g) || [fullDesc];
|
|
333
345
|
lines.forEach(line => {
|
|
334
346
|
console.log(` ${highlight}${line}${reset}`);
|
|
@@ -371,7 +383,33 @@ function promptItemSelection(items, installedItems = [], title = '📦 Available
|
|
|
371
383
|
render();
|
|
372
384
|
break;
|
|
373
385
|
case 'right':
|
|
374
|
-
|
|
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
|
+
}
|
|
375
413
|
render();
|
|
376
414
|
break;
|
|
377
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
|
|