* Parse SKILL.md frontmatter and list bundled assets in a skill folder * @param {string} skillPath - Path to skill folder * @returns {object|null} Skill metadata with name, description, and assets array
(skillPath)
| 125 | * @returns {object|null} Skill metadata with name, description, and assets array |
| 126 | */ |
| 127 | function parseSkillMetadata(skillPath) { |
| 128 | return safeFileOperation( |
| 129 | () => { |
| 130 | const skillFile = path.join(skillPath, "SKILL.md"); |
| 131 | if (!fs.existsSync(skillFile)) { |
| 132 | return null; |
| 133 | } |
| 134 | |
| 135 | const frontmatter = parseFrontmatter(skillFile); |
| 136 | |
| 137 | // Validate required fields |
| 138 | if (!frontmatter?.name || !frontmatter?.description) { |
| 139 | console.warn( |
| 140 | `Invalid skill at ${skillPath}: missing name or description in frontmatter` |
| 141 | ); |
| 142 | return null; |
| 143 | } |
| 144 | |
| 145 | // List bundled assets (all files except SKILL.md), recursing through subdirectories |
| 146 | const getAllFiles = (dirPath, arrayOfFiles = []) => { |
| 147 | const files = fs.readdirSync(dirPath); |
| 148 | const assetPaths = ['references', 'assets', 'scripts']; |
| 149 | |
| 150 | files.forEach((file) => { |
| 151 | const filePath = path.join(dirPath, file); |
| 152 | if (fs.statSync(filePath).isDirectory() && assetPaths.includes(file)) { |
| 153 | arrayOfFiles = getAllFiles(filePath, arrayOfFiles); |
| 154 | } else { |
| 155 | const relativePath = path.relative(skillPath, filePath); |
| 156 | if (relativePath !== "SKILL.md") { |
| 157 | // Normalize path separators to forward slashes for cross-platform consistency |
| 158 | arrayOfFiles.push(relativePath.replace(/\\/g, "/")); |
| 159 | } |
| 160 | } |
| 161 | }); |
| 162 | |
| 163 | return arrayOfFiles; |
| 164 | }; |
| 165 | |
| 166 | const assets = getAllFiles(skillPath).sort(); |
| 167 | |
| 168 | return { |
| 169 | name: frontmatter.name, |
| 170 | description: frontmatter.description, |
| 171 | assets, |
| 172 | path: skillPath, |
| 173 | }; |
| 174 | }, |
| 175 | skillPath, |
| 176 | null |
| 177 | ); |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Parse hook metadata from a hook folder (similar to skills) |
no test coverage detected