import asyncio, uuid, time from pathlib import Path from starlette.responses import JSONResponse from app.config import CLI_BIN def error(status_code:int, detail:str) -> JSONResponse: return JSONResponse({"detail": detail}, status_code=status_code) async def execute_command(args:list[str], cwd:Path, stdin:str|None, timeout:float) -> JSONResponse: command_id = str(uuid.uuid4()) command = [CLI_BIN, *args] started = time.perf_counter() try: process = await asyncio.create_subprocess_exec( *command, cwd=str(cwd), stdin=asyncio.subprocess.PIPE if stdin is not None else None, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) except FileNotFoundError: return error(500, f"configured CLI binary was not found: {CLI_BIN}") try: stdout_b, stderr_b = await asyncio.wait_for( process.communicate(None if stdin is None else stdin.encode()), timeout=timeout, ) timed_out = False except asyncio.TimeoutError: process.kill() stdout_b, stderr_b = await process.communicate() timed_out = True duration_ms = int((time.perf_counter() - started) * 1000) stdout = stdout_b.decode(errors="replace").rstrip() stderr = stderr_b.decode(errors="replace").rstrip() exit_code = process.returncode if process.returncode is not None else -1 if timed_out: exit_code = 124 stderr = (stderr + f"\nCommand timed out after {timeout}s").strip() return JSONResponse( { "id": command_id, "command": command, "cwd": str(cwd), "exit_code": exit_code, "stdout": stdout, "stderr": stderr, "duration_ms": duration_ms, "timed_out": timed_out, } )