#!/usr/bin/env python3 import json import os import pty import select import sys def emit(event_type, **payload): print(json.dumps({"type": event_type, **payload}), flush=True) def handle_message(pid, fd, raw_line): try: message = json.loads(raw_line.decode("utf-8", "replace")) except json.JSONDecodeError: return if message.get("type") == "input": os.write(fd, str(message.get("data", "")).encode("utf-8", "replace")) elif message.get("type") == "signal": os.kill(pid, int(message.get("signal", 15))) def main(): if "--" not in sys.argv: emit("exit", code=2, signaled=False) return 2 command = sys.argv[sys.argv.index("--") + 1 :] if not command: emit("exit", code=2, signaled=False) return 2 pid, fd = pty.fork() if pid == 0: os.execvp(command[0], command) emit("start", pid=pid) stdin_fd = sys.stdin.fileno() open_fds = [fd, stdin_fd] stdin_buffer = b"" while open_fds: readable, _, _ = select.select(open_fds, [], [], 0.2) if fd in readable: try: data = os.read(fd, 4096) except OSError: data = b"" if data: emit("out", data=data.decode("utf-8", "replace")) else: open_fds.remove(fd) if stdin_fd in readable: try: chunk = os.read(stdin_fd, 4096) except OSError: chunk = b"" if not chunk: open_fds.remove(stdin_fd) continue stdin_buffer += chunk while b"\n" in stdin_buffer: line, stdin_buffer = stdin_buffer.split(b"\n", 1) handle_message(pid, fd, line) try: finished_pid, status = os.waitpid(pid, os.WNOHANG) except ChildProcessError: break if finished_pid == pid: if os.WIFSIGNALED(status): emit("exit", code=os.WTERMSIG(status), signaled=True) return 128 + os.WTERMSIG(status) code = os.WEXITSTATUS(status) emit("exit", code=code, signaled=False) return code return 0 if __name__ == "__main__": raise SystemExit(main())