diff --git a/src/core/skills.ts b/src/core/skills.ts index ffc01119..8524ba55 100644 --- a/src/core/skills.ts +++ b/src/core/skills.ts @@ -98,26 +98,43 @@ export async function getAllSkillsFromPlugins( const pluginName = resolved.pluginName ?? getPluginName(pluginPath); const skillsDir = join(pluginPath, 'skills'); - if (!existsSync(skillsDir)) continue; - - const entries = await readdir(skillsDir, { withFileTypes: true }); - const skillDirs = entries.filter((e) => e.isDirectory()); - // Only apply enabledSkills to plugins that actually have entries in the set const hasEnabledEntries = enabledSkills && [...enabledSkills].some((s) => s.startsWith(`${pluginName}:`)); - for (const entry of skillDirs) { - const skillKey = `${pluginName}:${entry.name}`; + let skillEntries: { name: string; skillPath: string }[]; + + if (existsSync(skillsDir)) { + // Standard layout: plugin/skills// + const entries = await readdir(skillsDir, { withFileTypes: true }); + skillEntries = entries + .filter((e) => e.isDirectory()) + .map((e) => ({ name: e.name, skillPath: join(skillsDir, e.name) })); + } else { + // Flat layout: plugin//SKILL.md + const entries = await readdir(pluginPath, { withFileTypes: true }); + const flatSkills: { name: string; skillPath: string }[] = []; + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const skillMdPath = join(pluginPath, entry.name, 'SKILL.md'); + if (existsSync(skillMdPath)) { + flatSkills.push({ name: entry.name, skillPath: join(pluginPath, entry.name) }); + } + } + skillEntries = flatSkills; + } + + for (const { name, skillPath } of skillEntries) { + const skillKey = `${pluginName}:${name}`; const isDisabled = hasEnabledEntries ? !enabledSkills?.has(skillKey) : disabledSkills.has(skillKey); skills.push({ - name: entry.name, + name, pluginName, pluginSource, - path: join(skillsDir, entry.name), + path: skillPath, disabled: isDisabled, }); } diff --git a/src/core/transform.ts b/src/core/transform.ts index 5f53b94e..fcb54802 100644 --- a/src/core/transform.ts +++ b/src/core/transform.ts @@ -367,29 +367,44 @@ export async function collectPluginSkills( ): Promise { const skillsDir = join(pluginPath, 'skills'); - if (!existsSync(skillsDir)) { - return []; - } - - const entries = await readdir(skillsDir, { withFileTypes: true }); - const skillDirs = entries.filter((e) => e.isDirectory()); - // Filter skills: enabledSkills (allowlist) takes priority over disabledSkills (blocklist) // Only apply enabledSkills to plugins that actually have entries in the set const hasEnabledEntries = enabledSkills && pluginName && [...enabledSkills].some((s) => s.startsWith(`${pluginName}:`)); + let candidateDirs: { name: string; path: string }[]; + + if (existsSync(skillsDir)) { + // Standard layout: plugin/skills// + const entries = await readdir(skillsDir, { withFileTypes: true }); + candidateDirs = entries + .filter((e) => e.isDirectory()) + .map((e) => ({ name: e.name, path: join(skillsDir, e.name) })); + } else { + // Flat layout: plugin//SKILL.md + const entries = await readdir(pluginPath, { withFileTypes: true }); + const flatDirs: { name: string; path: string }[] = []; + for (const entry of entries) { + if (!entry.isDirectory()) continue; + const skillMdPath = join(pluginPath, entry.name, 'SKILL.md'); + if (existsSync(skillMdPath)) { + flatDirs.push({ name: entry.name, path: join(pluginPath, entry.name) }); + } + } + candidateDirs = flatDirs; + } + const filteredDirs = pluginName ? hasEnabledEntries - ? skillDirs.filter((e) => enabledSkills?.has(`${pluginName}:${e.name}`)) + ? candidateDirs.filter((e) => enabledSkills?.has(`${pluginName}:${e.name}`)) : disabledSkills - ? skillDirs.filter((e) => !disabledSkills.has(`${pluginName}:${e.name}`)) - : skillDirs - : skillDirs; + ? candidateDirs.filter((e) => !disabledSkills.has(`${pluginName}:${e.name}`)) + : candidateDirs + : candidateDirs; return filteredDirs.map((entry) => ({ folderName: entry.name, - skillPath: join(skillsDir, entry.name), + skillPath: entry.path, pluginPath, pluginSource, }));