repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/build/load-probe.ts
Raw// Loads a generated extension package through Pi's real loader and a print-mode
// session, then reports registrations as JSON on the last stdout line.
// Runs as a child process so the probed package sees a clean module graph.
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { getModels } from "@earendil-works/pi-ai/compat";
import {
createAgentSession,
DefaultResourceLoader,
SessionManager,
} from "@earendil-works/pi-coding-agent";
import { type ProbeReport, registrationConflicts } from "./conflicts.ts";
async function probe(packageRoots: string[]): Promise<ProbeReport> {
const cwd = mkdtempSync(join(tmpdir(), "pi-ext-probe-cwd-"));
const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(cwd, "agent");
if (process.env.PI_EXT_PROBE_USER_SETTINGS) {
mkdirSync(agentDir, { recursive: true });
writeFileSync(
join(agentDir, "settings.json"),
process.env.PI_EXT_PROBE_USER_SETTINGS,
);
}
const errors: string[] = [];
const packageNames: string[] = [];
const entries = packageRoots.flatMap((root) => {
const pkg = JSON.parse(
readFileSync(join(root, "package.json"), "utf8"),
) as {
name?: string;
pi?: { extensions?: string[] };
};
packageNames.push(pkg.name ?? root);
return (pkg.pi?.extensions ?? ["index.js"]).map((entry) =>
resolve(root, entry),
);
});
let session:
| Awaited<ReturnType<typeof createAgentSession>>["session"]
| undefined;
try {
const loader = new DefaultResourceLoader({
cwd,
agentDir,
noExtensions: true,
noSkills: true,
noPromptTemplates: true,
noThemes: true,
noContextFiles: true,
additionalExtensionPaths: entries,
});
await loader.reload();
const loaded = loader.getExtensions();
errors.push(...loaded.errors.map((error) => String(error)));
const model = getModels("openai")[0] ?? getModels("anthropic")[0];
if (!model)
throw new Error("Pi has no built-in model for an in-memory session.");
({ session } = await createAgentSession({
cwd,
agentDir,
model,
resourceLoader: loader,
sessionManager: SessionManager.inMemory(cwd),
}));
await session.bindExtensions({
mode: "print",
onError: (error) => errors.push(String(error)),
});
const activeTools = session.getActiveToolNames();
const extensions = loaded.extensions.map((extension) => ({
path: extension.path,
tools: [...extension.tools.keys()].sort(),
commands: [...extension.commands.keys()].sort(),
flags: [...extension.flags.keys()].sort(),
activeTools: [...extension.tools.keys()]
.filter((name) => activeTools.includes(name))
.sort(),
}));
const skills = session.resourceLoader
.getSkills()
.skills.map((skill) => skill.filePath)
.sort();
await session.extensionRunner.emit({
type: "session_shutdown",
reason: "quit",
});
return {
errors,
extensions,
skills,
conflicts: registrationConflicts(extensions, packageNames, skills),
};
} finally {
session?.dispose();
rmSync(cwd, { recursive: true, force: true });
}
}
const roots = process.argv.slice(2);
if (!roots.length) {
console.error("usage: node scripts/build/load-probe.ts <package-root>...");
process.exit(2);
}
const report = await probe(roots).catch((error: unknown) => ({
errors: [
error instanceof Error ? (error.stack ?? error.message) : String(error),
],
extensions: [],
skills: [],
conflicts: [],
}));
console.log(`PI_EXT_PROBE=${JSON.stringify(report)}`);
process.exit(0);