repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/skillz/inspect.ts
Rawimport {
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();
},
};
});
}