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 { 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(); }, }; }); }