diff --git a/src/Cli.ts b/src/Cli.ts index f7a2f82..e2462fa 100644 --- a/src/Cli.ts +++ b/src/Cli.ts @@ -2,6 +2,7 @@ import * as fs from 'node:fs/promises' import * as os from 'node:os' import * as path from 'node:path' import { estimateTokenCount, sliceByTokens } from 'tokenx' +import { parse as yamlParse } from 'yaml' import type { z } from 'zod' import * as Completions from './Completions.js' @@ -1565,10 +1566,11 @@ async function fetchImpl( if (segments[2] === 'index.json' && segments.length === 3) { const files = Skill.split(name, cmds, 1, groups) const skills = files.map((f) => { - const descMatch = f.content.match(/^description:\s*(.+)$/m) + const fmMatch = f.content.match(/^---\n([\s\S]*?)\n---/) + const meta = fmMatch ? (yamlParse(fmMatch[1]!) as Record) : {} return { name: f.dir || name, - description: descMatch?.[1] ?? '', + description: meta.description ?? '', files: ['SKILL.md'], } }) diff --git a/src/Skill.test.ts b/src/Skill.test.ts index 9410d23..7ae0aa7 100644 --- a/src/Skill.test.ts +++ b/src/Skill.test.ts @@ -341,6 +341,14 @@ describe('split', () => { expect(files[0]!.content).toContain('requires_bin: gh') }) + test('YAML-quotes description containing colon-space', () => { + const groups = new Map([['search', 'Search items. Use key: value for precision']]) + const files = Skill.split('app', [{ name: 'search list', description: 'List results' }], 1, groups) + expect(files[0]!.content).toContain( + 'description: "Search items. Use key: value for precision. List results. Run `app search --help` for usage details."', + ) + }) + test('no per-command frontmatter in split files', () => { const files = Skill.split('gh', commands, 1, groups) const afterFrontmatter = files[0]!.content.slice( diff --git a/src/Skill.ts b/src/Skill.ts index 3ec985f..495014d 100644 --- a/src/Skill.ts +++ b/src/Skill.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto' import type { z } from 'zod' +import { stringify as yamlStringify } from 'yaml' import * as Schema from './Schema.js' @@ -132,13 +133,14 @@ function renderGroup( .replace(/[^a-z0-9-]+/g, '-') .replace(/-{2,}/g, '-') .replace(/^-|-$/g, '') - const fm = ['---', `name: ${slug}`] - fm.push(`description: ${description}`) - fm.push(`requires_bin: ${cli}`) - fm.push(`command: ${title}`, '---') + const fm = yamlStringify( + { name: slug, description, requires_bin: cli, command: title }, + { lineWidth: 0 }, + ).trimEnd() + const fmBlock = `---\n${fm}\n---` const body = cmds.map((cmd) => renderCommandBody(cli, cmd)).join('\n\n---\n\n') - return `${fm.join('\n')}\n\n${body}` + return `${fmBlock}\n\n${body}` } /** @internal Renders a command's heading and sections without frontmatter. */