diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Anton Felix Lorenzen (c) 2017
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Anton Felix Lorenzen nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/dhall-check.cabal b/dhall-check.cabal
new file mode 100644
--- /dev/null
+++ b/dhall-check.cabal
@@ -0,0 +1,36 @@
+-- This file has been generated from package.yaml by hpack version 0.17.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           dhall-check
+version:        1.0.0.0
+synopsis:       Check all dhall files in a project
+category:       Development
+homepage:       https://github.com/anfelor/dhall-check#readme
+bug-reports:    https://github.com/anfelor/dhall-check/issues
+author:         Anton Felix Lorenzen
+maintainer:     anfelor@posteo.de
+copyright:      BSD3
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+
+source-repository head
+  type: git
+  location: https://github.com/anfelor/dhall-check
+
+executable dhall-check
+  main-is: Main.hs
+  hs-source-dirs:
+      src
+  build-depends:
+      base >= 4.7 && < 5
+    , containers
+    , directory
+    , dhall
+    , filepath
+    , fsnotify
+    , trifecta
+    , text
+  default-language: Haskell2010
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,180 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Main where
+
+import Control.Concurrent (threadDelay)
+import Control.Exception hiding (TypeError)
+import Control.Monad
+import Data.Char
+import Data.Map (Map)
+import Data.Monoid
+import Dhall
+import Dhall.Core
+import Dhall.Parser
+import Dhall.Import
+import Dhall.TypeCheck
+import qualified Data.Map as Map
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TLIO
+import System.Directory
+import System.FilePath
+import System.FSNotify
+import Text.Trifecta.Delta (Delta(..))
+
+import Debug.Trace
+
+
+-- | A dhall type, e.g. "entry" for "file.entry.dh"
+-- Should only contain lower-case strings.
+newtype DhallType = DhallType String
+  deriving (Eq, Ord, Show)
+
+-- | Turn a string into a 'DhallType'.
+-- >>> dhallType "Entry"
+-- DhallType "entry"
+dhallType :: String -> DhallType
+dhallType = DhallType . map toLower
+
+-- | Get the 'DhallType' for a file.
+-- >>> dhallTypeOf "file.entry.dh"
+-- DhallType "entry"
+dhallTypeOf :: FilePath -> DhallType
+dhallTypeOf = DhallType . tail . map toLower . takeExtension . dropExtension
+
+
+data CheckException
+  = CouldntFindTypeDefs
+  | ParseException FilePath ParseError
+  | UnknownType FilePath DhallType
+  | TypeChecking FilePath (TypeError Src)
+  deriving (Show)
+
+instance Exception CheckException where
+  displayException CouldntFindTypeDefs =
+    "Error: Expected a .dhall file with type definitions, but couldn't find it."
+  displayException (UnknownType f (DhallType t)) =
+    "Error: The file " ++ f ++ "has an unknown type: " ++ t
+  displayException (ParseException f pe) =
+    "While parsing " ++ f ++ ":" ++ displayException pe
+  displayException (TypeChecking f te) =
+    "While type checking " ++ f ++ ":" ++ displayException te
+
+
+main :: IO ()
+main = do
+  here <- getCurrentDirectory
+  printExceptions $ do
+    typedefs <- allTypeDefs here
+    dhallfiles <- allDhallFiles here
+    -- Check all files once
+    printExceptions $ do
+      checkAll typedefs dhallfiles
+      putStrLn "No errors."
+    -- and then watch for changes
+    withManager $ \mgr -> do
+      watchTree mgr here isDhallFile $ \event -> do
+        case event of
+          Added f utc -> do
+            putStrLn $ "\nAdded: " ++ f
+            printExceptions $ do
+              expr <- loadFile f
+              checkFile typedefs (f, expr)
+              putStrLn "No errors."
+          Modified f utc -> do
+            putStrLn $ "\nModified: " ++ f
+            printExceptions $ do
+              expr <- loadFile f
+              checkFile typedefs (f, expr)
+              putStrLn "No errors."
+          Removed f utc -> pure ()
+      forever $ threadDelay 1000000
+  where
+    -- | Print 'CheckException's and recover.
+    printExceptions comp = do
+      result <- try comp
+      case result of
+        Left e -> putStrLn $ displayException (e :: CheckException)
+        Right v -> pure v
+
+    isDhallFile f = (flip elem [".dhall", ".dh"] . takeExtension . eventPath) f
+                 && (not . (==) '.' . head . takeFileName . eventPath) f
+
+
+-- | A list of all files in a directory with the one of the given extensions,
+-- using absolute paths.
+--
+-- >>> allFiles "/home/user" [".hs"]
+-- ["/home/user/project/Main.hs", ...]
+allFiles :: Foldable t => FilePath -> t String -> IO [FilePath]
+allFiles dir exts = do
+  setCurrentDirectory dir
+  files <- mapM makeAbsolute =<< listDirectory dir
+  inThisDir <- flip filterM files $ \f -> do
+    exists <- doesFileExist f
+    let valid = takeExtension f `elem` exts
+    pure $ exists && valid
+  subdirs <- filterM doesDirectoryExist files
+  inSubDirs <- forM subdirs $ \d -> do
+    allFiles (dir </> d) exts
+  pure $ inThisDir ++ concat inSubDirs
+
+
+-- | Get all type definitions in the ".dhall" directory of a given directory
+-- and return them in a map of filename without ".dht" -> expression in file.
+allTypeDefs :: FilePath -> IO (Map DhallType (Expr Src X))
+allTypeDefs dir = do
+  isThere <- doesDirectoryExist (dir </> ".dhall")
+  when (not isThere) $ do
+    throwIO CouldntFindTypeDefs
+  files <- allFiles (dir </> ".dhall") [".dht"]
+  exprs <- forM files $ \f -> do
+    let name = dhallType $ takeFileName $ dropExtension f
+    sequence (name, loadFile f)
+  pure $ Map.fromList exprs
+
+
+-- | Get all a list of all dhall files in the given directory using absolute paths.
+allDhallFiles :: FilePath -> IO [(FilePath, Expr Src X)]
+allDhallFiles dir = do
+  files <- allFiles dir [".dh", ".dhall"]
+  forM files $ \f -> do
+    sequence (f, loadFile f)
+
+
+-- | Compile a dhall file.
+loadFile :: FilePath -> IO (Expr Src X)
+loadFile f = do
+  let delta = Directed "(input)" 0 0 0 0
+  content <- TLIO.readFile f
+  case exprFromText delta content of
+    Left e -> throwIO $ ParseException f e
+    Right e -> load e
+
+
+-- | Typecheck a single file against one of the given types.
+-- Throws an exception on failure to do so.
+--
+-- >>> checkFile (Map.fromList [("conf", .. )]) ("file.conf.dh", ..)
+-- /throws/ TypeChecking "file.conf.dh" (TypeError ..)
+checkFile :: Map DhallType (Expr Src X) -> (FilePath, Expr Src X) -> IO ()
+checkFile types (f, expr) = do
+  let name = dhallTypeOf f
+  case Map.lookup name types of
+    Nothing -> do
+      throwIO $ UnknownType f name
+    Just t -> do
+      let annot = case (expr, t) of
+            (Note (Src begin1 end1 bytes1) _, Note (Src begin2 end2 bytes2) _) ->
+              Note (Src begin1 end1 bytes') (Annot expr t)
+              where
+                bytes' = bytes1 <> "\n\n : \n\n" <> bytes2
+            _ -> Annot expr t
+      case typeOf annot of
+        Left err -> throwIO $ TypeChecking f err
+        Right _ -> pure ()
+
+
+-- | Perform 'checkFile' on every file in the Foldable.
+checkAll :: Foldable t => Map DhallType (Expr Src X) -> t (FilePath, Expr Src X) -> IO ()
+checkAll types = mapM_ (checkFile types)
