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/UNLICENSE b/UNLICENSE
new file mode 100644
--- /dev/null
+++ b/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/codex.cabal b/codex.cabal
new file mode 100644
--- /dev/null
+++ b/codex.cabal
@@ -0,0 +1,55 @@
+name:                codex
+version:             0.0.1.0
+synopsis:            Code Explorer for Cabal
+description:         
+  This tool download and cache the source code of packages in your local hackage,
+  he can then use this cache to generate `tags` files aggregating the sources of all the dependencies of your cabal projects.
+  . 
+  You basically do `codex update` in your cabal project directory and you'll get a `codex.tags` file
+  that you can use in your favorite text editor (VIM/Emacs/Sublime).
+
+homepage:            http://github.com/aloiscochard/codex
+license:             OtherLicense
+license-file:        UNLICENSE
+author:              Alois Cochard
+maintainer:          alois.cochard@gmail.com
+category:            Development
+build-type:          Simple
+cabal-version:       >=1.10
+
+library
+  default-language:  Haskell2010
+  hs-source-dirs:    src
+  ghc-options:       -O2 -threaded -fwarn-incomplete-patterns
+  exposed-modules:
+    Codex
+    Codex.Internal
+    Distribution.Hackage.Utils
+  build-depends:       
+    base >=4.7 && <4.8
+    , bytestring
+    , Cabal >= 1.19
+    , containers
+    , directory
+    , download-curl
+    , filepath
+    , hackage-db
+    , mtl
+    , process
+    , tar
+    , zlib
+
+executable codex
+  default-language:  Haskell2010
+  main-is:           src/Main.hs
+  ghc-options:       -O2 -threaded -fwarn-incomplete-patterns
+  build-depends:       
+      base
+    , Cabal
+    , directory
+    , filepath
+    , hackage-db
+    , MissingH
+    , mtl
+    , monad-loops
+    , codex 
diff --git a/src/Codex.hs b/src/Codex.hs
new file mode 100644
--- /dev/null
+++ b/src/Codex.hs
@@ -0,0 +1,83 @@
+module Codex (Codex(..), Verbosity, module Codex) where
+
+import Control.Exception (try, SomeException)
+import Control.Monad
+import Control.Monad.Error
+import Data.Traversable (sequenceA)
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.Verbosity
+import Network.Curl.Download.Lazy
+import System.Directory
+import System.FilePath
+import System.Process
+
+import qualified Codec.Archive.Tar as Tar
+import qualified Codec.Compression.GZip as GZip
+import qualified Data.ByteString.Lazy as BS
+import qualified Data.List as List
+
+import Codex.Internal
+
+-- TODO Replace the `Codex` context with a `Control.Reader.Monad `.
+
+data Tagging = Tagged | Untagged
+  deriving (Eq, Show)
+
+fromBool :: Bool -> Tagging
+fromBool True = Tagged
+fromBool False = Untagged
+
+data Status = Source Tagging | Archive | Remote
+  deriving (Eq, Show)
+
+type Action = ErrorT String IO
+
+-- TODO It would be much better to work out which `Exception`s are thrown by which operations,
+--      and store all of that in a ADT. For now, I'll just be lazy.
+tryIO :: IO a -> Action a
+tryIO io = do 
+  res <- liftIO $ (try :: IO a -> IO (Either SomeException a)) io
+  either (throwError . show) return res
+
+status :: Codex -> PackageIdentifier -> Action Status
+status cx i = do
+  sourcesExist <- tryIO . doesDirectoryExist $ packageSources cx i
+  archiveExist <- tryIO . doesFileExist $ packageArchive cx i
+  case (sourcesExist, archiveExist) of
+    (True, _) -> fmap (Source . fromBool) (liftIO . doesFileExist $ packageTags cx i)
+    (_, True) -> return Archive
+    (_, _)    -> return Remote
+
+fetch :: Codex -> PackageIdentifier -> Action FilePath
+fetch cx i = do
+  bs <- tryIO $ do 
+    createDirectoryIfMissing True (packagePath cx i)
+    openLazyURI url
+  either throwError write bs where
+    write bs = fmap (const archivePath) $ tryIO $ BS.writeFile archivePath bs
+    archivePath = packageArchive cx i
+    url = packageUrl i
+
+extract :: Codex -> PackageIdentifier -> Action FilePath
+extract cx i = fmap (const path) . tryIO $ read path (packageArchive cx i) where
+  read dir tar = Tar.unpack dir . Tar.read . GZip.decompress =<< BS.readFile tar
+  path = packagePath cx i
+
+tags :: Codex -> PackageIdentifier -> Action FilePath
+tags cx i = do
+  tryIO . createProcess $ shell command
+  return tags where
+    command = concat ["ctags --tag-relative=no --recurse -f '", tags, "' '", sources, "'"]
+    sources = packageSources cx i
+    tags = packageTags cx i
+
+assembly :: Codex -> [PackageIdentifier] -> FilePath -> Action FilePath
+assembly cx is o = tryIO . fmap (const o) $ mergeTags (fmap tags is) o where
+  mergeTags files o = do
+    contents <- sequence $ fmap readFile files
+    let xs = List.sort . (List.filter (\x -> List.head x /= '!')) . concat $ fmap lines contents
+    writeFile o $ unlines (concat [header, xs])
+  tags i = packageTags cx i
+  header = ["!_TAG_FILE_FORMAT 2", "!_TAG_FILE_SORTED 1"]
+
diff --git a/src/Codex/Internal.hs b/src/Codex/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Codex/Internal.hs
@@ -0,0 +1,32 @@
+module Codex.Internal where
+
+import Control.Arrow
+import Distribution.Package
+import Distribution.Text
+import Distribution.Verbosity
+import System.FilePath
+
+data Codex = Codex { hackagePath :: FilePath, verbosity :: Verbosity }
+
+packagePath :: Codex -> PackageIdentifier -> FilePath
+packagePath cx i = joinPath [hackagePath cx, relativePath i] where
+  relativePath i = joinPath [name, version] where
+    name = display $ pkgName i
+    version = display $ pkgVersion i
+
+packageArchive :: Codex -> PackageIdentifier -> FilePath
+packageArchive cx i = joinPath [packagePath cx i, name] where
+  name = concat [display $ pkgName i, "-", display $ pkgVersion i, ".tar.gz"]
+
+packageSources :: Codex -> PackageIdentifier -> FilePath
+packageSources cx i = joinPath [packagePath cx i, name] where
+  name = concat [display $ pkgName i, "-", display $ pkgVersion i]
+
+packageTags :: Codex -> PackageIdentifier -> FilePath
+packageTags cx i = joinPath [packagePath cx i, "tags"]
+
+packageUrl :: PackageIdentifier -> String
+packageUrl i = concat ["http://hackage.haskell.org/package/", path] where
+  path = concat [name, "/", name, ".tar.gz"]
+  name = concat [display $ pkgName i, "-", display $ pkgVersion i]
+
diff --git a/src/Distribution/Hackage/Utils.hs b/src/Distribution/Hackage/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Hackage/Utils.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE CPP #-}
+module Distribution.Hackage.Utils where
+
+import Data.Maybe
+import Distribution.Hackage.DB (Hackage)
+import Distribution.Package
+import Distribution.PackageDescription
+import Distribution.Version
+import System.Directory
+import System.FilePath
+
+import qualified Data.List as List
+import qualified Data.Map as Map
+
+-- TODO Remove once path extracted in hackage-db
+getHackagePath :: IO FilePath
+getHackagePath = do
+ homedir <- getHomeDirectory
+ return (joinPath [homedir,
+#ifdef IS_DARWIN
+    "Library", "Haskell", "repo-cache"
+#else
+    ".cabal", "packages"
+#endif
+    , "hackage.haskell.org"])
+
+allDependencies :: GenericPackageDescription -> [Dependency]
+allDependencies pd = concat [lds, eds, tds] where
+  lds = condTreeConstraints =<< (maybeToList $ condLibrary pd) 
+  eds = (condTreeConstraints . snd) =<< condExecutables pd 
+  tds = (condTreeConstraints . snd) =<< condTestSuites pd 
+
+resolveDependency :: Hackage -> Dependency -> Maybe GenericPackageDescription
+resolveDependency db (Dependency (PackageName name) versionRange) = do
+  pdsByVersion <- Map.lookup name db
+  latest <- List.find (\x -> withinRange x versionRange) $ List.reverse $ List.sort $ Map.keys pdsByVersion
+  Map.lookup latest pdsByVersion
+
+resolveDependencies :: Hackage -> GenericPackageDescription -> [GenericPackageDescription]
+resolveDependencies db pd = maybeToList . resolveDependency db =<< allDependencies pd 
+
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,66 @@
+import Control.Arrow
+import Control.Monad.Error
+import Data.Traversable (sequenceA)
+import Data.String.Utils
+import Distribution.Hackage.DB (readHackage)
+import Distribution.Package -- TODO Remove
+import Distribution.PackageDescription
+import Distribution.PackageDescription.Parse
+import Distribution.Text
+import Distribution.Verbosity
+import System.Directory
+import System.Environment
+import System.FilePath
+
+import qualified Data.List as List
+
+import Distribution.Hackage.Utils
+
+import Codex
+
+-- TODO Make Async
+-- TODO Make verbosity configurable
+-- TODO Make `ctags` command configurable
+-- TODO Use `ctags --version` to check if ctags is installed
+
+retrying :: Int -> IO (Either a b) -> IO (Either [a] b)
+retrying n x = retrying' n $ fmap (left (:[])) x where
+  retrying' 0 x = x
+  retrying' n x = retrying' (n - 1) $ x >>= \res -> case res of
+    Left ls -> fmap (left (++ ls)) x
+    Right r -> return $ Right r
+
+-- TODO Better error handling and fine grained retry
+update :: Codex -> IO ()
+update cx = resolve =<< getCurrentProject where
+  resolve Nothing = putStrLn "No cabal file found."
+  resolve (Just project) = do
+    putStrLn $ concat ["Updating ", display . identifier $ project]
+    dependencies <- fmap (\db -> resolveDependencies db project) readHackage
+    founds <- retrying 4 $ runErrorT . sequence $ fmap (getTags . identifier) dependencies 
+    failOr (generate dependencies) founds where
+      identifier = package . packageDescription
+      -- TODO Add println of info (use verbosity)
+      getTags i = status cx i >>= \x -> case x of
+        (Source Tagged)   -> return ()
+        (Source Untagged) -> tags cx i >>= (const $ getTags i)
+        (Archive)         -> extract cx i >>= (const $ getTags i)
+        (Remote)          -> fetch cx i >>= (const $ getTags i)
+      generate xs = do 
+        res <- runErrorT $ assembly cx (fmap identifier xs) path
+        failOr (return ()) res where
+          path = joinPath [concat [display . pkgName . identifier $ project, ".tags"]]
+      failOr y x = either (putStrLn . show) (const y) x
+
+getCurrentProject :: IO (Maybe GenericPackageDescription)
+getCurrentProject = do
+  files <- getDirectoryContents $ joinPath ["."]
+  sequenceA . fmap (readPackageDescription silent) $ List.find (endswith ".cabal") files
+
+main :: IO ()
+main = do
+  hp    <- getHackagePath
+  args  <- getArgs
+  run (Codex hp silent) args where
+    run cx ["update"] = update cx
+    run cx _          = putStrLn "Usage: codex update"
