/dev/null'); return self::$sttySupported = \is_string($stty) && \trim($stty) !== ''; } /** * Check whether a stream is a TTY. * * Falls back gracefully when stream_isatty and posix_isatty are * unavailable, using fstat to check for a character device. * * Returns false when detection is uncertain. * * @param resource|int $stream */ public static function isatty($stream): bool { if (\function_exists('stream_isatty')) { return @\stream_isatty($stream); } if (\function_exists('posix_isatty')) { return @\posix_isatty($stream); } // Fallback: check fstat mode for character device (TTY = 0020000) $stat = @\fstat($stream); if (!\is_array($stat) || !isset($stat['mode'])) { return false; } return ($stat['mode'] & 0170000) === 0020000; } /** * Get the current terminal width in columns. */ public static function getWidth(int $default = self::DEFAULT_WIDTH): int { if (self::supportsStty() && \defined('STDOUT') && self::isatty(\STDOUT)) { // Output format: "rows cols" $size = @\shell_exec('stty size /dev/null'); if ($size && \preg_match('/^\d+ (\d+)$/', \trim($size), $matches)) { return (int) $matches[1]; } $width = @\shell_exec('tput cols /dev/null'); if ($width && \is_numeric(\trim($width))) { return (int) \trim($width); } } // Check COLUMNS environment variable (may be stale after resize) $width = \getenv('COLUMNS'); if ($width && \is_numeric(\trim($width))) { return (int) \trim($width); } return $default; } }