import { FastifyInstance, FastifyRequest } from 'fastify'; import { spawn } from 'child_process'; import { getCommand } from '../whitelist.js'; interface ExecBody { command_key: string; args?: string[]; } export async function execRoutes(app: FastifyInstance): Promise { app.post('/agent/v1/exec', async (req: FastifyRequest<{ Body: ExecBody }>, reply) => { const { command_key, args = [] } = req.body; const def = getCommand(command_key); if (!def) { return reply .status(403) .send({ error: `command_key '${command_key}' is not in the whitelist` }); } reply.raw.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); const sendEvent = (event: string, data: string) => { reply.raw.write(`event: ${event}\ndata: ${JSON.stringify({ data })}\n\n`); }; const [bin, ...staticArgs] = def.exec.split(' '); const child = spawn(bin, [...staticArgs, ...args], { shell: false }); child.stdout.on('data', (chunk: Buffer) => { sendEvent('stdout', chunk.toString()); }); child.stderr.on('data', (chunk: Buffer) => { sendEvent('stderr', chunk.toString()); }); child.on('close', (code) => { reply.raw.write(`event: exit\ndata: ${JSON.stringify({ code })}\n\n`); reply.raw.end(); }); child.on('error', (err) => { reply.raw.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`); reply.raw.end(); }); req.raw.on('close', () => { child.kill(); }); }); }