import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; const EXTREMELY_IMPORTANT_MARKER = ""; const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for pi"; const extensionDir = dirname(fileURLToPath(import.meta.url)); const packageRoot = resolve(extensionDir, "../.."); const skillsDir = resolve(packageRoot, "skills"); const bootstrapSkillPath = resolve(skillsDir, "using-superpowers", "SKILL.md"); let cachedBootstrap: string | null | undefined; export default function superpowersPiExtension(pi: ExtensionAPI) { let injectBootstrap = true; pi.on("resources_discover", async () => ({ skillPaths: [skillsDir], })); pi.on("session_start", async () => { injectBootstrap = true; }); pi.on("session_compact", async () => { injectBootstrap = true; }); pi.on("agent_end", async () => { injectBootstrap = false; }); pi.on("context", async (event) => { if (!injectBootstrap) return; if (event.messages.some(messageContainsBootstrap)) return; const bootstrap = getBootstrapContent(); if (!bootstrap) return; const bootstrapMessage = { role: "user" as const, content: [{ type: "text" as const, text: bootstrap }], timestamp: Date.now(), }; const insertAt = firstNonCompactionSummaryIndex(event.messages); return { messages: [ ...event.messages.slice(0, insertAt), bootstrapMessage, ...event.messages.slice(insertAt), ], }; }); } function getBootstrapContent(): string | null { if (cachedBootstrap !== undefined) return cachedBootstrap; try { const skillContent = readFileSync(bootstrapSkillPath, "utf8"); const body = stripFrontmatter(skillContent); cachedBootstrap = `${EXTREMELY_IMPORTANT_MARKER} ${BOOTSTRAP_MARKER} You have superpowers. The using-superpowers skill content is included below and is already loaded for this Pi session. Follow it now. Do not try to load using-superpowers again. ${body} ${piToolMapping()} `; return cachedBootstrap; } catch { cachedBootstrap = null; return null; } } function stripFrontmatter(content: string): string { const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/); return (match ? match[1] : content).trim(); } function piToolMapping(): string { return `## Pi tool mapping Pi has native skills but does not expose Claude Code's \`Skill\` tool. When a Superpowers instruction says to invoke a skill, use Pi's native skill system instead: load the relevant \`SKILL.md\` with \`read\` when the skill applies, or let a human invoke \`/skill:name\` explicitly. Pi's built-in coding tools are lowercase: \`read\`, \`write\`, \`edit\`, \`bash\`, plus optional \`grep\`, \`find\`, and \`ls\`. Use those for the corresponding actions: read a file, create or edit files, run shell commands, search file contents, find files by name, and list directories. Pi does not ship a standard subagent tool. If a subagent tool such as \`subagent\` from \`pi-subagents\` is available, use it for Superpowers subagent workflows. If no subagent tool is available, do the work in this session or explain the missing capability instead of inventing \`Task\` calls. Pi does not ship a standard task-list tool. If an installed todo/task tool is available, use it. Otherwise track work in plan files or a repo-local \`TODO.md\` when task tracking is needed. Treat older \`TodoWrite\` references as this task-tracking action.`; } function messageContainsBootstrap(message: unknown): boolean { const content = (message as { content?: unknown }).content; if (typeof content === "string") return content.includes(BOOTSTRAP_MARKER); if (!Array.isArray(content)) return false; return content.some((part) => { return ( part && typeof part === "object" && (part as { type?: unknown }).type === "text" && typeof (part as { text?: unknown }).text === "string" && (part as { text: string }).text.includes(BOOTSTRAP_MARKER) ); }); } function firstNonCompactionSummaryIndex(messages: unknown[]): number { let index = 0; while ((messages[index] as { role?: unknown } | undefined)?.role === "compactionSummary") { index += 1; } return index; }