(content: string, opts: { skillName?: string } = {})
| 130 | * missing required fields (host, triggers, args). |
| 131 | */ |
| 132 | export function parseSkillFile(content: string, opts: { skillName?: string } = {}): { frontmatter: SkillFrontmatter; bodyMd: string } { |
| 133 | if (!content.startsWith('---\n')) { |
| 134 | throw new Error('SKILL.md missing frontmatter block (expected starting "---\\n")'); |
| 135 | } |
| 136 | const fmEnd = content.indexOf('\n---', 4); |
| 137 | if (fmEnd === -1) { |
| 138 | throw new Error('SKILL.md frontmatter block not terminated (expected "\\n---")'); |
| 139 | } |
| 140 | const fmText = content.slice(4, fmEnd); |
| 141 | const bodyMd = content.slice(fmEnd + 4).replace(/^\n+/, ''); |
| 142 | const fm = parseFrontmatterFields(fmText); |
| 143 | |
| 144 | // Validate required fields. |
| 145 | const errors: string[] = []; |
| 146 | const name = fm.name ?? opts.skillName ?? ''; |
| 147 | if (!name) errors.push('missing required field: name (or skillName hint)'); |
| 148 | if (!fm.host) errors.push('missing required field: host'); |
| 149 | // triggers and args may be omitted — empty list is valid. |
| 150 | if (errors.length > 0) { |
| 151 | throw new Error(`SKILL.md validation failed: ${errors.join('; ')}`); |
| 152 | } |
| 153 | |
| 154 | const frontmatter: SkillFrontmatter = { |
| 155 | name, |
| 156 | description: fm.description, |
| 157 | host: fm.host as string, |
| 158 | triggers: Array.isArray(fm.triggers) ? fm.triggers : [], |
| 159 | args: Array.isArray(fm.args) ? fm.args : [], |
| 160 | trusted: fm.trusted === true, |
| 161 | version: typeof fm.version === 'string' ? fm.version : undefined, |
| 162 | source: fm.source === 'agent' || fm.source === 'human' ? fm.source : undefined, |
| 163 | }; |
| 164 | |
| 165 | return { frontmatter, bodyMd }; |
| 166 | } |
| 167 | |
| 168 | interface RawFrontmatter { |
| 169 | name?: string; |
no test coverage detected