diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,11 +1,30 @@
 # Elocrypt
 [![Build Status](https://travis-ci.org/sgillespie/elocrypt.svg?branch=master)](https://travis-ci.org/sgillespie/elocrypt)
 
-Elocrypt is a Haskell library that generates pronounceable, hard-to-guess passwords.. as hard as Vince Carter's knee cartilage is. Elocrypt includes a library and program.
+Elocrypt generates pronounceable, easy-to-remember, hard-to-guess passwords... as hard as Vince Carter's knee cartilage is. Elocrypt includes a Haskell library and program.
 
-## Downloading
-Elocrypt sources can be found @ https://github.com/sgillespie/elocrypt
+## Prerequisites
+In order to build or install you will need
+ * GHC (tested on 7.10 and 7.8)
+ * cabal-install (tested on 1.20 and 1.22)
 
+## Installing
+Elocrypt is on [Hackage](https://hackage.haskell.org/package/elocrypt).  Installation is as easy as:
+```
+cabal install elocrypt
+```
+
+## Running
+Running elocrypt is as simple as:
+```
+elocrypt [length]
+```
+
+## Obtaining the source
+Elocrypt sources can be found 
+ * https://github.com/sgillespie/elocrypt
+ * https://hackage.haskell.org/package/elocrypt
+
 ## Building
 In order to build or install you will need
  * GHC (tested on 7.10 and 7.8)
@@ -21,20 +40,14 @@
 cabal install
 ```
 
-## Running
-Running elocrypt is as simple as...
-```
-elocrypt [length]
-```
-
 ## API Documentation
-The full API documentation is generated by haddock
+The full API documentation is on hackage @ https://hackage.haskell.org/package/elocrypt/docs. To build the documentation yourself, run
 ```
 cabal haddock
 ```
 
 ### API Examples
-You can use elocrypt to generate words in any Haskell code, so long as you have installed elocrypt. Generate a word by using Elocrypt.Password.generate
+You can use elocrypt to generate words in any Haskell code, so long as you have installed elocrypt. Generate a word by using Data.Elocrypt.newPassword
 ```
 import Data.Elocrypt
 ...
@@ -43,7 +56,7 @@
 fooGen = newPassword 10 `liftM` getStdGen
 ```
 
-Alternatively, you can use Elocrypt.Password.mkPassword if you want to complete control of the random monad
+Alternatively, you can use Data.Elocrypt.mkPassword if you want to complete control of the random monad
 ```
 import Data.Elocrypt
 import Control.Monad.Random
diff --git a/cli-test/Tests.hs b/cli-test/Tests.hs
new file mode 100644
--- /dev/null
+++ b/cli-test/Tests.hs
@@ -0,0 +1,112 @@
+{-# LANGUAGE TemplateHaskell #-}
+module Main where
+
+import Data.Functor
+import Data.List
+import Data.Maybe
+import Control.Monad
+import Text.Printf
+
+import Test.Proctest
+import Test.Proctest.Assertions
+import Test.QuickCheck
+import Test.QuickCheck.Property
+import Test.Tasty hiding (Timeout)
+import Test.Tasty.QuickCheck (QuickCheckTests(..), testProperty)
+import Test.Tasty.TH
+
+import Test.Elocrypt.Instances
+
+main :: IO ()
+main = defaultMain (options tests)
+
+options :: TestTree -> TestTree
+options = localOption (QuickCheckTests 10)
+
+tests :: TestTree
+tests = testGroup "CLI Tests" [testGroup']
+
+testGroup' = $(testGroupGenerator)
+
+newtype CliArgs = CliArgs  { getArgs :: [String] }
+               deriving Eq
+
+instance Show CliArgs where
+  show = concat . intersperse " " . getArgs
+
+instance Arbitrary CliArgs where
+  arbitrary = do
+    len  <- arbitrary `suchThat` (>2) `suchThat` (<=40) :: Gen Int
+    num  <- arbitrary `suchThat` (>2) `suchThat` (<=20) :: Gen Int
+    args <- sublistOf ["-n %d" `printf` num,
+                       show len]
+    return (CliArgs args)
+              
+prop_shouldPrintPasswordsWithLength :: CliArgs -> Property
+prop_shouldPrintPasswordsWithLength (CliArgs args)
+  = isJust (getPosParam args) ==>
+    ioProperty $ do
+      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
+      response <- readHandle out'
+
+      let len    = read . fromJust . getPosParam $ args
+          words' = words response
+
+      return (all ((==) len . length) words')
+
+prop_shouldPrintNumberPasswords :: CliArgs -> Property
+prop_shouldPrintNumberPasswords (CliArgs args)
+  = isJust (getArg "-n" args) ==>
+    ioProperty $ do
+      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
+      response <- readHandle out'
+
+      let number = read . fromJust . getArg "-n" $ args
+          words' = words response
+          
+      return (number == length words')
+
+prop_shouldPrintMultPasswordsPerLine :: CliArgs -> Property
+prop_shouldPrintMultPasswordsPerLine (CliArgs args)
+  = (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>
+    ioProperty $ do
+      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
+      response <- readHandle out'
+      return . all (>1) . tail . reverse . map length . map words . lines $ response
+
+prop_shouldPrintDefaultMultPasswordsPerLine :: CliArgs -> Property
+prop_shouldPrintDefaultMultPasswordsPerLine (CliArgs args)
+  = isNothing (getArg "-n" args) ==>
+    (read . fromMaybe "8" . getPosParam $ args) <= 38 ==>
+    ioProperty $ do
+      (in', out', err', p) <- run' "dist/build/elocrypt/elocrypt" args
+      response <- readHandle out'
+      return . all (>1) . tail . reverse . map length . map words . lines $ response
+
+-- Utility functions
+run' :: FilePath -> [String] -> IO (Handle, Handle, Handle, ProcessHandle)
+run' exe args = do
+  res@(in', out', err', p) <- run exe args
+  sleep'
+  assertExitedSuccess (seconds 2) p
+  return res
+
+readHandle :: Handle -> IO String
+readHandle = (<$>) asUtf8Str . waitOutput (seconds 2) 5000
+
+assertExitedSuccess :: Timeout -> ProcessHandle -> IO Bool
+assertExitedSuccess t = liftM (== ExitSuccess) . assertExitedTimeout t
+
+assertExitedFailure :: Timeout -> ProcessHandle -> IO Bool
+assertExitedFailure t = liftM not . assertExitedSuccess t
+
+sleep' :: IO ()
+sleep' = sleep (seconds 0.0001)
+
+getArg :: String -> [String] -> Maybe String
+getArg prefix args = (tail . dropWhile (not . elem')) `liftM` arg
+  where arg = find (isPrefixOf prefix) args
+        elem' = (flip elem) [' ', '=']
+
+getPosParam :: [String] -> Maybe String
+getPosParam = find $ (/= '-') . head
diff --git a/elocrypt.cabal b/elocrypt.cabal
--- a/elocrypt.cabal
+++ b/elocrypt.cabal
@@ -1,5 +1,5 @@
 name:                elocrypt
-version:             0.3.2
+version:             0.4.0
 synopsis:            Generate easy-to-remember, hard-to-guess passwords
 homepage:            https://www.github.com/sgillespie/elocrypt
 license:             OtherLicense
@@ -26,7 +26,7 @@
 source-repository this
   type: git
   location: https://github.com/sgillespie/elocrypt.git
-  tag: v0.3.2
+  tag: v0.4.0
                   
 library
   exposed-modules:     Data.Elocrypt,
@@ -58,5 +58,21 @@
                        tasty-th
   default-language:    Haskell2010
   hs-source-dirs:      test
+  main-is:             Tests.hs
+  type:                exitcode-stdio-1.0
+
+test-suite ui-test
+  build-depends:       base >= 4.7 && <4.9,
+                       elocrypt,
+                       proctest,
+                       MonadRandom,
+                       QuickCheck,
+                       random,
+                       tasty,
+                       tasty-quickcheck,
+                       tasty-th                      
+  default-language:    Haskell2010
+  hs-source-dirs:      cli-test,
+                       test
   main-is:             Tests.hs
   type:                exitcode-stdio-1.0
diff --git a/src/cli/Main.hs b/src/cli/Main.hs
--- a/src/cli/Main.hs
+++ b/src/cli/Main.hs
@@ -2,6 +2,7 @@
 
 import Control.Monad
 import Data.List
+import Data.Maybe (fromMaybe)
 import System.Console.GetOpt
 import System.Environment
 import System.Exit
@@ -10,7 +11,9 @@
 
 import Data.Elocrypt
 
-version = "elocrypt 0.3.2"
+version = "elocrypt 0.4.0"
+termLen = 80
+termHeight = 10
 
 main :: IO ()
 main = do
@@ -18,41 +21,42 @@
   opts <- elocryptOpts args
   gen  <- getStdGen
 
-  -- TODO: Refactor me
-  if (optHelp opts) then do
+  when (optHelp opts) $ do
     hPutStrLn stderr usage
     exitSuccess
-  else return ()
-
-  -- TODO: Refactor me
-  if (optVersion opts) then do
+  when (optVersion opts) $ do
     hPutStrLn stderr version
     exitSuccess
-  else return ()
 
   putStrLn (passwords opts gen)
 
 passwords :: RandomGen g => Options -> g -> String
-passwords opts gen = concat . intersperse "\n" . map (concat . intersperse "\t") . group $ ps
-  where ps = take (optNumber opts) . newPasswords (optLength opts) $ gen
-        group (p:ps:pss:psss) = [p, ps, pss]:(group psss)
-        group ps = [ps]
+passwords opts gen = format . group' cols $ ps
+  where ps = take num . newPasswords (optLength opts) $ gen
+        format = concat . intersperse "\n" . map (concat . intersperse "  ")
+        cols = termLen `div` (optLength opts + 2)
+        num = fromMaybe (cols * termHeight) (optNumber opts)
 
+group' :: Int -> [a] -> [[a]]
+group' _ [] = []
+group' i ls = g:(group' i ls')
+  where (g, ls') = splitAt i ls
+
 data Options
-  = Options { optLength  :: Int,    -- Size of the password(s)
-              optNumber  :: Int,    -- Number of passwords to generate
+  = Options { optLength  :: Int,        -- Size of the password(s)
+              optNumber  :: Maybe Int,  -- Number of passwords to generate
               optHelp    :: Bool,
               optVersion :: Bool }
   deriving (Show)
 
 defaultOptions :: Options
 defaultOptions = Options { optLength = 8,
-                           optNumber = 42,
+                           optNumber = Nothing,
                            optHelp = False,
                            optVersion = False }
 
 options :: [OptDescr (Options -> Options)]
-options = [Option ['n'] ["number"] (ReqArg (\n o -> o {optNumber = read n}) "NUMBER") "The number of passwords to generate (default: 42)",
+options = [Option ['n'] ["number"] (ReqArg (\n o -> o {optNumber = Just (read n)}) "NUMBER") "The number of passwords to generate",
            Option ['h'] ["help"] (NoArg (\o -> o { optHelp = True })) "Show this help",
            Option ['v'] ["version"] (NoArg (\o -> o { optVersion = True })) "Show version and exit"]
 
diff --git a/src/lib/Data/Elocrypt.hs b/src/lib/Data/Elocrypt.hs
--- a/src/lib/Data/Elocrypt.hs
+++ b/src/lib/Data/Elocrypt.hs
@@ -85,7 +85,8 @@
 mkPassword :: MonadRandom m
               => Int -- ^ password length
               -> m String
-mkPassword len = reverse `liftM` first2 >>= (flip lastN) (len - 2) >>= return . reverse
+mkPassword len = first2 >>= reverse' >>= lastN (len - 2) >>= reverse'
+  where reverse' = return . reverse
 
 -- |Plural version of mkPassword.  Generate an infinite list of passwords using
 --  the MonadRandom m.  MonadRandom is exposed here for extra control.
@@ -106,9 +107,11 @@
 
 -- * Internal
 
--- |Generate two random characters
+-- |Generate two random characters. Uses 'Elocrypt.Trigraph.trigragh'
+--  to generate a weighted list.
 first2 :: MonadRandom m => m String
-first2 = sequence . take 2 . repeat . uniform $ alphabet
+first2 = fromList (map toWeight frequencies)
+  where toWeight (s, w) = (s, sum w)
 
 -- |Generate a random character based on the previous two characters and
 --  their 'Elocrypt.Trigraph.trigraph'
@@ -117,6 +120,6 @@
 
 -- |Generate the last n characters using previous two characters
 --  and their 'Elocrypt.Trigraph.trigraph'
-lastN :: MonadRandom m => String -> Int -> m String
-lastN ls 0 = return ls
-lastN ls len = next ls >>= (flip lastN) (len - 1) . (flip (:)) ls
+lastN :: MonadRandom m => Int -> String -> m String
+lastN 0 ls   = return ls
+lastN len ls = next ls >>= lastN (len - 1) . (:ls)
diff --git a/test/Test/Elocrypt/Instances.hs b/test/Test/Elocrypt/Instances.hs
--- a/test/Test/Elocrypt/Instances.hs
+++ b/test/Test/Elocrypt/Instances.hs
@@ -13,7 +13,7 @@
 instance Arbitrary AlphaChar where
   arbitrary = Alpha `liftM` elements ['a'..'z']
 
-newtype GreaterThan2 a = GT2 { greaterThan2 :: a }
+newtype GreaterThan2 a = GT2 { getGT2 :: a }
                        deriving (Eq, Ord, Show)
 
 instance (Num a, Ord a, Arbitrary a) => Arbitrary (GreaterThan2 a) where
diff --git a/test/Test/ElocryptTest.hs b/test/Test/ElocryptTest.hs
--- a/test/Test/ElocryptTest.hs
+++ b/test/Test/ElocryptTest.hs
@@ -48,7 +48,7 @@
 prop_lastNShouldSkip0Weights (Alpha c1) (Alpha c2) len gen
   = len > 0 ==> lastNShouldSkip0Weights' lastN'
   where f2 = [c1, c2]
-        lastN' = evalRand (lastN (reverse [c1, c2]) len) gen
+        lastN' = evalRand (lastN len [c2, c1]) gen
 
 lastNShouldSkip0Weights' :: String -> Property
 lastNShouldSkip0Weights' (p:ps:pss:psss) = isCandidate p [pss, ps] .&&.
