(
body: string,
targets?: CodexInvocationTargets,
options: CodexTransformOptions = {},
)
| 21 | * 4. Claude config paths: .claude/ -> .codex/ |
| 22 | */ |
| 23 | export function transformContentForCodex( |
| 24 | body: string, |
| 25 | targets?: CodexInvocationTargets, |
| 26 | options: CodexTransformOptions = {}, |
| 27 | ): string { |
| 28 | let result = body |
| 29 | const promptTargets = targets?.promptTargets ?? {} |
| 30 | const skillTargets = targets?.skillTargets ?? {} |
| 31 | const agentTargets = targets?.agentTargets ?? {} |
| 32 | const unknownSlashBehavior = options.unknownSlashBehavior ?? "prompt" |
| 33 | |
| 34 | const taskPattern = /^(\s*-?\s*)Task\s+([a-z][a-z0-9:-]*)\(([^)]*)\)/gm |
| 35 | result = result.replace(taskPattern, (_match, prefix: string, agentName: string, args: string) => { |
| 36 | const agentTarget = resolveAgentTarget(agentName, agentTargets) |
| 37 | const trimmedArgs = args.trim() |
| 38 | if (agentTarget) { |
| 39 | return trimmedArgs |
| 40 | ? `${prefix}Spawn the custom agent \`${agentTarget}\` with task: ${trimmedArgs}` |
| 41 | : `${prefix}Spawn the custom agent \`${agentTarget}\`` |
| 42 | } |
| 43 | |
| 44 | // For namespaced calls like "compound-engineering:research:repo-research-analyst", |
| 45 | // use only the final segment as the skill name when no custom agent target exists. |
| 46 | const finalSegment = agentName.includes(":") ? agentName.split(":").pop()! : agentName |
| 47 | const skillName = normalizeCodexName(finalSegment) |
| 48 | return trimmedArgs |
| 49 | ? `${prefix}Use the $${skillName} skill to: ${trimmedArgs}` |
| 50 | : `${prefix}Use the $${skillName} skill` |
| 51 | }) |
| 52 | |
| 53 | const backtickedAgentPattern = /`([a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*){1,2})`/gi |
| 54 | result = result.replace(backtickedAgentPattern, (match, agentName: string) => { |
| 55 | const agentTarget = resolveAgentTarget(agentName, agentTargets) |
| 56 | return agentTarget ? `custom agent \`${agentTarget}\`` : match |
| 57 | }) |
| 58 | |
| 59 | const slashCommandPattern = /(?<![:\w>}\]\)])\/([a-z][a-z0-9_:-]*?)(?=[\s,."')\]}`]|$)/gi |
| 60 | result = result.replace(slashCommandPattern, (match, commandName: string) => { |
| 61 | if (commandName.includes("/")) return match |
| 62 | if (["dev", "tmp", "etc", "usr", "var", "bin", "home"].includes(commandName)) return match |
| 63 | |
| 64 | const normalizedName = normalizeCodexName(commandName) |
| 65 | if (promptTargets[normalizedName]) { |
| 66 | return `/prompts:${promptTargets[normalizedName]}` |
| 67 | } |
| 68 | if (skillTargets[normalizedName]) { |
| 69 | return `the ${skillTargets[normalizedName]} skill` |
| 70 | } |
| 71 | if (unknownSlashBehavior === "preserve") { |
| 72 | return match |
| 73 | } |
| 74 | return `/prompts:${normalizedName}` |
| 75 | }) |
| 76 | |
| 77 | result = result |
| 78 | .replace(/~\/\.claude\//g, "~/.codex/") |
| 79 | .replace(/\.claude\//g, ".codex/") |
| 80 |
no test coverage detected