diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2014 Justin Leitgeb
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+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 OR COPYRIGHT HOLDERS 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.
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/src/Web/Twitter/Feed.hs b/src/Web/Twitter/Feed.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Twitter/Feed.hs
@@ -0,0 +1,183 @@
+{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
+
+module Web.Twitter.Feed
+  ( Tweet
+  , Link (..)
+  , URLEntity (..)
+  , UserEntity (..)
+  , MediaEntity (..)
+  , timeline
+  , addLink
+  , timelineUrl
+  , sortLinks
+  )
+where
+
+import Network.HTTP.Conduit
+import Web.Authenticate.OAuth
+import Data.Aeson
+import GHC.Generics (Generic)
+import Control.Applicative
+import Data.List (sortBy)
+import Control.Monad (mzero)
+import Web.Authenticate.OAuth
+
+type BoundingIndices = [Int]
+
+data SimpleTweet = SimpleTweet
+    { body    :: String
+    , tweetId :: String
+    } deriving (Show, Generic)
+
+data Tweet =  Tweet
+  { text       :: String
+  , createdAt  :: String
+  , idStr      :: String
+  , entities   :: Entities
+  } deriving (Show, Generic)
+
+data UserEntity = UserEntity
+  { screenName  :: String
+  , userIndices :: BoundingIndices
+  } deriving (Show, Generic)
+
+data URLEntity = URLEntity
+  { urlMessage   :: String
+  , urlIndices   :: BoundingIndices
+  , displayUrl   :: String
+  } deriving (Show, Generic)
+
+data MediaEntity = MediaEntity
+  { mediaUrl        :: String
+  , mediaIndices    :: BoundingIndices
+  , displayMediaUrl :: String
+  } deriving (Show, Generic)
+
+data Entities = Entities
+  { userEntities  :: [UserEntity]
+  , urlEntities   :: [URLEntity]
+  , mediaEntities :: [MediaEntity]
+  } deriving (Show, Generic)
+
+data Link = Link
+  { startIndex :: Int
+  , endIndex   :: Int
+  , newHtml    :: String
+  } deriving (Show, Eq)
+
+instance ToJSON SimpleTweet
+
+instance ToJSON Tweet
+instance FromJSON Tweet
+  where parseJSON (Object v) = Tweet
+                               <$> v .: "text"
+                               <*> v .: "created_at"
+                               <*> v .: "id_str"
+                               <*> v .: "entities"
+        parseJSON _          = mzero
+
+instance ToJSON UserEntity
+instance FromJSON UserEntity
+  where parseJSON (Object v) = UserEntity
+                               <$> v .: "screen_name"
+                               <*> v .: "indices" .!= []
+        parseJSON _          = mzero
+
+instance ToJSON URLEntity
+instance FromJSON URLEntity
+  where parseJSON (Object v) = URLEntity
+                               <$> v .: "url"
+                               <*> v .: "indices" .!= []
+                               <*> v .: "display_url"
+        parseJSON _          = mzero
+
+instance ToJSON MediaEntity
+instance FromJSON MediaEntity
+  where parseJSON (Object v) = MediaEntity
+                               <$> v .: "url"
+                               <*> v .: "indices" .!= []
+                               <*> v .: "display_url"
+        parseJSON _          = mzero
+
+instance ToJSON Entities
+instance FromJSON Entities
+  where parseJSON (Object v) = Entities
+                               <$> v .:? "user_mentions" .!= []
+                               <*> v .:? "urls" .!= []
+                               <*> v .:? "media" .!= []
+        parseJSON _          = mzero
+
+timeline :: String -> OAuth -> Credential -> Int ->
+            IO (Either String [SimpleTweet])
+timeline twitterUsername oauth credential tweets = do
+  req <- parseUrl $ timelineUrl twitterUsername tweets
+  res <- withManager $ \m -> do
+           signedreq <- signOAuth oauth credential req
+           httpLbs signedreq m
+
+  let decoded = decode $ responseBody res
+
+  case decoded of
+    Nothing -> return $ Left "Unable to retrieve tweets!"
+    Just ts -> return $ Right $ map (simplifyTweet . linkifyTweet) ts
+
+timelineUrl :: String -> Int -> String
+timelineUrl user count =
+    "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" ++
+    user ++ "&count=" ++ show count
+
+linkifyTweet :: Tweet -> Tweet
+linkifyTweet tweet = Tweet (processText (text tweet)
+                                        (userEntities  $ entities tweet)
+                                        (urlEntities   $ entities tweet)
+                                        (mediaEntities $ entities tweet))
+                           (createdAt tweet)
+                           (idStr tweet)
+                           (entities tweet)
+
+processText :: String -> [UserEntity] -> [URLEntity] -> [MediaEntity] -> String
+processText message users urls medias = foldr addLink message
+                                          (sortLinks urls users medias)
+
+sortLinks :: [URLEntity] -> [UserEntity] -> [MediaEntity] -> [Link]
+sortLinks urls users medias = sortBy sortDesc (map makeURLLink   urls ++
+                                               map makeUserLink  users ++
+                                               map makeMediaLink medias)
+  where sortDesc a b
+          | startIndex a < startIndex b = LT
+          | otherwise = GT
+
+makeURLLink :: URLEntity -> Link
+makeURLLink urlEntity = Link x y url
+  where x       = head (urlIndices urlEntity)
+        y       = urlIndices urlEntity !! 1
+        urlText = displayUrl urlEntity
+        href    = urlMessage urlEntity
+        url     = "<a target=\"_blank\" href=\"" ++ href ++ "\">"
+                    ++ urlText ++ "</a>"
+
+makeMediaLink :: MediaEntity -> Link
+makeMediaLink mediaEntity = Link x y url
+  where x       = head (mediaIndices mediaEntity)
+        y       = mediaIndices mediaEntity !! 1
+        urlText = displayMediaUrl mediaEntity
+        href    = mediaUrl mediaEntity
+        url     = "<a target=\"_blank\" href=\"" ++ href ++ "\">"
+                    ++ urlText ++ "</a>"
+
+makeUserLink :: UserEntity -> Link
+makeUserLink userEntity = Link x y mention
+  where x        = head (userIndices userEntity)
+        y        = userIndices userEntity !! 1
+        username = screenName userEntity
+        mention  = "<a target=\"_blank\" href=\"//twitter.com/" ++ username
+                   ++ "\">@" ++ username ++ "</a>"
+
+simplifyTweet :: Tweet -> SimpleTweet
+simplifyTweet tweet =
+    SimpleTweet { body = text tweet, tweetId = idStr tweet }
+
+addLink :: Link -> String -> String
+addLink link tweet = before ++ newHtml link ++ after
+  where before = fst (splitAt (startIndex link) tweet)
+        after  = snd (splitAt (endIndex link) tweet)
diff --git a/test/Suite.hs b/test/Suite.hs
new file mode 100644
--- /dev/null
+++ b/test/Suite.hs
@@ -0,0 +1,9 @@
+module Main where
+
+import  Test.Framework (defaultMain)
+
+import qualified Web.Twitter.Feed.Tests
+
+main :: IO ()
+main = defaultMain
+    [ Web.Twitter.Feed.Tests.tests ]
diff --git a/twitter-feed.cabal b/twitter-feed.cabal
new file mode 100644
--- /dev/null
+++ b/twitter-feed.cabal
@@ -0,0 +1,47 @@
+name:                twitter-feed
+version:             0.1.0.0
+synopsis:            Client for fetching Twitter timeline via Oauth
+description:         Fetches a user timeline from Twitter, and optionally linkifies the results using the Twitter entity API.
+homepage:            https://github.com/stackbuilders/twitter-feed
+license:             MIT
+license-file:        LICENSE
+author:              Justin Leitgeb, Andrés Torres
+maintainer:          justin@stackbuilders.com
+category:            Web
+build-type:          Simple
+extra-source-files:
+cabal-version:       >=1.10
+
+source-repository head
+  type:            git
+  location:        https://github.com/stackbuilders/twitter-feed.git
+
+library
+  exposed-modules:     Web.Twitter.Feed
+  build-depends:         base >=4.6 && <4.7
+                       , json
+                       , authenticate-oauth
+                       , aeson
+                       , http-conduit
+
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite twitter-library
+  type: exitcode-stdio-1.0
+  hs-source-dirs: test, src
+  main-is: Suite.hs
+  build-depends:
+      base >=4.6 && <4.7
+      , test-framework
+      , HUnit
+      , test-framework-hunit
+      , containers
+
+      -- Copied from the main build
+      , aeson
+      , json
+      , authenticate-oauth
+      , http-conduit
+
+  default-language:    Haskell2010
