import { getBackend } from './backend.ts' import { getFile, getSkillOrThrow, listFilesForSkill, listSkillIds, } from './db.ts' import { parseSkillMeta, readRepoFile, skillFilePath, skillMetaPath, withSkillsRepoSession, } from './repo.ts' export type SkillGetInput = { id: string path?: string } export type SkillFileContent = { path: string content: string } export type SkillGetResult = | { id: string name: string description: string created_at: string updated_at: string files: SkillFileContent[] } | { id: string name: string description: string path: string content: string updated_at: string } async function getFromLegacy( id: string, path: string, ): Promise { const skill = await getSkillOrThrow(id) if (!path) { const files = await listFilesForSkill(id) return { id: skill.id, name: skill.name, description: skill.description, created_at: skill.created_at, updated_at: skill.updated_at, files: files.map((file) => ({ path: file.path, content: file.content, })), } } const file = await getFile(id, path) if (!file) { const files = await listFilesForSkill(id) const available = files.length > 0 ? files.map((row) => `\`${row.path}\``).join(', ') : '(none)' throw new Error( `File not found: \`${path}\` in skill \`${id}\`. Valid paths: ${available}.`, ) } return { id: skill.id, name: skill.name, description: skill.description, path: file.path, content: file.content, updated_at: file.updated_at, } } async function getFromRepo(id: string, path: string): Promise { return withSkillsRepoSession(async (session) => { const meta = parseSkillMeta(await readRepoFile(session.id, skillMetaPath(id))) if (!meta) { const ids = await listSkillIds() const available = ids.length > 0 ? ids.map((value) => `\`${value}\``).join(', ') : '(none)' throw new Error( `Skill not found: \`${id}\`. Valid skill ids: ${available}. Call skill_list for details.`, ) } if (!path) { const files: SkillFileContent[] = [] for (const filePath of meta.files) { const content = await readRepoFile( session.id, skillFilePath(id, filePath), ) if (content == null) continue files.push({ path: filePath, content }) } return { id, name: meta.name, description: meta.description, created_at: meta.created_at, updated_at: meta.updated_at, files, } } const content = await readRepoFile(session.id, skillFilePath(id, path)) if (content == null) { const available = meta.files.length > 0 ? meta.files.map((value) => `\`${value}\``).join(', ') : '(none)' throw new Error( `File not found: \`${path}\` in skill \`${id}\`. Valid paths: ${available}.`, ) } return { id, name: meta.name, description: meta.description, path, content, updated_at: meta.updated_at, } }) } /** * Read a skill. Without `path`, returns metadata plus all files. * With `path`, returns that one file. * * @example * import skillGet from 'kody:@kody/skills/skill-get' * const skill = await skillGet({ id: 'writing-style' }) * const file = await skillGet({ id: 'writing-style', path: 'SKILL.md' }) */ export default async function skillGet( params: SkillGetInput = {} as SkillGetInput, ): Promise { const id = String(params.id ?? '').trim() if (!id) { const ids = await listSkillIds() const available = ids.length > 0 ? ids.map((value) => `\`${value}\``).join(', ') : '(none)' throw new Error( `skill_get requires \`id\`. Valid skill ids: ${available}. Call skill_list for details.`, ) } const path = params.path == null ? '' : String(params.path).trim() const backend = await getBackend() return backend === 'repo' ? getFromRepo(id, path) : getFromLegacy(id, path) }