// ─── Shell Tool ───────────────────────────────────────────────────────────── import { z } from 'zod'; import { exec } from 'child_process'; import { promisify } from 'util'; import type { ToolDef } from '../../core/tools/index.js'; const execAsync = promisify(exec); export const shellExecTool: ToolDef< { command: string; timeout?: number }, { stdout: string; stderr: string; exitCode: number } > = { id: 'shell.exec', name: 'shell_exec', description: 'Execute a shell command and return stdout, stderr, and exit code. Use for running builds, tests, git commands, or any CLI tool.', inputSchema: z.object({ command: z.string().describe('Shell command to execute'), timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)'), }), outputSchema: z.object({ stdout: z.string(), stderr: z.string(), exitCode: z.number(), }), permission: 'exec', sideEffect: 'process', timeout: 60000, retries: 0, tags: ['shell', 'exec'], renderer: { icon: '⚡', color: 'green' }, async execute(input, ctx) { ctx.emit(`Executing: ${input.command}`); const timeout = input.timeout ?? 30000; try { const { stdout, stderr } = await execAsync(input.command, { cwd: ctx.workDir, timeout, maxBuffer: 1024 * 1024 * 10, // 10MB signal: ctx.signal, }); return { stdout: stdout.slice(0, 50000), stderr: stderr.slice(0, 10000), exitCode: 0 }; } catch (err: any) { return { stdout: (err.stdout ?? '').slice(0, 50000), stderr: (err.stderr ?? err.message ?? '').slice(0, 10000), exitCode: err.code ?? 1, }; } }, };