/** * create-wizard.ts — Multi-step agent creation wizard. * * Guides the user through creating a new agent with two paths: * 1. AI-generated: Describe intent → AI generates the .md file * 2. Manual: Walk through each setting step by step */ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { getAgentDir } from "@mariozechner/pi-coding-agent"; import type { AgentManager } from "../agent-manager.js"; import { loadCustomAgents } from "../custom-agents.js"; import { showModelPicker } from "./model-picker.js"; import { showToolPicker } from "./tool-picker.js"; // ---- Path Helpers ---- const projectAgentsDir = () => join(process.cwd(), ".pi", "agents"); const personalAgentsDir = () => join(getAgentDir(), "agents"); /** * Reload custom agents in the registry. */ function reloadAgents(): void { const { registerAgents } = require("../agent-types.js"); const agents = loadCustomAgents(process.cwd()); registerAgents(agents); } /** * Show the multi-step creation wizard. * * @param ctx Extension command context * @param manager AgentManager for spawning AI generation agents * @param pi ExtensionAPI (needed for spawnAndWait) */ export async function showCreateWizard( ctx: ExtensionCommandContext, manager: AgentManager, pi: any, ): Promise { // Step 1: Choose location const location = await ctx.ui.select("Choose location", [ "Project (.pi/agents/)", `Personal (${personalAgentsDir()})`, ]); if (!location) return; const targetDir = location.startsWith("Project") ? projectAgentsDir() : personalAgentsDir(); // Step 2: Choose method const method = await ctx.ui.select("Creation method", [ "Generate with Claude (recommended)", "Manual configuration", ]); if (!method) return; if (method.startsWith("Generate")) { await showGenerateWizard(ctx, targetDir, manager, pi); } else { await showManualWizard(ctx, targetDir); } } // ---- AI-Generated Path ---- async function showGenerateWizard( ctx: ExtensionCommandContext, targetDir: string, manager: AgentManager, pi: any, ): Promise { const description = await ctx.ui.input("Describe what this agent should do"); if (!description) return; const name = await ctx.ui.input("Agent name (filename, no spaces)"); if (!name) return; mkdirSync(targetDir, { recursive: true }); const targetPath = join(targetDir, `${name}.md`); if (existsSync(targetPath)) { const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`); if (!overwrite) return; } ctx.ui.notify("Generating agent definition...", "info"); const generatePrompt = `Create a custom pi sub-agent definition file based on this description: "${description}" Write a markdown file to: ${targetPath} The file format is a markdown file with YAML frontmatter and a system prompt body: \`\`\`markdown --- description: tools: model: thinking: max_turns: prompt_mode: <"replace" (body IS the full system prompt) or "append" (body is appended to default prompt). Default: replace> extensions: skills: disallowed_tools: inherit_context: run_in_background: isolated: memory: <"user" (global), "project" (per-project), or "local" (gitignored per-project) for persistent memory. Omit for none> isolation: <"worktree" to run in isolated git worktree. Omit for normal> --- \`\`\` Guidelines for choosing settings: - For read-only tasks (review, analysis): tools: read, bash, grep, find, ls - For code modification tasks: include edit, write - Use prompt_mode: append if the agent should keep the default system prompt and add specialization on top - Use prompt_mode: replace for fully custom agents with their own personality/instructions - Set inherit_context: true if the agent needs to know what was discussed in the parent conversation - Set isolated: true if the agent should NOT have access to MCP servers or other extensions - Only include frontmatter fields that differ from defaults — omit fields where the default is fine Write the file using the write tool. Only write the file, nothing else.`; const record = await manager.spawnAndWait(pi, ctx, "general-purpose", generatePrompt, { description: `Generate ${name} agent`, maxTurns: 5, }); if (record.status === "error") { ctx.ui.notify(`Generation failed: ${record.error}`, "warning"); return; } reloadAgents(); if (existsSync(targetPath)) { ctx.ui.notify(`Created ${targetPath}`, "info"); } else { ctx.ui.notify("Agent generation completed but file was not created. Check the agent output.", "warning"); } } // ---- Manual Path ---- async function showManualWizard(ctx: ExtensionCommandContext, targetDir: string): Promise { // Step 3a: Name const name = await ctx.ui.input("Agent name (filename, no spaces)", ""); if (!name) return; // Step 3b: Description const description = await ctx.ui.input("Description (one line)", ""); if (!description) return; // Step 3c: Tools const selectedTools = await showToolPicker(ctx); if (selectedTools === null) return; const toolsField = selectedTools === undefined ? "all" : selectedTools.join(", "); // Step 3d: Model const model = await showModelPicker(ctx); if (model === undefined) return; // Step 3e: Thinking level const thinking = await ctx.ui.select("Thinking level", [ "inherit (parent default)", "off", "minimal", "low", "medium", "high", "xhigh", ]); if (!thinking) return; const thinkingValue = thinking.startsWith("inherit") ? "" : thinking.split(" ")[0]; // Step 3f: System prompt const systemPrompt = await ctx.ui.editor( `System prompt for ${name}`, `You are a specialized agent for ${description}`, ); if (systemPrompt === undefined) return; // Step 4: Write file mkdirSync(targetDir, { recursive: true }); const targetPath = join(targetDir, `${name}.md`); if (existsSync(targetPath)) { const overwrite = await ctx.ui.confirm("Overwrite", `${targetPath} already exists. Overwrite?`); if (!overwrite) return; } const fmFields: string[] = [ `description: ${description}`, ]; if (toolsField !== "all") { fmFields.push(`tools: ${toolsField}`); } if (model && model !== "inherit") { fmFields.push(`model: ${model}`); } if (thinkingValue) { fmFields.push(`thinking: ${thinkingValue}`); } const content = `---\n${fmFields.join("\n")}\n---\n\n${systemPrompt}\n`; writeFileSync(targetPath, content, "utf-8"); reloadAgents(); ctx.ui.notify(`Created ${targetPath}`, "info"); }