import { access, readdir, readFile } from 'fs/promises'; import { basename, dirname, join, resolve } from 'path'; import { homedir } from 'os'; import { parse as parseYaml } from 'yaml'; export interface SkillSummary { name: string; description?: string; } export interface ListSkillsRequest { } export interface ListSkillsResponse { success: boolean; skills?: SkillSummary[]; error?: string; } function getHomeDirectory(): string { return process.env.HOME ?? process.env.USERPROFILE ?? homedir(); } function getUserSkillsRoots(): string[] { const home = getHomeDirectory(); return [ join(home, '.agents', 'skills'), join(home, '.claude', 'skills'), join(home, '.codex', 'skills'), ]; } function getAdminSkillsRoot(): string { return join('/etc', 'codex', 'skills'); } function getProjectSkillsRoots(directory: string): string[] { return [ join(directory, '.agents', 'skills'), join(directory, '.claude', 'skills'), join(directory, '.codex', 'skills'), ]; } async function pathExists(path: string): Promise { try { await access(path); return true; } catch { return false; } } async function listProjectSkillsRoots(workingDirectory?: string): Promise { if (!workingDirectory) { return []; } const resolvedWorkingDirectory = resolve(workingDirectory); const directories = [resolvedWorkingDirectory]; let currentDirectory = resolvedWorkingDirectory; while (true) { if (await pathExists(join(currentDirectory, '.git'))) { return directories.flatMap(getProjectSkillsRoots); } const parentDirectory = dirname(currentDirectory); if (parentDirectory === currentDirectory) { return getProjectSkillsRoots(resolvedWorkingDirectory); } currentDirectory = parentDirectory; directories.push(currentDirectory); } } function parseFrontmatter(fileContent: string): { frontmatter?: Record; body: string } { const match = fileContent.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); if (!match) { return { body: fileContent.trim() }; } const yamlContent = match[1]; const body = match[2].trim(); try { const parsed = parseYaml(yamlContent) as Record | null; return { frontmatter: parsed ?? undefined, body }; } catch { return { body: fileContent.trim() }; } } function extractSkillSummary(skillDir: string, fileContent: string): SkillSummary | null { const parsed = parseFrontmatter(fileContent); const nameFromFrontmatter = typeof parsed.frontmatter?.name === 'string' ? parsed.frontmatter.name.trim() : ''; const name = nameFromFrontmatter || basename(skillDir); if (!name) { return null; } const description = typeof parsed.frontmatter?.description === 'string' ? parsed.frontmatter.description.trim() : undefined; return { name, description }; } async function listTopLevelSkillDirs(skillsRoot: string, options: { includeCodexSystem?: boolean } = {}): Promise { try { const entries = await readdir(skillsRoot, { withFileTypes: true }); const result: string[] = []; for (const entry of entries) { if (!entry.isDirectory()) { continue; } if (entry.name.startsWith('.')) { if (options.includeCodexSystem && entry.name === '.system') { const systemEntries = await readdir(join(skillsRoot, entry.name), { withFileTypes: true }).catch(() => []); for (const systemEntry of systemEntries) { if (systemEntry.isDirectory() && !systemEntry.name.startsWith('.')) { result.push(join(skillsRoot, entry.name, systemEntry.name)); } } } continue; } result.push(join(skillsRoot, entry.name)); } return result; } catch { return []; } } async function readSkillsFromDirs(skillDirs: string[]): Promise { const skills = await Promise.all(skillDirs.map(async (dir): Promise => { const filePath = join(dir, 'SKILL.md'); try { const fileContent = await readFile(filePath, 'utf-8'); return extractSkillSummary(dir, fileContent); } catch { return null; } })); return skills.filter((skill): skill is SkillSummary => skill !== null); } function isCodexSkillsRoot(root: string): boolean { return root.endsWith(join('.codex', 'skills')); } export async function listSkills(workingDirectory?: string): Promise { const projectRoots = await listProjectSkillsRoots(workingDirectory); const userRoots = getUserSkillsRoots(); const adminRoot = getAdminSkillsRoot(); const [projectSkillDirs, userSkillDirs, adminSkillDirs] = await Promise.all([ Promise.all(projectRoots.map(async (root) => await listTopLevelSkillDirs(root, { includeCodexSystem: isCodexSkillsRoot(root) }))).then((dirs) => dirs.flat()), Promise.all(userRoots.map(async (root) => await listTopLevelSkillDirs(root, { includeCodexSystem: isCodexSkillsRoot(root) }))).then((dirs) => dirs.flat()), listTopLevelSkillDirs(adminRoot, { includeCodexSystem: true }), ]); const [projectSkills, userSkills, adminSkills] = await Promise.all([ readSkillsFromDirs(projectSkillDirs), readSkillsFromDirs(userSkillDirs), readSkillsFromDirs(adminSkillDirs), ]); const dedupedSkills = new Map(); for (const skill of [ ...projectSkills, ...userSkills, ...adminSkills, ]) { if (!dedupedSkills.has(skill.name)) { dedupedSkills.set(skill.name, skill); } } return [...dedupedSkills.values()].sort((a, b) => a.name.localeCompare(b.name)); }