Skip to content
← Public packages

@kentcdodds/skills

Store, retrieve, edit, version, and revert reusable agent skill documents via skillList then skillGet.

src/skill-history.ts

106 lines · 2.4 KB · TypeScript
import { getBackend } from './backend.ts'
import {
	ensureSchema,
	getSkillOrThrow,
	listSkillRevisions,
	sqlRows,
} from './db.ts'

export type SkillHistoryInput = {
	id: string
	path?: string
	limit?: number
}

export type SkillHistoryItem = {
	versionId: number
	path: string
	replacedAt: string
	contentLength: number
	/** Present on the repo backend: git commit that holds the restored content. */
	commitOid?: string
}

type SqlVersionRow = {
	id: number
	skill_id: string
	path: string
	content: string
	replaced_at: string
}

async function historyLegacy(
	id: string,
	path: string,
	limit: number,
): Promise<SkillHistoryItem[]> {
	await ensureSchema()
	await getSkillOrThrow(id)

	const rows = path
		? await sqlRows<SqlVersionRow>(
				`SELECT id, skill_id, path, content, replaced_at
				 FROM skill_versions
				 WHERE skill_id = ? AND path = ?
				 ORDER BY id DESC
				 LIMIT ?`,
				[id, path, limit],
			)
		: await sqlRows<SqlVersionRow>(
				`SELECT id, skill_id, path, content, replaced_at
				 FROM skill_versions
				 WHERE skill_id = ?
				 ORDER BY id DESC
				 LIMIT ?`,
				[id, limit],
			)

	return rows.map((row) => ({
		versionId: Number(row.id),
		path: row.path,
		replacedAt: row.replaced_at,
		contentLength: String(row.content ?? '').length,
	}))
}

async function historyRepo(
	id: string,
	path: string,
	limit: number,
): Promise<SkillHistoryItem[]> {
	await getSkillOrThrow(id)
	const rows = await listSkillRevisions({
		skillId: id,
		path: path || undefined,
		limit,
	})
	return rows.map((row) => ({
		versionId: Number(row.id),
		path: row.path,
		replacedAt: row.replacedAt,
		contentLength: Number(row.contentLength),
		commitOid: row.commitOid,
	}))
}

/**
 * Recent skill version metadata (not full content).
 * On the repo backend, each version maps to a git commit (`commitOid`).
 *
 * @example
 * import skillHistory from 'kody:@kentcdodds/skills/skill-history'
 * const history = await skillHistory({ id: 'kent-writing-voice', limit: 10 })
 */
export default async function skillHistory(
	params: SkillHistoryInput = {} as SkillHistoryInput,
): Promise<SkillHistoryItem[]> {
	const id = String(params.id ?? '').trim()
	if (!id) throw new Error('skillHistory requires `id`.')

	const path = params.path == null ? '' : String(params.path).trim()
	const limit = Math.max(1, Math.min(200, Number(params.limit ?? 50) || 50))
	const backend = await getBackend()
	return backend === 'repo'
		? historyRepo(id, path, limit)
		: historyLegacy(id, path, limit)
}