Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/skills

src/skill-list.ts

103 lines · 2.5 KB · TypeScript
import { getBackend } from './backend.ts'
import {
	ensureSchema,
	listFilesForSkill,
	listSkillsIndex,
	sqlRows,
	type SkillRow,
} from './db.ts'
import {
	listSkillIdsFromRepo,
	parseSkillMeta,
	readRepoFile,
	skillMetaPath,
	withSkillsRepoSession,
} from './repo.ts'

export type SkillListItem = {
	id: string
	name: string
	description: string
	files: string[]
	updated_at: string
}

async function listFromLegacy(): Promise<SkillListItem[]> {
	await ensureSchema()
	const skills = await sqlRows<SkillRow>(
		`SELECT id, name, description, created_at, updated_at
		 FROM skills
		 ORDER BY id ASC`,
	)

	const items: SkillListItem[] = []
	for (const skill of skills) {
		const files = await listFilesForSkill(skill.id)
		items.push({
			id: skill.id,
			name: skill.name,
			description: skill.description,
			files: files.map((file) => file.path),
			updated_at: skill.updated_at,
		})
	}
	return items
}

async function listFromRepo(): Promise<SkillListItem[]> {
	const indexed = await listSkillsIndex()
	if (indexed.length > 0) {
		return indexed.map((row) => {
			let files: string[] = []
			try {
				const parsed = JSON.parse(row.files_json) as unknown
				if (Array.isArray(parsed)) {
					files = parsed.filter((value): value is string => typeof value === 'string')
				}
			} catch {
				files = []
			}
			return {
				id: row.id,
				name: row.name,
				description: row.description,
				files,
				updated_at: row.updated_at,
			}
		})
	}

	// Index empty (fresh install / before migrate sync): discover from repo.
	return withSkillsRepoSession(async (session) => {
		const ids = await listSkillIdsFromRepo(session.id)
		const items: SkillListItem[] = []
		for (const id of ids) {
			const meta = parseSkillMeta(
				await readRepoFile(session.id, skillMetaPath(id)),
			)
			if (!meta) continue
			items.push({
				id,
				name: meta.name,
				description: meta.description,
				files: meta.files,
				updated_at: meta.updated_at,
			})
		}
		return items
	})
}

/**
 * List all skills in the registry with descriptions and file paths.
 * This is the agents' index — descriptions are complete enough to choose a skill.
 *
 * @example
 * import skillList from 'kody:@kody/skills/skill-list'
 * const skills = await skillList()
 * // => [{ id: 'writing-style', name: '...', description: '...', files: ['SKILL.md', ...], updated_at }]
 */
export default async function skillList(): Promise<SkillListItem[]> {
	const backend = await getBackend()
	return backend === 'repo' ? listFromRepo() : listFromLegacy()
}