diff --git a/src/cli/commands/plugin-skills.ts b/src/cli/commands/plugin-skills.ts index f61e771..8d58077 100644 --- a/src/cli/commands/plugin-skills.ts +++ b/src/cli/commands/plugin-skills.ts @@ -166,7 +166,8 @@ function extractInlineRef(spec: string): string | undefined { * If the skill argument is a GitHub URL, extract the skill name and return * it along with the URL as the plugin source. Returns null if not a URL. * - * With subpath: skill name = last path segment + * With subpath: skill name = last path segment. A URL ending in SKILL.md is + * normalized to its containing skill directory. * Without subpath: skill name = repo name (caller should use resolveSkillNameFromRepo to check frontmatter) */ export function resolveSkillFromUrl( @@ -183,7 +184,18 @@ export function resolveSkillFromUrl( if (parsed.subpath) { const segments = parsed.subpath.split('/').filter(Boolean); - const name = segments[segments.length - 1]; + if (segments.at(-1) === 'SKILL.md') { + const from = skill.slice(0, -'/SKILL.md'.length); + const containingParsed = parseGitHubUrl(from); + if (!containingParsed) return null; + return { + skill: segments.at(-2) ?? parsed.repo, + from, + parsed: containingParsed, + }; + } + + const name = segments.at(-1); if (!name) return null; return { skill: name, from: skill, parsed }; } diff --git a/tests/unit/cli/skills-add-url-detection.test.ts b/tests/unit/cli/skills-add-url-detection.test.ts index 02065a3..8b3dfcf 100644 --- a/tests/unit/cli/skills-add-url-detection.test.ts +++ b/tests/unit/cli/skills-add-url-detection.test.ts @@ -25,6 +25,37 @@ describe('resolveSkillFromUrl', () => { }); }); + it('resolves a SKILL.md blob URL to its containing skill directory', () => { + const result = resolveSkillFromUrl( + 'https://github.com/anthropics/skills/blob/main/skills/pdf/SKILL.md', + ); + expect(result).toEqual({ + skill: 'pdf', + from: 'https://github.com/anthropics/skills/blob/main/skills/pdf', + parsed: expect.objectContaining({ + owner: 'anthropics', + repo: 'skills', + branch: 'main', + subpath: 'skills/pdf', + }), + }); + }); + + it('resolves a root SKILL.md blob URL to the repository root', () => { + const result = resolveSkillFromUrl( + 'https://github.com/owner/root-skill/blob/main/SKILL.md', + ); + expect(result).toEqual({ + skill: 'root-skill', + from: 'https://github.com/owner/root-skill/blob/main', + parsed: { + owner: 'owner', + repo: 'root-skill', + branch: 'main', + }, + }); + }); + it('extracts skill name from URL with deep subpath', () => { const result = resolveSkillFromUrl( 'https://github.com/org/repo/tree/main/plugins/my-plugin/skills/cool-skill',