2026-05-07 11:11:12 -07:00
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 = "<EXTREMELY_IMPORTANT>" ;
const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for pi" ;
const extensionDir = dirname ( fileURLToPath ( import . meta . url ) ) ;
2026-05-07 19:42:34 -07:00
const packageRoot = resolve ( extensionDir , "../.." ) ;
2026-05-07 11:11:12 -07:00
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 ( ) }
< / EXTREMELY_IMPORTANT > ` ;
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
2026-05-13 17:53:35 -07:00
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.
2026-05-07 11:11:12 -07:00
2026-05-13 17:53:35 -07:00
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.
2026-05-07 11:11:12 -07:00
2026-05-13 17:53:35 -07:00
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.
2026-05-07 11:11:12 -07:00
2026-05-13 17:53:35 -07:00
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. ` ;
2026-05-07 11:11:12 -07:00
}
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 ;
}