Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/skillz/inspect.ts

Raw
import {
	type ExtensionCommandContext,
	getSelectListTheme,
	type SlashCommandInfo,
} from "@earendil-works/pi-coding-agent";
import {
	Container,
	SelectList,
	stripTerminalSequences,
	Text,
} from "@earendil-works/pi-tui";
import type { ConditionCheck } from "./match.ts";

export interface Candidate {
	name: string;
	path: string;
	checks?: ConditionCheck[];
	submitted: boolean;
	reason?: string;
	warning?: boolean;
}

export interface Inspection {
	name: string;
	status: "loaded" | "excluded";
	detail: string;
}

function safe(text: string): string {
	return stripTerminalSequences(text).replace(/\p{Cc}/gu, " ");
}

export function inspectCandidates(
	candidates: Candidate[],
	commands: SlashCommandInfo[],
): Inspection[] {
	const registered = commands.filter((command) => command.source === "skill");
	return candidates
		.map((candidate) => {
			const sameName = registered.find(
				(command) => command.name === `skill:${candidate.name}`,
			);
			const loaded = sameName?.sourceInfo.path === candidate.path;
			const status: Inspection["status"] = loaded ? "loaded" : "excluded";
			const checks = candidate.checks?.map((check) => {
				const choices = check.options
					.map((option) =>
						check.field === "env"
							? `${safe(option.value)} ${option.matches ? "present" : "absent"}`
							: `${safe(option.value)} ${option.matches ? "✓" : "✗"}`,
					)
					.join(", ");
				return `${check.field}${check.mode === "single" ? "" : ` ${check.mode}`} (${choices})`;
			});
			const reason = candidate.reason
				? safe(candidate.reason)
				: candidate.submitted && !loaded
					? sameName
						? `name claimed by ${safe(sameName.sourceInfo.path)}`
						: "eligible, not registered by Pi"
					: undefined;
			return {
				name: safe(candidate.name),
				status,
				detail: [
					loaded && !candidate.submitted
						? "already registered by Pi despite skillz exclusion"
						: undefined,
					reason,
					...(checks ?? []),
					candidate.warning ? "Pi reported a validation warning" : undefined,
				]
					.filter(Boolean)
					.join("; "),
			};
		})
		.sort((left, right) => left.name.localeCompare(right.name));
}

export async function showInspection(
	ctx: ExtensionCommandContext,
	inspection: Inspection[],
	hasSnapshot: boolean,
): Promise<void> {
	const empty = hasSnapshot
		? "No conditional skills discovered"
		: "No skillz discovery snapshot yet";
	if (ctx.mode !== "tui") {
		if (ctx.hasUI) {
			ctx.ui.notify(
				inspection.length
					? inspection
							.map((row) => `${row.status}: ${row.name}: ${row.detail}`)
							.join("\n")
					: empty,
				"info",
			);
		}
		return;
	}
	await ctx.ui.custom((tui, theme, _keybindings, done) => {
		const items = inspection.map((row, index) => ({
			value: String(index),
			label: `${row.status === "loaded" ? "✓" : "✗"} ${row.name}`,
			description: row.detail,
		}));
		const list = new SelectList(items, 10, getSelectListTheme());
		const details = new Text("", 1, 0);
		const update = () => {
			const item = list.getSelectedItem();
			details.setText(item ? inspection[Number(item.value)].detail : empty);
			tui.requestRender();
		};
		list.onSelectionChange = update;
		list.onCancel = () => done(undefined);
		list.onSelect = update;
		const container = new Container();
		container.addChild(
			new Text(
				theme.fg("accent", theme.bold(`Skillz (${inspection.length})`)),
				1,
				0,
			),
		);
		if (inspection.length) container.addChild(list);
		container.addChild(details);
		container.addChild(
			new Text(theme.fg("dim", "↑↓ inspect · Esc close"), 1, 0),
		);
		update();
		return {
			render: (width: number) => container.render(width),
			invalidate: () => container.invalidate(),
			handleInput: (data: string) => {
				list.handleInput(data);
				tui.requestRender();
			},
		};
	});
}