11import { formatFrontmatter } from "../utils/frontmatter"
2- import { normalizePiSkillName , uniquePiSkillName } from "../utils/pi-skills"
2+ import { normalizePiSkillName , transformPiBodyContent , uniquePiSkillName } from "../utils/pi-skills"
33import type { ClaudeAgent , ClaudeCommand , ClaudeMcpServer , ClaudePlugin } from "../types/claude"
44import type {
55 PiBundle ,
@@ -49,14 +49,13 @@ export function convertClaudeToPi(
4949}
5050
5151function convertPrompt ( command : ClaudeCommand , usedNames : Set < string > ) {
52- const name = uniqueName ( normalizeName ( command . name ) , usedNames )
52+ const name = uniquePiSkillName ( normalizePiSkillName ( command . name ) , usedNames )
5353 const frontmatter : Record < string , unknown > = {
5454 description : command . description ,
5555 "argument-hint" : command . argumentHint ,
5656 }
5757
58- let body = transformContentForPi ( command . body )
59- body = appendCompatibilityNoteIfNeeded ( body )
58+ const body = transformPiBodyContent ( command . body )
6059
6160 return {
6261 name,
@@ -80,74 +79,19 @@ function convertAgent(agent: ClaudeAgent, usedNames: Set<string>): PiGeneratedSk
8079 sections . push ( `## Capabilities\n${ agent . capabilities . map ( ( capability ) => `- ${ capability } ` ) . join ( "\n" ) } ` )
8180 }
8281
83- const body = [
82+ const body = transformPiBodyContent ( [
8483 ...sections ,
8584 agent . body . trim ( ) . length > 0
8685 ? agent . body . trim ( )
8786 : `Instructions converted from the ${ agent . name } agent.` ,
88- ] . join ( "\n\n" )
87+ ] . join ( "\n\n" ) )
8988
9089 return {
9190 name,
9291 content : formatFrontmatter ( frontmatter , body ) ,
9392 }
9493}
9594
96- function transformContentForPi ( body : string ) : string {
97- let result = body
98-
99- // Task repo-research-analyst(feature_description)
100- // -> Run subagent with agent="repo-research-analyst" and task="feature_description"
101- const taskPattern = / ^ ( \s * - ? \s * ) T a s k \s + ( [ a - z ] [ a - z 0 - 9 - ] * ) \( ( [ ^ ) ] + ) \) / gm
102- result = result . replace ( taskPattern , ( _match , prefix : string , agentName : string , args : string ) => {
103- const skillName = normalizeName ( agentName )
104- const trimmedArgs = args . trim ( ) . replace ( / \s + / g, " " )
105- return `${ prefix } Run subagent with agent=\"${ skillName } \" and task=\"${ trimmedArgs } \".`
106- } )
107-
108- // Claude-specific tool references
109- result = result . replace ( / \b A s k U s e r Q u e s t i o n \b / g, "ask_user_question" )
110- result = result . replace ( / \b T o d o W r i t e \b / g, "file-based todos (todos/ + /skill:file-todos)" )
111- result = result . replace ( / \b T o d o R e a d \b / g, "file-based todos (todos/ + /skill:file-todos)" )
112-
113- // /command-name or /workflows:command-name -> /workflows-command-name
114- const slashCommandPattern = / (?< ! [: \w] ) \/ ( [ a - z ] [ a - z 0 - 9 _ : - ] * ?) (? = [ \s , . " ' ) \] } ` ] | $ ) / gi
115- result = result . replace ( slashCommandPattern , ( match , commandName : string ) => {
116- if ( commandName . includes ( "/" ) ) return match
117- if ( [ "dev" , "tmp" , "etc" , "usr" , "var" , "bin" , "home" ] . includes ( commandName ) ) {
118- return match
119- }
120-
121- if ( commandName . startsWith ( "skill:" ) ) {
122- const skillName = commandName . slice ( "skill:" . length )
123- return `/skill:${ normalizePiSkillName ( skillName ) } `
124- }
125-
126- const withoutPrefix = commandName . startsWith ( "prompts:" )
127- ? commandName . slice ( "prompts:" . length )
128- : commandName
129-
130- return `/${ normalizeName ( withoutPrefix ) } `
131- } )
132-
133- return result
134- }
135-
136- function appendCompatibilityNoteIfNeeded ( body : string ) : string {
137- if ( ! / \b m c p \b / i. test ( body ) ) return body
138-
139- const note = [
140- "" ,
141- "## Pi + MCPorter note" ,
142- "For MCP access in Pi, use MCPorter via the generated tools:" ,
143- "- `mcporter_list` to inspect available MCP tools" ,
144- "- `mcporter_call` to invoke a tool" ,
145- "" ,
146- ] . join ( "\n" )
147-
148- return body + note
149- }
150-
15195function convertMcpToMcporter ( servers : Record < string , ClaudeMcpServer > ) : PiMcporterConfig {
15296 const mcpServers : Record < string , PiMcporterServer > = { }
15397
@@ -173,36 +117,10 @@ function convertMcpToMcporter(servers: Record<string, ClaudeMcpServer>): PiMcpor
173117 return { mcpServers }
174118}
175119
176- function normalizeName ( value : string ) : string {
177- const trimmed = value . trim ( )
178- if ( ! trimmed ) return "item"
179- const normalized = trimmed
180- . toLowerCase ( )
181- . replace ( / [ \\ / ] + / g, "-" )
182- . replace ( / [: \s] + / g, "-" )
183- . replace ( / [ ^ a - z 0 - 9 _ - ] + / g, "-" )
184- . replace ( / - + / g, "-" )
185- . replace ( / ^ - + | - + $ / g, "" )
186- return normalized || "item"
187- }
188-
189120function sanitizeDescription ( value : string , maxLength = PI_DESCRIPTION_MAX_LENGTH ) : string {
190121 const normalized = value . replace ( / \s + / g, " " ) . trim ( )
191122 if ( normalized . length <= maxLength ) return normalized
192123 const ellipsis = "..."
193124 return normalized . slice ( 0 , Math . max ( 0 , maxLength - ellipsis . length ) ) . trimEnd ( ) + ellipsis
194125}
195126
196- function uniqueName ( base : string , used : Set < string > ) : string {
197- if ( ! used . has ( base ) ) {
198- used . add ( base )
199- return base
200- }
201- let index = 2
202- while ( used . has ( `${ base } -${ index } ` ) ) {
203- index += 1
204- }
205- const name = `${ base } -${ index } `
206- used . add ( name )
207- return name
208- }
0 commit comments