*/ private array $pipes = []; public function __construct( private string $command, ) {} public function boot(): void { $spec = [ 0 => ['pipe', 'r'], 1 => ['pty'], 2 => ['pty'], ]; $process = proc_open($this->command, $spec, $pipes, base_path()); if (! is_resource($process)) { throw new \RuntimeException('Unable to start local PTY shell.'); } $this->process = $process; $this->pipes = $pipes; foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, false); } } public function read(): string { if (! $this->isAlive()) { return ''; } $output = ''; $stream = $this->outputStream(); if (! $stream) { return ''; } $read = [$stream]; $write = null; $except = null; $available = @stream_select($read, $write, $except, 0, 200000); if ($available === false || $available === 0) { return ''; } $chunk = @stream_get_contents($stream); if ($chunk !== false && $chunk !== '') { $output .= $chunk; } return $output; } public function write(string $input): void { $stream = $this->inputStream(); if ($input === '' || ! $stream) { return; } @fwrite($stream, $input); @fflush($stream); } public function resize(int $cols, int $rows): void { // proc_open PTYs do not expose a portable runtime resize API. } public function isAlive(): bool { return is_resource($this->process) && (proc_get_status($this->process)['running'] ?? false); } public function close(): void { $input = $this->inputStream(); if ($input) { @fwrite($input, "exit\n"); @fflush($input); } foreach ($this->pipes as $pipe) { if (is_resource($pipe)) { @fclose($pipe); } } $this->pipes = []; if (is_resource($this->process)) { @proc_terminate($this->process); @proc_close($this->process); } $this->process = null; } /** @return resource|null */ private function inputStream() { return isset($this->pipes[0]) && is_resource($this->pipes[0]) ? $this->pipes[0] : null; } /** @return resource|null */ private function outputStream() { if (isset($this->pipes[1]) && is_resource($this->pipes[1])) { return $this->pipes[1]; } if (isset($this->pipes[2]) && is_resource($this->pipes[2])) { return $this->pipes[2]; } return isset($this->pipes[0]) && is_resource($this->pipes[0]) ? $this->pipes[0] : null; } }