import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { dirname, join } from "node:path"; import { getAgentDir, parseFrontmatter } from "@mariozechner/pi-coding-agent"; export interface SubagentPreset { name: string; description: string; model?: string; tools?: string[]; systemPrompt: string; source: "global" | "project"; filePath: string; } export interface SubagentPresetDiscoveryResult { presets: SubagentPreset[]; projectPresetsDir: string | null; } function findNearestProjectPresetsDir(cwd: string): string | null { let current = cwd; while (true) { const candidate = join(current, ".pi", "subagents"); try { if (statSync(candidate).isDirectory()) return candidate; } catch (_err) { // ignore } const parent = dirname(current); if (parent === current) return null; current = parent; } } function loadPresetDir(dir: string, source: "global" | "project"): SubagentPreset[] { if (!existsSync(dir)) return []; const entries = readdirSync(dir, { withFileTypes: true }); const presets: SubagentPreset[] = []; for (const entry of entries) { if (!entry.name.endsWith(".md")) continue; if (!entry.isFile() && !entry.isSymbolicLink()) continue; const filePath = join(dir, entry.name); let content: string; try { content = readFileSync(filePath, "utf8"); } catch (_err) { continue; } const parsed = parseFrontmatter>(content); const frontmatter = parsed.frontmatter ?? {}; const body = parsed.body ?? ""; if (typeof frontmatter.name !== "string") continue; if (typeof frontmatter.description !== "string") continue; const tools = typeof frontmatter.tools === "string" ? frontmatter.tools.split(",").map((t) => t.trim()).filter(Boolean) : undefined; presets.push({ name: frontmatter.name, description: frontmatter.description, model: typeof frontmatter.model === "string" ? frontmatter.model : undefined, tools: tools && tools.length > 0 ? tools : undefined, systemPrompt: body.trim(), source, filePath, }); } return presets; } export function discoverSubagentPresets(cwd: string, options: { homeDir?: string } = {}): SubagentPresetDiscoveryResult { const globalAgentDir = options.homeDir ? join(options.homeDir, ".pi", "agent") : getAgentDir(); const globalDir = join(globalAgentDir, "subagents"); const projectPresetsDir = findNearestProjectPresetsDir(cwd); const map = new Map(); for (const preset of loadPresetDir(globalDir, "global")) { map.set(preset.name, preset); } if (projectPresetsDir) { for (const preset of loadPresetDir(projectPresetsDir, "project")) { // project overrides global by name map.set(preset.name, preset); } } return { presets: Array.from(map.values()), projectPresetsDir }; }