diff --git a/cabal-audit.cabal b/cabal-audit.cabal
--- a/cabal-audit.cabal
+++ b/cabal-audit.cabal
@@ -1,5 +1,5 @@
 name:                cabal-audit
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            Check how up-to-date your .cabal dependencies are.
 description:         Check how up-to-date your .cabal dependencies are.
 homepage:            https://github.com/joelteon/cabal-audit.git
@@ -14,6 +14,10 @@
 executable cabal-audit
   hs-source-dirs:      src
   main-is:             Main.hs
+  other-modules:       Audit
+                       Audit.Latest
+                       Audit.Options
+                       Audit.Panic
   build-depends:       base ==4.6.*,
                        Cabal == 1.16.*,
                        directory == 1.2.*,
diff --git a/src/Audit.hs b/src/Audit.hs
new file mode 100644
--- /dev/null
+++ b/src/Audit.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE TupleSections #-}
+
+module Audit (
+  audit
+) where
+
+import Audit.Latest
+import Audit.Panic
+import Control.Monad
+import Data.Maybe
+import Data.Version
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parse
+import Distribution.Text
+import Distribution.Verbosity
+import Distribution.Version
+import Text.Printf
+
+audit :: FilePath -> IO ()
+audit f = do
+        pkgDesc <- readPackageDescription deafening f
+        mapM_ (auditChunk id) (fmap ("library",) $ maybeToList $ condLibrary pkgDesc)
+        mapM_ (auditChunk (printf "executable %s")) (condExecutables pkgDesc)
+        mapM_ (auditChunk (printf "test suite %s")) (condTestSuites pkgDesc)
+        mapM_ (auditChunk (printf "benchmark %s")) (condBenchmarks pkgDesc)
+
+auditChunk :: (String -> String) -> (String, CondTree ConfVar [Dependency] a) -> IO ()
+auditChunk f (e,m) = do
+        putStrLn (f $ bold e)
+        allOk <- mapM auditDep $ condTreeConstraints m
+        when (and allOk) $ putStrLn $ success "ok!"
+
+auditDep :: Dependency -> IO Bool
+auditDep (Dependency (PackageName n) v)
+    | n == "base" = return True
+    | otherwise = do
+        latest <- getLatestVersionOf n
+        unless (withinRange latest v) $
+            printf "%s\n  latest: %s\n  requested: %s\n" (warning n) (showVersion latest) (display v)
+        return (withinRange latest v)
diff --git a/src/Audit/Latest.hs b/src/Audit/Latest.hs
new file mode 100644
--- /dev/null
+++ b/src/Audit/Latest.hs
@@ -0,0 +1,30 @@
+module Audit.Latest (
+  getLatestVersionOf
+) where
+
+import Audit.Panic
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parse
+import Distribution.ParseUtils
+import Distribution.Version
+import Network.HTTP
+import Text.Printf
+
+getLatestVersionOf :: String -> IO Version
+getLatestVersionOf pkg = do
+        resp <- simpleHTTP (getRequest url)
+        case resp of
+            Left err -> panic $ "Connection error when connecting to hackage: " ++ show err
+            Right s -> do
+                let desc = parsePackageDescription $ rspBody s
+                case desc of
+                    ParseFailed pe -> giveUp pe
+                    ParseOk ws d -> do
+                        mapM_ (worry . showPWarning (pkg ++ ".cabal")) ws
+                        return . pkgVersion . package $ packageDescription d
+    where url = printf "http://new-hackage.haskell.org/package/%s/src/%s.cabal" pkg pkg
+          giveUp pe = case ln of
+                          Nothing -> panic $ printf "Error when parsing %s.cabal: %s" pkg s
+                          Just num -> panic $ printf "Error when parsing %s.cabal: line %d: %s" pkg num s
+              where (ln, s) = locatedErrorMsg pe
diff --git a/src/Audit/Options.hs b/src/Audit/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Audit/Options.hs
@@ -0,0 +1,19 @@
+module Audit.Options (
+  Opts(..),
+  fullOpts,
+  module Options.Applicative.Extra
+) where
+
+import Options.Applicative
+import Options.Applicative.Extra
+
+data Opts = Opts { cabalFile :: Maybe FilePath } deriving Show
+
+opts :: Parser Opts
+opts = Opts <$> optional (argument str ( metavar "FILE"
+                                      <> help ".cabal file to parse" ))
+
+fullOpts :: ParserInfo Opts
+fullOpts = info (helper <*> opts)
+                ( fullDesc
+               <> progDesc "Audit your .cabal file" )
diff --git a/src/Audit/Panic.hs b/src/Audit/Panic.hs
new file mode 100644
--- /dev/null
+++ b/src/Audit/Panic.hs
@@ -0,0 +1,21 @@
+module Audit.Panic where
+
+import System.IO
+import System.Exit
+import Text.Printf
+
+panic :: String -> IO a
+panic s = do hPutStrLn stderr s
+             exitFailure
+
+worry :: String -> IO ()
+worry = hPutStrLn stderr
+
+bold :: String -> String
+bold s = printf "\27[1m%s\27[0m" s
+
+warning :: String -> String
+warning s = printf "\27[33m%s\27[0m" s
+
+success :: String -> String
+success s = printf "\27[32m%s\27[0m" s
