diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2009,  Tom Lokhorst
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. 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.
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell
+
+> import Distribution.Simple
+> main = defaultMain
diff --git a/hackage2twitter.cabal b/hackage2twitter.cabal
new file mode 100644
--- /dev/null
+++ b/hackage2twitter.cabal
@@ -0,0 +1,33 @@
+name:                hackage2twitter
+version:             0.2.1
+synopsis:            Send new Hackage releases to Twitter
+description:         Build on the `feed2twitter` library, this program reads
+                     the Hackage RSS feed and tweets each new release to
+                     Twitter.
+                     .
+                     The Hackage feed is read only once. To keep updating, call
+                     this program every few minutes in a cron job.
+                     .
+                     A local cache of the last 20 tweets is kept in a file to
+                     make sure no duplicates are sent.
+                     .
+                     Usage:
+                     .
+                     > $ hackage2twitter username password cache-file [--debug-mode]
+license:             BSD3
+license-file:        LICENSE
+author:              Tom Lokhorst
+maintainer:          Tom Lokhorst <tom@lokhorst.eu>
+homepage:            http://github.com/tomlokhorst/hackage2twitter
+stability:           Experimental
+category:            Distribution, Web
+build-type:          Simple
+cabal-version:       >= 1.6
+
+executable hackage2twitter
+  build-depends:     base >= 4,
+                     feed >= 0.3.6,
+                     feed2twitter >= 0.2
+  main-is:           Main.hs
+  hs-source-dirs:    src
+
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,83 @@
+-------------------------------------------------------------------------------
+-- |
+-- Module      : Main
+-- Copyright   : (c) 2009, Tom Lokhorst
+-- License     : BSD3
+--
+-- Maintainer  : Tom Lokhorst <tom@lokhorst.eu>
+-- Stability   : Experimental
+--
+-- Main module for the `hackage2twitter` executable.
+-- Contains `rssItem2Tweet` function to parse Hackage RSS items.
+--
+-------------------------------------------------------------------------------
+module Main where
+
+import Text.RSS.Syntax
+import Web.Feed2Twitter
+
+import Data.Maybe
+import Data.List
+import System.Console.GetOpt
+import System.Environment
+
+main :: IO ()
+main = do
+  args <- getArgs
+  (cfg, rest) <- processArgs defaultConfig options header args
+  if length rest /= 3
+   then putStrLn $ usageInfo header options
+   else do
+        let cfg' = cfg { username  = rest !! 0
+                       , password  = rest !! 1
+                       , cacheFile = rest !! 2
+                       }
+        rss2twitter cfg' rssItem2tweet
+  where
+    header = "Usage: hackage2twitter USERNAME PASSWORD CACHE-FILE [OPTIONS...]"
+              ++ ", with the following options:"
+
+-- Map a RSS item to a tweet.
+-- This function will fail on a few `fromJust`s if the Hackage RSS feed ever changes.
+rssItem2tweet :: RSSItem -> Tweet
+rssItem2tweet ri = trunc4url (title ++ ", added by " ++ uploader
+                               ++ maybe "." (": " ++) blurb) ++ url
+  where
+    title     = filter (/='\n') . fromJust . rssItemTitle $ ri
+    uploader  = take (fromJust $ elemIndex ',' uploader') uploader'
+    blurb     = fmap (\x -> drop (x + 3) desc) (findSubList "<p>" desc)
+    url       = fromJust . rssItemLink $ ri
+    guid      = rssGuidValue . fromJust . rssItemGuid $ ri
+    desc      = map (\x -> if x == '\n' then ' ' else x)
+                  . fromJust . rssItemDescription $ ri
+    uploader' = drop 12 desc
+
+findSubList :: Eq a => [a] -> [a] -> Maybe Int
+findSubList [] s2 = Nothing
+findSubList s1 s2 = listToMaybe $ filter (\x -> isPrefixOf s1 $ drop x s2) xs
+  where
+    xs = elemIndices (head s1) s2
+
+defaultConfig :: Config
+defaultConfig = Config
+  { feedUrl = "http://hackage.haskell.org/packages/archive/recent.rss"
+  , username = ""
+  , password = ""
+  , cacheFile = ""
+  , cacheSize = 20
+  , debugMode = False
+  }
+
+options :: [OptDescr (Config -> Config)]
+options =
+  [ Option ['d'] ["debug-mode"] (NoArg (\c -> c { debugMode = True })) "Debug mode, send tweets to stdout."
+  -- , Option ['V'] ["version"] (NoArg (\c -> c { showVersion = True })) "Show program version."
+  ]
+
+-- Seems like this function should already exist somewhere in a package.
+processArgs :: a -> [OptDescr (a -> a)] -> String -> [String] -> IO (a, [String])
+processArgs defaultConfig options header args =
+  case getOpt Permute options args of
+    (oargs, nonopts, []    ) -> return (foldl (flip ($)) defaultConfig oargs, nonopts)
+    (_    , _      , errors) -> ioError $ userError $ (concat errors) ++ usageInfo header options
+
