diff --git a/Network/Yogurt/Ansi.hs b/Network/Yogurt/Ansi.hs
new file mode 100644
--- /dev/null
+++ b/Network/Yogurt/Ansi.hs
@@ -0,0 +1,13 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+module Network.Yogurt.Ansi (rmAnsi) where
+
+-- Removes ANSI sequences from a string.
+rmAnsi :: String -> String
+rmAnsi [] = []
+rmAnsi ab = a ++ (rmAnsi . tail' . dropWhile (/= 'm')) b
+  where (a, b) = break (== '\ESC') ab
+
+tail' :: [a] -> [a]
+tail' [] = []
+tail' xs = tail xs
diff --git a/Network/Yogurt/IO.hs b/Network/Yogurt/IO.hs
new file mode 100644
--- /dev/null
+++ b/Network/Yogurt/IO.hs
@@ -0,0 +1,63 @@
+{-# OPTIONS_HADDOCK hide #-}
+
+module Network.Yogurt.IO
+  ( writeToTTY, splitAtPrompt
+  , maybeInput
+  , hGetImpatientLine
+  ) where
+
+import System.IO
+import System.Console.Readline
+import Control.Monad (when)
+import Data.List (elemIndices)
+
+writeToTTY :: String -> IO ()
+writeToTTY msg = do
+  -- Find out which part of the message is the prompt.
+  let (prePrompt, prompt) = splitAtPrompt msg
+
+  -- Empty buffer.
+  buf <- getLineBuffer
+  pt  <- getPoint
+  setLineBuffer ""
+  setPoint 0  -- AAARGH
+  redisplay
+
+  -- Print prePrompt.
+  when (prePrompt /= "") $ do
+    putStr prePrompt
+    onNewLine
+
+  -- Set prompt as new message; put buffer and point back.
+  message prompt
+  insertText buf
+  setPoint pt
+  redisplay
+
+-- Splits a message x in two submessages y and z such that:
+-- * x == y ++ z
+-- * all (/= '\n') z
+-- * null y || last y == '\n'  (i.e. z is maximal)
+splitAtPrompt :: String -> (String, String)
+splitAtPrompt cs = case elemIndices '\n' cs of
+  [] -> ("", cs)
+  is -> splitAt (last is + 1) cs
+
+-- Takes an input method and catches errors, returning results in the Maybe monad.
+maybeInput :: IO String -> IO (Maybe String)
+maybeInput input = fmap Just input `catch` const (return Nothing)
+
+-- Waits for input, but once the first character is read, waits
+-- no longer than the specified number of ms before giving up.
+hGetImpatientLine :: Handle -> Int -> IO String
+hGetImpatientLine h patience = rec where
+  rec = do
+    c <- hGetChar h
+    if c == '\n'
+      then return [c]
+      else do
+        b <- hWaitForInput h patience
+        if b
+          then rec >>= return . (c:)
+          else return [c]
+
diff --git a/Yogurt.cabal b/Yogurt.cabal
--- a/Yogurt.cabal
+++ b/Yogurt.cabal
@@ -1,17 +1,20 @@
 Name:           Yogurt
-Version:        0.1
-Cabal-Version:  >= 1.2
-License:        BSD3
-License-file:   LICENSE
+Version:        0.2
+Synopsis:       A MUD client library
+Description:    A MUD client library for Haskell. Features prioritized, regex-based hooks, variables and timers.
+
 Author:         Martijn van Steenbergen
 Maintainer:     martijn@van.steenbergen.nl
-Copyright:	Copyright (c) 2008 Martijn van Steenbergen
-Description:    A MUD client library for Haskell. Features prioritized, regex-based hooks, variables and timers.
-Synopsis:	A MUD client library
+Copyright:      Copyright (c) 2008 Martijn van Steenbergen
 Homepage:       http://martijn.van.steenbergen.nl/projects/yogurt/
+
+Cabal-Version:  >= 1.2
+License:        BSD3
+License-file:   LICENSE
 Category:       Network
 Build-type:     Simple
 
 Library
   Build-Depends:    base, mtl, regex-posix, containers, time, old-locale, process, readline, network
-  Exposed-modules:  Network.Yogurt, Network.Yogurt.Mud, Network.Yogurt.Utils, Network.Yogurt.Engine
+  Exposed-Modules:  Network.Yogurt, Network.Yogurt.Mud, Network.Yogurt.Utils, Network.Yogurt.Engine
+  Other-Modules:    Network.Yogurt.Ansi, Network.Yogurt.IO
