@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @supercorks/skills-installer
2
2
 
3
- Interactive CLI installer for AI agent skills. Selectively install skills for GitHub Copilot, Claude, and other AI assistants using Git sparse-checkout.
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 path(s)** - Select one or more locations where skills should be installed:
20
- - `.github/skills/` (GitHub Copilot default)
21
- - `.agents/skills/` (Agent workspace skills)
22
- - `/etc/codex/skills/` (System-level skills)
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
- 2. **Gitignore option** - Optionally add the installation path to `.gitignore`
30
+ 3. **Gitignore option** - Optionally add the installation path to `.gitignore`
27
31
 
28
- 3. **Select skills** - Interactive checkbox to pick which skills to install:
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
- 4. **Sparse clone** - Only downloads the selected skills using Git sparse-checkout, keeping the download minimal while preserving full git functionality.
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/', '.agents/skills/', '/etc/codex/skills/', '.claude/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 = resolve(process.cwd(), path);
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 = resolve(process.cwd(), path);
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 = resolve(process.cwd(), installPath);
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 = resolve(process.cwd(), '.gitignore');
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(skills, installedSkills, skillsNeedingUpdate);
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 = resolve(process.cwd(), installPath);
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 = resolve(process.cwd(), '.gitignore');
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(subagents, installedAgents, subagentsNeedingUpdate);
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
- AGENTS: '.agents/skills/',
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.AGENTS,
85
- SKILL_PATH_CHOICES.ETC,
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: path, value: path });
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 = [AGENT_PATH_CHOICES.GITHUB, AGENT_PATH_CHOICES.CLAUDE];
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: path, value: path });
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.description || 'No description available';
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
- expanded.add(items[cursor].id);
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
- // Check each directory for SKILL.md to confirm it's a skill
39
- const skillChecks = await Promise.all(
40
- potentialSkillDirs.map(async (dir) => {
41
- const metadata = await fetchSkillMetadata(dir.name);
42
- // Only include if we found valid metadata (has a SKILL.md)
43
- if (metadata.name || metadata.description) {
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
- return { name: '', description: '' };
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
- return { name: '', description: '' };
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
- // Fetch metadata for each agent file
36
- const agentChecks = await Promise.all(
37
- agentFiles.map(async (file) => {
38
- const metadata = await fetchSubagentMetadata(file.name);
39
- return {
40
- filename: file.name,
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
- return { name: '', description: '' };
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
- return { name: '', description: '' };
77
+ throw error;
76
78
  }
77
79
  }
78
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercorks/skills-installer",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Interactive CLI installer for AI agent skills and subagents",
5
5
  "type": "module",
6
6
  "bin": {