I second pseudo-terminals. I find the System.Posix.Terminal to be very usable (though I've admittedly never tried System.Posix.Pty). I occasionally dabble with "bots" for terminal-based games (e.g., Nethack), and have used PTYs with some deal of success.
I've included some code from a Nethack bot attempt from several years ago. Hopefully you'll find it useful. As a quick usage/rationale overview, I feed an attoparsec parser by repeatedly calling receive (intermixed with some intelligent use of `transmit` to deal with messages and other incomplete screen updates) until I receive a valid map. `hGetNonBlocking` bypasses the standard buffering mechanisms and returns whatever is in the PTY buffer (which may be nothing) rather than waiting for the specified line/block/whatever buffer to fill. `stop` and `start` should be obvious in their purpose, if not their implementation.
data Local = Local { pty :: Handle }
class Connection a where
transmit :: a -> ByteString -> IO ()
receive :: a -> IO ByteString
stop :: a -> IO ()
start :: IO a
instance Connection Local where
transmit l s = B.hPut (pty l) s
receive l = B.hGetNonBlocking (pty l) 4096
stop l = hClose $ pty l
start = do
(fd1, fd2) <- openPseudoTerminal
(hPty) <- fdToHandle fd1
slave <- fdToHandle fd2
_<- createProcess (proc "sh" ["-c", "/usr/games/bin/nethack"]){ std_in = (UseHandle slave), std_out = (UseHandle slave) }
return $ Local hPty