diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2015, Jeremy Shaw
+
+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 Jeremy Shaw 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/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,117 @@
+module Main where
+
+import qualified Data.Map as Map
+import Distribution.Hackage.DB.Parsed
+import Distribution.Package (PackageName(..), PackageIdentifier(PackageIdentifier, pkgName, pkgVersion))
+import Distribution.PackageDescription (GenericPackageDescription(packageDescription), PackageDescription(package))
+import Distribution.PackageDescription.Parse (readPackageDescription)
+import Distribution.Simple.Utils (defaultPackageDesc)
+-- import Distribution.Version (Version)
+import Distribution.Text (display)
+import Distribution.Verbosity (Verbosity, normal)
+import System.Directory ( getAppUserDataDirectory )
+import System.Exit (ExitCode(..), exitSuccess, exitWith)
+import System.FilePath ( (</>), (<.>) )
+import System.IO (hPutStr, hPutStrLn, stderr)
+import System.IO.Temp (withSystemTempDirectory)
+import System.Process
+
+-- | get the package description for the local directory
+local :: Verbosity -> IO PackageDescription
+local v =
+  fmap packageDescription (readPackageDescription v =<< defaultPackageDesc v)
+
+-- | get the package description from hackage
+--
+-- This gets the latest version available.
+hackage :: PackageName -- ^ the package name
+        -> IO (Maybe PackageDescription)
+hackage (PackageName pn) = do
+  hackage <- readHackage
+  case Map.lookup pn hackage of
+    Nothing ->
+      return Nothing
+    (Just gpdMap) ->
+      return $ Just $ packageDescription (snd $ head $ Map.toDescList gpdMap)
+
+-- | calculate the directory to the tarball for `PackageIndentifier`
+packageDir :: FilePath -> PackageIdentifier -> FilePath
+packageDir repoLocal (PackageIdentifier pn version) =
+    repoLocal </> display pn </> display version
+
+-- | calculate the path to the tarball for `PackageIndentifier`
+packageFile :: FilePath -> PackageIdentifier -> FilePath
+packageFile repoLocal pi =
+    (packageDir repoLocal pi) </> display pi <.> "tar.gz"
+
+-- | fetch `PackageIdentifier` from hackage
+--
+-- returns path to downloaded `.tar.gz`
+fetchFromHackage :: PackageIdentifier
+                 -> IO FilePath
+fetchFromHackage pi@(PackageIdentifier (PackageName pn) version) = do
+  (ec, out, err) <- readProcessWithExitCode "cabal" ["fetch", "--no-dependencies", display pi] ""
+  case ec of
+    ExitSuccess -> do
+      dd <- getAppUserDataDirectory "cabal"
+      let repoLocal = dd </> "packages" </> "hackage.haskell.org" -- FIXME: this is the default, but users could be using a mirror or alternative repo
+      return $ packageFile repoLocal pi
+    (ExitFailure _) -> do
+      putStrLn "Failed to fetch package from hackage."
+      putStr out
+      hPutStr stderr err
+      exitWith (ExitFailure 2)
+
+-- | create a `.tar.gz` in the local directory by calling `cabal sdist`.
+cabalSDist :: PackageIdentifier -> IO FilePath
+cabalSDist pi = do
+  (ec, out, err) <- readProcessWithExitCode "cabal" ["sdist"] ""
+  case ec of
+    ExitFailure _ -> do
+      putStr out
+      hPutStr stderr err
+      exitWith (ExitFailure 2)
+    ExitSuccess ->
+      return $ "dist" </> display pi <.> "tar.gz"
+
+-- | calculate the `sha256sum`
+
+sha256Sum :: FilePath -> IO String
+sha256Sum fp =
+    do sum <- readProcess "sha256sum" [fp] "" -- `catch`
+       return $ takeWhile (/= ' ') sum
+
+-- | extract two `tar.gz` filesa nd run a recursive diff on them.
+tardiff :: FilePath -- ^ tarball 'a'
+        -> FilePath -- ^ tarball 'b'
+        -> IO ()
+tardiff tar1 tar2 =
+  withSystemTempDirectory "tardiff-XXXXXX" $ \tar1dir ->
+  withSystemTempDirectory "tardiff-XXXXXX" $ \tar2dir -> do
+    callProcess "tar" ["-C",tar1dir, "-x", "-z", "--strip=1", "-f", tar1]
+    callProcess "tar" ["-C",tar2dir, "-x", "-z", "--strip=1", "-f", tar2]
+    (ec, out, err) <- readProcessWithExitCode "diff" ["-r","-N","-u", tar1dir, tar2dir] ""
+    case ec of
+      ExitSuccess -> exitSuccess
+      ExitFailure _ -> do
+        putStr out
+        hPutStr stderr err
+        exitWith (ExitFailure 1)
+
+-- | tie all the bits together
+main :: IO ()
+main = do
+  let v = normal
+  localPackage <- local v
+  mHackagePackage <- hackage (pkgName $ package $ localPackage)
+  case mHackagePackage of
+    Nothing ->
+        do hPutStrLn stderr $ "Could not find " ++ (unPackageName $ pkgName $ package $ localPackage) ++  " on hackage."
+           exitWith (ExitFailure 2)
+    (Just hackagePackage) -> do
+      if (pkgVersion $ package localPackage) /= (pkgVersion $ package hackagePackage)
+        then do hPutStrLn stderr $ "Versions do not match. Local = " ++ display (pkgVersion $ package $ localPackage) ++ ", Hackage = " ++ display (pkgVersion $ package $ hackagePackage)
+        else return ()
+      pf <- fetchFromHackage (package hackagePackage)
+      lf <- cabalSDist (package localPackage)
+      tardiff pf lf
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+hackage-whatsnew [![Hackage](https://img.shields.io/hackage/v/hackage-whatsnew.svg)](https://hackage.haskell.org/package/hackage-whatsnew) [![Build Status](https://api.travis-ci.org/stepcut/hackage-whatsnew.svg?branch=master)](https://travis-ci.org/stepcut/hackage-whatsnew)
+================
+
+Do you often modify a package, check that it builds in travis, and then FORGET TO UPLOAD IT!
+
+Then this tool is for you! Its raison d'etre is to see if you have
+changes in your local directory which are not on hackage.
+
+Usage
+-----
+
+`hackage-whatsnew` depends on the following executables:
+
+ 1. cabal
+
+ 2. tar
+
+ 3. GNU diff (or any `diff` which supports the `-r`, `-u`, and `-N` options)
+
+These binaries need to be in the current search path. Assuming
+everything is installed, to use `hackage-whatsnew` you simply need to:
+
+ 1. run `cabal update`
+
+ 2. cd into the same directory as the `.cabal` file
+
+ 3. run `hackage-whatsnew`
+
+If no changes are detected, then nothing is printed and the exit code is 0.
+
+If changes are detected a recursive diff is displayed and the exit code is 1.
+
+How It Works
+------------
+
+This tool works as follows:
+
+ 1. read the local `.cabal` file and figure out the package name
+
+ 2. use `cabal fetch` to get the latest version of the package from hackage
+
+ 3. use `cabal sdist` to generate the `.tar.gz` for the local working directory
+
+ 4. untar both `.tar.gz` bundles into temporary directories
+
+ 5. use `diff -ruN` to check for differences
+
+ 6. exit with 0 if no differences found
+
+ 7. exit with 1 if differences with found
+
+ 8. exit with 2 if other errors encountered
+
+
+FAQ
+---
+
+**Q**: Why is it called `hackage-whatsnew` instead of `hackage-diff`?
+
+**A**: Because `hackage-diff` was already taken. The `whatsnew` term is inspired by `darcs whatsnew`.
+
+**Q**: Would it by great if the tool did XYZ?
+
+**A**: Yes! Please submit a pull request.
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/hackage-whatsnew.cabal b/hackage-whatsnew.cabal
new file mode 100644
--- /dev/null
+++ b/hackage-whatsnew.cabal
@@ -0,0 +1,31 @@
+name:                hackage-whatsnew
+version:             0.1.0.0
+synopsis:            Check for differences between working directory and hackage
+description:         This tool checks to see if the package in a local working directory
+                     has changes which are not yet on hackage. This is useful to check if
+                     you have forgetton to upload your changes to hackage.
+license:             BSD3
+license-file:        LICENSE
+author:              Jeremy Shaw
+maintainer:          jeremy@n-heptane.com
+category:            Distribution
+build-type:          Simple
+cabal-version:       >=1.10
+tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3
+extra-source-files:  README.md
+
+source-repository head
+    type:     git
+    location: https://github.com/stepcut/hackage-whatsnew.git
+
+executable hackage-whatsnew
+  main-is:             Main.hs
+  build-depends:       base >=4.6 && <4.9,
+                       Cabal >= 1.16 && < 1.23,
+                       containers >=0.5 && <0.6,
+                       directory >= 1.2 && < 1.3,
+                       filepath >= 1.4 && < 1.5,
+                       hackage-db >= 1.22 && < 1.23,
+                       process >= 1.2 && < 1.5,
+                       temporary >= 1.2 && < 1.3
+  default-language:    Haskell2010
