Skip to content

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

Package listing

@kody/skills

src/skill-history.ts

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

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

export type SkillHistoryItem = {
	version_id: number
	path: string
	replaced_at: string
	content_length: number
	/** Present on the repo backend: git commit that holds the restored content. */
	commit_oid?: string
}

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

	const rows = path
		? await sqlRows<SkillVersionRow>(
				`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<SkillVersionRow>(
				`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) => ({
		version_id: Number(row.id),
		path: row.path,
		replaced_at: row.replaced_at,
		content_length: String(row.content ?? '').length,
	}))
}

async function historyRepo(
	id: string,
	path: string,
	limit: number,
): Promise<SkillHistoryItem[]> {
	await getSkillOrThrow(id)
	const rows = await listSkillRevisions({
		skill_id: id,
		path: path || undefined,
		limit,
	})
	return rows.map((row) => ({
		version_id: Number(row.id),
		path: row.path,
		replaced_at: row.replaced_at,
		content_length: Number(row.content_length),
		commit_oid: row.commit_oid,
	}))
}

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