diff --git a/Monadoro.cabal b/Monadoro.cabal
--- a/Monadoro.cabal
+++ b/Monadoro.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           Monadoro
-version:        0.3.0.0
+version:        0.3.1.0
 synopsis:       A minimalistic CLI Pomodoro timer.
 description:    A Pomodoro timer with two modes: single-pomodoro (default), and four-pomodoro (`--session`).
 category:       Tools
@@ -26,6 +26,7 @@
 
 library
   exposed-modules:
+      CLI
       ConsoleDisplay
       CountdownLoop
       ParseTime
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,132 +1,22 @@
 module Main where
 
-import Control.Monad      (void)
-import Data.Char          (isDigit)
-import Data.List          (intersect)
-import Data.Version       (showVersion)
 import System.Environment (getArgs)
-import System.Exit        (ExitCode(ExitSuccess))
-import Control.Concurrent (threadDelay)
-import System.Process     (system)
+import CLI                (containsEither,
+                           filterParams,
+                           printVersion,
+                           runPomodoros,
+                           runSession,
+                           showManual,
+                           showUsage)
 
-import CountdownLoop      (countdown_loop)
-import Pomodoro           (session)
 
-import Paths_Monadoro     (getDataFileName, version)
-
 main :: IO ()
-main = monadoro
-
-monadoro :: IO ()
-monadoro = getArgs >>= parseArgs
-
-remove :: String -> [String] -> [String]
-remove element list = filter (/=element) list
+main = getArgs >>= parseArgs
 
 parseArgs :: [String] -> IO ()
 parseArgs xs
-  | xs `containsEither` ["--help", "-h"]    = putStrLn usage
-  | xs `containsEither` ["--version", "-v"] = putStrLn . showVersion $ version
-  | xs `containsEither` ["--session", "-s"] = session (getDelayIfNeeded xs)
-  | xs `containsEither` ["--man", "-m"] = do
-    man_file <- getDataFileName "man/monadoro.1"
-    ExitSuccess <- system $ "man " ++ man_file
-    return ()
-  | otherwise = do
-    let filtered_params = filterParams xs
-    runPomodoro (getDelayIfNeeded xs) filtered_params
-
-filterParams :: [String] -> [String]
-filterParams xs = (remove "-n" . remove "--nodelay") xs
-
-runPomodoro :: IO () -> [String] -> IO ()
-runPomodoro delay xs = do
-    warnAboutErrorsIfAny invalidIntervals
-    runTimer delay validIntervals
-    where (validIntervals, invalidIntervals) = checkInput xs
-
-with_delay :: IO()
-with_delay = wait 1
-
--- Mocks a delay for test purposes.
-no_delay :: IO()
-no_delay = wait 0
-
-getDelayIfNeeded :: [String] -> IO ()
-getDelayIfNeeded xs
-  | xs `containsEither` ["--nodelay", "-n"] = no_delay
-  | otherwise = with_delay
-
-warnAboutErrorsIfAny :: [String] -> IO ()
-warnAboutErrorsIfAny [] = return ()
-warnAboutErrorsIfAny errors =
-  putStrLn $ "Unable to parse as interval: " ++ show errors
-
-{-| checkInput
-
-Returns valid entries (first list) and invalid entries (second list).
-
->>> checkInput []
-([],[])
-
->>> checkInput ["x"]
-([],["x"])
-
->>> checkInput ["00:00"]
-(["00:00"],[])
-
->>> checkInput ["00:00", "x"]
-(["00:00"],["x"])
-
->>> checkInput ["00:00", "x"]
-(["00:00"],["x"])
-
->>> checkInput ["00:00", "00:01", "x"]
-(["00:00","00:01"],["x"])
-
--}
-
-checkInput :: [String] -> ([String], [String])
-checkInput [] = ([], [])
-checkInput [x]
-  | isValidTimeInterval x = ([x], [])
-  | otherwise = ([], [x])
-checkInput (x:xs) = (validEntries, invalidEntries)
-  where
-    validEntries =
-      fst (checkInput [x]) ++ fst (checkInput xs)
-    invalidEntries =
-      snd (checkInput [x]) ++ snd (checkInput xs)
-
-{-| isValidTimeInterval
-
->>> isValidTimeInterval "x"
-False
-
->>> isValidTimeInterval "00:00"
-True
-
--}
-
-isValidTimeInterval :: String -> Bool
-isValidTimeInterval (m1:m2:':':s1:[s2])
-  | all isDigit [m1, m2, s1, s2] = True
-isValidTimeInterval _ = False
-
-containsEither :: (Eq a) => [a] -> [a] -> Bool
-containsEither a = not . null . intersect a
-
-usage :: String
-usage = "Usage: monadoro [-h|--help] [-m|--man] [-v|--version] [--session]" ++
-  "[INTERVAL [...]]"
-
-milisec_per_second :: Int
-milisec_per_second = 10 ^ (6 :: Int)
-
-wait :: Int -> IO()
-wait n = threadDelay (n * milisec_per_second)
-
-runTimer :: IO () -> [String] -> IO ()
-runTimer delayer []     = void (countdown_loop delayer "25:00")
-runTimer delayer [t]    = void (countdown_loop delayer t)
-runTimer delayer (t:ts) = parseArgs [t] >> parseArgs ts
+  | xs `containsEither` ["--help", "-h"]    = showUsage
+  | xs `containsEither` ["--version", "-v"] = printVersion
+  | xs `containsEither` ["--session", "-s"] = runSession xs
+  | xs `containsEither` ["--man", "-m"]     = showManual
+  | otherwise                               = runPomodoros xs
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,7 @@
+monadoro (3.1.0)
+
+	* Move library functions out of the main module.
+
 monadoro (3.0.0)
 
 	* Drop dependency on package ansi-terminal.
diff --git a/src/CLI.hs b/src/CLI.hs
new file mode 100644
--- /dev/null
+++ b/src/CLI.hs
@@ -0,0 +1,130 @@
+module CLI (
+    containsEither,
+    filterParams,
+    printVersion,
+    runPomodoros,
+    runSession,
+    showManual,
+    showUsage
+) where
+
+import Control.Concurrent (threadDelay)
+import Control.Monad      (void)
+import Data.Char          (isDigit)
+import Data.List          (intersect)
+import Data.Version       (showVersion)
+import System.Exit        (ExitCode(ExitSuccess))
+import System.Process     (system)
+
+import Paths_Monadoro     (getDataFileName, version)
+import CountdownLoop      (countdown_loop)
+import Pomodoro           (session)
+
+showUsage :: IO ()
+showUsage = putStrLn usage
+
+usage :: String
+usage = "Usage: monadoro [-h|--help] [-m|--man] [-v|--version] [--session]" ++
+  "[INTERVAL [...]]"
+
+runPomodoros :: [String] -> IO ()
+runPomodoros xs = runPomodoro $ filterParams xs
+
+runPomodoro :: [String] -> IO ()
+runPomodoro xs = do
+    warnAboutErrorsIfAny invalidIntervals
+    runTimer (getDelayIfNeeded xs) validIntervals
+    where (validIntervals, invalidIntervals) = checkInput xs
+
+runSession :: [String] -> IO ()
+runSession xs = session (getDelayIfNeeded xs)
+
+runTimer :: IO () -> [String] -> IO ()
+runTimer delayer []     = void (countdown_loop delayer "25:00")
+runTimer delayer [t]    = void (countdown_loop delayer t)
+runTimer delayer (t:ts) = runTimer delayer [t] >> runTimer delayer ts
+
+filterParams :: [String] -> [String]
+filterParams xs = (remove "-n" . remove "--nodelay") xs
+
+remove :: String -> [String] -> [String]
+remove element list = filter (/=element) list
+
+showManual :: IO ()
+showManual = do
+    man_file <- getDataFileName "man/monadoro.1"
+    ExitSuccess <- system $ "man " ++ man_file
+    return ()
+
+printVersion :: IO ()
+printVersion = putStrLn . showVersion $ version
+
+warnAboutErrorsIfAny :: [String] -> IO ()
+warnAboutErrorsIfAny [] = return ()
+warnAboutErrorsIfAny errors =
+  putStrLn $ "Unable to parse as interval: " ++ show errors
+
+{-| checkInput
+
+Returns valid entries (first list) and invalid entries (second list).
+
+>>> checkInput []
+([],[])
+
+>>> checkInput ["x"]
+([],["x"])
+
+>>> checkInput ["00:00"]
+(["00:00"],[])
+
+>>> checkInput ["00:00", "x"]
+(["00:00"],["x"])
+
+>>> checkInput ["00:00", "x"]
+(["00:00"],["x"])
+
+>>> checkInput ["00:00", "00:01", "x"]
+(["00:00","00:01"],["x"])
+
+-}
+
+checkInput :: [String] -> ([String], [String])
+checkInput [] = ([], [])
+checkInput [x]
+  | isValidTimeInterval x = ([x], [])
+  | otherwise = ([], [x])
+checkInput (x:xs) = (validEntries, invalidEntries)
+  where
+    validEntries =
+      fst (checkInput [x]) ++ fst (checkInput xs)
+    invalidEntries =
+      snd (checkInput [x]) ++ snd (checkInput xs)
+
+{-| isValidTimeInterval
+
+>>> isValidTimeInterval "x"
+False
+
+>>> isValidTimeInterval "00:00"
+True
+
+-}
+
+isValidTimeInterval :: String -> Bool
+isValidTimeInterval (m1:m2:':':s1:[s2])
+  | all isDigit [m1, m2, s1, s2] = True
+isValidTimeInterval _ = False
+
+containsEither :: (Eq a) => [a] -> [a] -> Bool
+containsEither a = not . null . intersect a
+
+getDelayIfNeeded :: [String] -> IO ()
+getDelayIfNeeded xs
+  | xs `containsEither` ["--nodelay", "-n"] = wait 0
+  | otherwise = wait 1
+
+milisec_per_second :: Int
+milisec_per_second = 10 ^ (6 :: Int)
+
+wait :: Int -> IO()
+wait n = threadDelay (n * milisec_per_second)
diff --git a/test/DocTests.hs b/test/DocTests.hs
--- a/test/DocTests.hs
+++ b/test/DocTests.hs
@@ -5,7 +5,7 @@
 main :: IO ()
 main = doctest [
         "-isrc",
-        "app/Main.hs",
+        "src/CLI.hs",
         "src/ParseTime.lhs",
         "src/CountdownLoop.hs",
         "src/Pomodoro.lhs",
