import { parametersSchema as z, defineCustomTool, CustomToolContext } from "@roo-code/types" // @ts-ignore - Node built-ins import { spawnSync } from "child_process" export default defineCustomTool({ name: "brew_leaves", description: "List Homebrew packages installed on request (not pulled in as dependencies). Optionally includes installed versions. Answers 'what did I install myself?'", parameters: z.object({ withVersions: z.boolean().optional().describe("Include installed versions for each leaf package (default: true)"), }), async execute({ withVersions = true }, context: CustomToolContext) { try { const leavesResult = spawnSync("brew", ["leaves", "-r"], { encoding: "utf-8", timeout: 15_000, }) if (leavesResult.error) { return JSON.stringify({ error: `brew leaves failed: ${leavesResult.error.message}` }, null, 2) } const leavesList = (leavesResult.stdout || "") .split("\n") .map((l: string) => l.trim()) .filter((l: string) => l.length > 0) if (!withVersions) { return JSON.stringify({ count: leavesList.length, leaves: leavesList.map((name: string) => ({ name })), }, null, 2) } // Get versions in batch — brew list --versions for all leaves const versResult = spawnSync("brew", ["list", "--versions", ...leavesList], { encoding: "utf-8", timeout: 30_000, }) const versionMap: Record = {} if (versResult.stdout) { for (const line of versResult.stdout.split("\n")) { const parts = line.trim().split(/\s+/) if (parts.length >= 2) { versionMap[parts[0]] = parts.slice(1).join(", ") } } } const leaves = leavesList.map((name: string) => ({ name, version: versionMap[name] || "unknown", })) return JSON.stringify({ count: leaves.length, leaves }, null, 2) } catch (err: any) { return JSON.stringify({ error: err.message ?? String(err) }, null, 2) } }, })