diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Vanessa McHale (c) 2016
+
+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 Vanessa McHale 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# Command Line Interface Tweeter
+
+![Displaying a user timeline in a terminal.](https://raw.githubusercontent.com/vmchale/command-line-tweeter/master/screenshot.png)
+## Config
+Generate a token to authorize access to your twitter account by following the guide [here](https://dev.twitter.com/oauth/overview/application-owner-access-tokens)
+
+Then place your API keys and OAuth tokens in a file `~/.cred`, separated by a line break:
+
+```
+api-key: API_KEY_HERE
+api-sec: API_SECRET_HERE
+tok: OAUTH_TOKEN_HERE
+tok-sec: TOKEN_SECRET_HERE
+```
+
+## Installation
+
+If you're on Linux/Windows the best way is probably to download the binaries
+from the releases page [here](https://github.com/vmchale/command-line-tweeter/releases).
+
+To build from source, install [haskell stack](https://docs.haskellstack.org/en/stable/README/#how-to-install); on unix systems this is as simple as
+
+```
+wget -qO- https://get.haskellstack.org/ | sh
+```
+
+Then type `stack install tweet-hs` it will generate an executable called `tweet`, which is what we want.
+
+## Use
+
+### View Profiles and timelines
+
+To get your timeline, simply type:
+
+```
+tweet view
+```
+
+To view a user's profile, type e.g.
+
+```
+tweet user pinepapplesmear --color
+```
+
+### Sending tweets
+To tweet from stderr, run a command that pipes stderr to stdin, i.e.
+
+```
+YOUR_BUILD_COMMAND 2>&1 >/dev/null | tweet input
+```
+
+The `tweet` executable reads from stdIn only, but you can view the options (replies, number of tweets to thread, etc.) with
+
+```
+tweet --help
+```
+
+This script powers the twitter account [@my\_build\_errors](https://twitter.com/my_build_errors) for instance. There's an example bash script for in `bash/example`
+
+### Viewing your timeline
+
+You can also use
+
+```
+tweet view
+```
+
+or 
+
+```
+tweet view --color
+```
+
+to view your own timeline.
+
+### Completions
+
+The directory `bash/` has a `mkCompletions` script to allow command completions for your convenice.
+
+## Library
+
+A haskell package is included. It's fairly easy to use once you have the credentials set up, with two main functions: `thread` and `basicTweet`: the first for threading your own tweets or replying to someone else's and the second for just tweeting.
+
+### Finer details
+
+The function `tweetData` will tweet an object of type `Tweet`. Its use is pretty self-explanatory, but how to best form `Tweet`s is not immediately obvious.
+
+`Tweet` is an instance of `Default` so you can use `def` to get an empty tweet replying to nobody and not fetching extended user data. This is especially useful if you want to use lenses and avoid ugly record syntax, e.g.
+
+```
+set status "This is the new status field" $ def
+```
+
+will give you a `Tweet` with sensible defaults and the desired text.
+
+### Haskell
+
+This 
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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,6 @@
+module Main where
+
+import Web.Tweet.Exec
+
+main :: IO ()
+main = exec
diff --git a/bash/mkCompletions b/bash/mkCompletions
new file mode 100644
--- /dev/null
+++ b/bash/mkCompletions
@@ -0,0 +1,4 @@
+#!/bin/bash
+tweet --bash-completion-script `which tweet` > tmp
+sudo cp tmp /etc/bash_completion.d/tweet
+rm tmp
diff --git a/src/Web/Tweet.hs b/src/Web/Tweet.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet.hs
@@ -0,0 +1,198 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Various utilities to tweet using the twitter api
+-- 
+-- Make sure you have a file credentials file (default the executable looks for is `.cred`) with the following info:
+--
+-- @
+--
+-- api-key: API_KEY
+--
+-- api-sec: API_SECRE
+--
+-- tok: OAUTH_TOKEN
+--
+-- tok-sec: TOKEN_SECRET
+--
+-- @
+
+module Web.Tweet
+    (
+    -- * Functions to tweet
+    basicTweet
+    , tweetData
+    , thread
+    -- * Data type for a tweet
+    , module Web.Tweet.Types
+    -- * Functions to sign API requests
+    , signRequest
+    -- * Functions to generate a URL string from a `Tweet`
+    , urlString
+    , getTimeline
+    , showTimeline
+    , getProfile
+    , showProfile
+    , getDMs
+    , showDMs
+    , getRaw
+    ) where
+
+import Network.HTTP.Client
+import Network.HTTP.Client.TLS
+import Network.HTTP.Types.Status (statusCode)
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.ByteString.Lazy.Char8 as BSL
+import qualified Data.Text as T
+import Data.Text.Encoding
+import Data.Char
+import Web.Tweet.Types
+import Web.Tweet.Utils
+import Control.Monad
+import Data.List.Split (chunksOf)
+import Data.Maybe
+import Control.Lens
+import Control.Lens.Tuple
+import Web.Authenticate.OAuth
+import Web.Tweet.Sign
+import Data.List.Utils
+import qualified Data.Text as T
+
+-- | thread tweets together nicely. Takes a string, a list of handles to reply to, plus the ID of the status you're replying to.
+-- If you need to thread tweets without replying, pass a `Nothing` as the third argument.
+--
+-- > thread "Hi I'm back in New York!" ["friend1","friend2"] Nothing 1 ".cred"
+thread :: String -> [String] -> Maybe Int -> Int -> FilePath -> IO ()
+thread contents hs idNum num filepath = do
+    let handleStr = concatMap (((++) " ") . ((++) "@")) hs
+    let content = (take num) . (chunksOf (140-(length handleStr))) $ contents
+    case idNum of
+        (Just i) -> thread' content hs idNum num filepath
+        Nothing -> case content of
+            [] -> pure ()
+            [x] -> void $ basicTweet x filepath
+            y@(x:xs) -> thread' y hs (Just 0) num filepath
+
+-- | Helper function to make `thread` easier to write. 
+thread' :: [String] -> [String] -> Maybe Int -> Int -> FilePath -> IO ()
+thread' content hs idNum num filepath = do
+    -- fix the stuff with the handles.
+    let f = \str i -> tweetData (Tweet { _status = str, _trimUser = True, _handles = hs, _replyID = if i == 0 then Nothing else Just i }) filepath
+    let initial = f (head content)
+    last <- foldr ((>=>) . f) initial (content) $ fromMaybe 0 idNum
+    deleteTweet last filepath
+
+-- | Reply with a single tweet. Works the same as `thread` but doesn't take the fourth argument.
+--
+-- > reply "Idk what that means" ["friend1"] (Just 189943500) ".cred"
+reply :: String -> [String] -> Maybe Int -> FilePath -> IO ()
+reply contents hs idNum = thread contents hs idNum 1
+
+-- | Tweet a string given a path to credentials; return the id of the status.
+--
+-- > basicTweet "On the airplane." ".cred"
+basicTweet :: String -> FilePath -> IO Int
+basicTweet contents = tweetData (mkTweet contents)
+
+-- | Make a `Tweet` with only the contents.
+mkTweet :: String -> Tweet
+mkTweet contents = over (status) (const (contents)) def 
+
+-- | tweet, given a `Tweet` and path to credentials. Return id of posted tweet.
+tweetData :: Tweet -> FilePath -> IO Int
+tweetData tweet filepath = do
+    let requestString = urlString tweet
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString)
+    request <- signRequest filepath $ initialRequest { method = "POST" }
+    responseInt request manager
+
+-- | Get tweets (text only) for some user
+getRaw screenName filepath = do
+    let requestString = "?screen_name=" ++ screenName ++ "&count=3200"
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString)
+    request <- signRequest filepath $ initialRequest { method = "GET"}
+    let fromRight (Right a) = a
+    map (view _2) . fromRight .  getTweets . BSL.unpack <$> responseBS request manager
+
+-- | Get user profile given screen name and how many tweets to return
+getProfile screenName count filepath = do
+    let requestString = "?screen_name=" ++ screenName ++ "&count=" ++ (show count)
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString)
+    request <- signRequest filepath $ initialRequest { method = "GET"}
+    responseBS request manager -- TODO
+    getTweets . BSL.unpack <$> responseBS request manager
+
+-- | Show your DMs, given how many to return and whether or not to use color.
+showDMs count color filepath = showTweets color <$> getDMs count filepath
+
+-- | Show a user profile given screen name, how many tweets to return (API
+-- maximum is 3200), and whether to print them in color.
+showProfile :: Show t => String -> t -> Bool -> FilePath -> IO String
+showProfile screenName count color filepath = showTweets color <$> getProfile screenName count filepath
+
+-- | Display user timeline
+showTimeline count color filepath = showTweets color <$> getTimeline count filepath
+
+-- | Display user timeline in color
+showTweets color = (either show id) . (fmap (if color then displayTimelineColor else displayTimeline))
+
+-- | Get user's DMs.
+getDMs count filepath = do
+    let requestString = "?count=" ++ (show count)
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/direct_messages.json" ++ requestString)
+    request <- signRequest filepath $ initialRequest { method = "GET" }
+    getTweets . BSL.unpack <$> responseBS request manager
+
+-- | Get a timeline
+getTimeline count filepath = do
+    let requestString = "?count=" ++ (show count)
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)
+    request <- signRequest filepath $ initialRequest { method = "GET" }
+    getTweets . BSL.unpack <$> responseBS request manager
+
+-- | Delete a tweet given its id
+deleteTweet id filepath = do
+    manager <- newManager tlsManagerSettings
+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/destroy/" ++ (show id) ++ ".json")
+    request <- signRequest filepath $ initialRequest { method = "POST" }
+    void $ responseBS request manager
+
+-- | Return HTTP request's result as a bytestring
+responseBS :: Request -> Manager -> IO BSL.ByteString
+responseBS request manager = do
+    response <- httpLbs request manager
+    let code = statusCode $ responseStatus response
+    putStr $ if (code == 200) then "" else "failed :(\n error code: " ++ (show code) ++ "\n"
+    -- print $ generalCategory ('📚')
+    -- print $ (toEnum (0x1F48C) :: Char) -- (0xF079)
+    pure . responseBody $ response
+
+-- | print output of a request and return status id as an `Int`. 
+responseInt :: Request -> Manager -> IO Int
+responseInt request manager = do
+    response <- httpLbs request manager
+    let code = statusCode $ responseStatus response
+    putStrLn $ if (code == 200) then "POST succesful!" else "failed :(\n error code: " ++ (show code)
+    return $ (read . (takeWhile (/=',')) . (drop 52)) (BSL.unpack $ responseBody response)
+
+-- | Convert a tweet to a percent-encoded url for querying an API
+urlString :: Tweet -> String
+urlString tweet = concat [ "?status="
+                         , BS.unpack (tweetEncode tweet)
+                         , "&trim_user="
+                         , map toLower (show trim)
+                         , (if isJust (_replyID tweet) then "&in_reply_to_status_id=" else "")
+                         , reply ]
+    where trim  = _trimUser tweet
+          reply = fromMaybe "" (show <$> _replyID tweet)
+
+-- | Percent-encode the status update so it's fit for a URL and UTF-encode it as well. 
+tweetEncode :: Tweet -> BS.ByteString
+tweetEncode tweet = paramEncode . encodeUtf8 $ handleStr `T.append` content
+    where content   = T.pack . _status $ tweet
+          handleStr = T.pack $ concatMap ((++ " ") . ((++) "@")) hs
+          hs        = _handles tweet
diff --git a/src/Web/Tweet/Exec.hs b/src/Web/Tweet/Exec.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/Exec.hs
@@ -0,0 +1,183 @@
+-- | Provides IO action that parses command line options and tweetInputs from stdin
+module Web.Tweet.Exec ( exec
+                      , Program (..)
+                      , Command (..)) where
+
+import Web.Tweet
+import Options.Applicative
+import qualified Data.ByteString.Char8 as BS
+import Control.Monad
+import Data.Foldable (fold)
+import Data.List
+import Data.Monoid
+import System.Directory
+
+-- | Data type for our program: one optional path to a credential file, (optionally) the number of tweetInputs to make, the id of the status you're replying to, and a list of users you wish to mention.
+data Program = Program { subcommand :: Command , cred :: Maybe FilePath }
+
+-- | Data type for a 
+data Command = Timeline { count :: Maybe Int , color :: Bool } 
+    | SendInput { tweetInputs :: Maybe Int, replyId :: Maybe String, replyHandles :: Maybe [String] } 
+    | Profile { count :: Maybe Int , color :: Bool , screenName :: String } 
+    | Raw { screenName :: String } 
+    | Send { tweets :: Maybe Int , replyId :: Maybe String , replyHandles :: Maybe [String] , userInput :: String }
+
+-- | query twitter to post stdin with no fancy options
+fromStdIn :: Int -> FilePath -> IO ()
+fromStdIn = threadStdIn [] Nothing
+
+-- | Tweet string given to us, as parsed from the command line
+fromCLI :: String -> Int -> FilePath -> IO ()
+fromCLI str = thread str [] Nothing
+
+-- | Threaded tweetInputs from stdIn
+threadStdIn :: [String] -> Maybe Int -> Int -> FilePath -> IO ()
+threadStdIn hs idNum num filepath = do
+    contents <- getContents
+    thread contents hs idNum num filepath
+
+-- | Executes parser
+exec :: IO ()
+exec = execParser opts >>= select
+    where
+        opts = info (helper <*> program)
+            (fullDesc
+            <> progDesc "SendInput from stdin!"
+            <> header "clit - a Command Line Interface SendInputer")
+
+-- | Executes program given parsed `Program`
+select :: Program -> IO ()
+select (Program (Send (Just n) Nothing Nothing input) Nothing) = fromCLI input n =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send Nothing Nothing Nothing input) Nothing) = fromCLI input 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send (Just n) Nothing Nothing input) (Just file))  = fromCLI input n file
+select (Program (Send Nothing Nothing Nothing input) (Just file)) = fromCLI input 15 file
+select (Program (Send (Just n) (Just id) (Just handles) input) Nothing) = thread input handles (read id) n  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send (Just n) (Just id) (Just handles) input) (Just file)) = thread input handles (pure . read $ id) n file
+select (Program (Send Nothing (Just id) (Just handles) input) (Just file)) = thread input handles (pure . read $ id) 15 file
+select (Program (Send (Just n) (Just id) Nothing input) Nothing) = (thread input [] (pure . read $ id) n)  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send Nothing (Just id) Nothing input) Nothing) = thread input [] (pure . read $ id) 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send Nothing (Just id) (Just handles) input) Nothing) = thread input handles (pure . read $ id) 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Send (Just n) (Just id) Nothing input) (Just file)) = thread input [] (pure . read $ id) n file
+select (Program (Send (Just n) Nothing (Just handles) input) (Just file)) = thread input handles Nothing n file
+select (Program (SendInput (Just n) Nothing Nothing) Nothing) = fromStdIn n =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput Nothing Nothing Nothing) Nothing) = fromStdIn 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput (Just n) Nothing Nothing) (Just file))  = fromStdIn n file
+select (Program (SendInput Nothing Nothing Nothing) (Just file) ) = fromStdIn 15 file
+select (Program (SendInput (Just n) (Just id) (Just handles)) Nothing) = threadStdIn handles (read id) n  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput (Just n) (Just id) (Just handles)) (Just file)) = threadStdIn handles (pure . read $ id) n file
+select (Program (SendInput Nothing (Just id) (Just handles)) (Just file))  = threadStdIn handles (pure . read $ id) 15 file
+select (Program (SendInput (Just n) (Just id) Nothing) Nothing) = threadStdIn [] (pure . read $ id) n  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput Nothing (Just id) Nothing) Nothing) = threadStdIn [] (pure . read $ id) 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput Nothing (Just id) (Just handles)) Nothing) = threadStdIn handles (pure . read $ id) 15  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (SendInput (Just n) (Just id) Nothing) (Just file)) = threadStdIn [] (pure . read $ id) n file
+select (Program (SendInput (Just n) Nothing (Just handles)) (Just file)) = threadStdIn handles Nothing n file
+select (Program (Timeline Nothing False) Nothing) = putStrLn =<< showTimeline 8 False  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Timeline Nothing False) (Just file)) = putStrLn =<< showTimeline 8 False file
+select (Program (Timeline (Just n) False) (Just file)) = putStrLn =<< showTimeline 8 False file
+select (Program (Timeline (Just n) False) Nothing) = putStrLn =<< showTimeline 8 False  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Timeline Nothing True) Nothing) = putStrLn =<< showTimeline 8 True  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Timeline Nothing True) (Just file)) = putStrLn =<< showTimeline 8 True file
+select (Program (Timeline (Just n) True) (Just file)) = putStrLn =<< showTimeline 8 True file
+select (Program (Timeline (Just n) True) Nothing) = putStrLn =<< showTimeline 8 True  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Profile (Just n) True name) (Just file)) = putStrLn =<< showProfile name n True file
+select (Program (Profile Nothing True name) (Just file)) = putStrLn =<< showProfile name 12 True file
+select (Program (Profile (Just n) True name) Nothing) = putStrLn =<< showProfile name n True  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Profile Nothing True name) Nothing) = putStrLn =<< showProfile name 12 True  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Profile (Just n) False name) (Just file)) = putStrLn =<< showProfile name n False file
+select (Program (Profile Nothing False name) (Just file)) = putStrLn =<< showProfile name 12 False file
+select (Program (Profile (Just n) False name) Nothing) = putStrLn =<< showProfile name n False  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Profile Nothing False name) Nothing) = putStrLn =<< showProfile name 12 False  =<< (++ "/.cred") <$> getHomeDirectory
+select (Program (Raw name) Nothing) = do
+    raw <- getRaw name =<< (++ "/.cred") <$> getHomeDirectory
+    sequence_ $ putStrLn <$> raw
+select (Program (Raw name) (Just file)) = do
+    raw <- getRaw name file
+    sequence_ $ putStrLn <$> raw --fix this idk
+
+-- | Parser to return a program datatype
+program :: Parser Program
+program = Program
+    <$> (hsubparser
+        (command "send" (info tweet (progDesc "Send a tweet from the command-line"))
+        <> command "input" (info tweetInput (progDesc "Send a tweet from stdIn"))
+        <> command "view" (info timeline (progDesc "Get your timeline"))
+        <> command "user" (info profile (progDesc "Get a user's profile"))
+        <> command "raw" (info raw (progDesc "Grab tweetInputs en masse."))))
+    <*> (optional $ strOption
+        (long "cred"
+        <> short 'c'
+        <> metavar "CREDENTIALS"
+        <> help "path to credentials"))
+
+-- | Parser for the view subcommand
+timeline :: Parser Command
+timeline = Timeline
+    <$> (optional $ read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "number of tweetInputs to fetch, default 5"))
+    <*> switch
+        (long "color"
+        <> short 'l'
+        <> help "Display timeline with colorized terminal output.")
+
+-- | Parser for the raw subcommand
+raw :: Parser Command
+raw = Raw
+  <$> argument str
+    (metavar "SCREEN_NAME"
+    <> help "Screen name of user whose tweetInputs you want in bulk.")
+
+-- | Parser for the user subcommand
+profile :: Parser Command
+profile = Profile
+    <$> (optional $ read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs to fetch, default 12"))
+    <*> switch
+        (long "color"
+        <> short 'l'
+        <> help "Whether to display profile with colorized terminal output")
+    <*> argument str
+        (metavar "SCREEN_NAME"
+        <> help "Screen name of user you want to view.")
+
+-- | Parser for the send subcommand
+tweet :: Parser Command
+tweet = Send
+    <$> (optional $ read <$> strOption
+        (long "tweets"
+        <> short 't'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs in a row, default 15"))
+    <*> (optional $ strOption
+        (long "reply"
+        <> short 'r'
+        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
+    <*> (optional (some $ strOption $
+        (long "handle"
+        <> short 'h'
+        <> metavar "HANDLE1"
+        <> help "Handles to include in replies")))
+    <*> (unwords <$> (some $ argument str
+        (metavar "TEXT"
+        <> help "text of tweet to be sent")))
+
+-- | Parser for the input command
+tweetInput :: Parser Command
+tweetInput = SendInput
+    <$> (optional $ read <$> strOption
+        (long "tweets"
+        <> short 't'
+        <> metavar "NUM"
+        <> help "Number of tweets in a row, default 15"))
+    <*> (optional $ strOption
+        (long "reply"
+        <> short 'r'
+        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
+    <*> (optional $ (some $ argument str
+        (metavar "HANDLE1"
+        <> help "handles to include in replies")))
diff --git a/src/Web/Tweet/Sign.hs b/src/Web/Tweet/Sign.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/Sign.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Functions to sign HTTP requests with oAuth
+module Web.Tweet.Sign where
+
+import Web.Tweet.Utils
+import Web.Authenticate.OAuth
+import Network.HTTP.Client
+
+-- | Sign a request using your OAuth dev token.
+-- Uses the IO monad because signatures require a timestamp
+signRequest :: FilePath -> Request -> IO Request
+signRequest filepath req = do
+    o <- oAuth filepath
+    c <- credential filepath
+    signOAuth o c req
+
+-- | Create an OAuth from config data in a file
+oAuth :: FilePath -> IO OAuth
+oAuth filepath = do
+    secret <- (lineByKey "api-sec") <$> getConfigData filepath
+    key <- (lineByKey "api-key") <$> getConfigData filepath
+    let url = "api.twitter.com"
+    return newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
+
+-- | Create a new credential from a token and token secret
+credential :: FilePath -> IO Credential
+credential filepath = newCredential <$> token <*> secretToken
+    where token       = (lineByKey "tok") <$> getConfigData filepath
+          secretToken = (lineByKey "tok-sec") <$> getConfigData filepath
diff --git a/src/Web/Tweet/Types.hs b/src/Web/Tweet/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/Types.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE DeriveGeneric   #-}
+{-# LANGUAGE DeriveAnyClass  #-}
+{-# LANGUAGE TemplateHaskell #-}
+
+-- | Exports the `Tweet` type, a datatype for building tweets easily
+module Web.Tweet.Types where
+
+import GHC.Generics
+import Control.Lens
+import Data.Default
+
+-- | Default value for Bool for trim_user (`True` in our case)
+instance Default Bool where
+    def = True
+
+-- | Data type for our request: consists of the status text, whether to trium user information in the response, the handles to mention, and optionally the id of the status to reply to.
+data Tweet = Tweet
+    { _status   :: String
+    , _trimUser :: Bool
+    , _handles  :: [String]
+    , _replyID  :: Maybe Int
+    } deriving (Generic, Default)
+
+-- | Data type for tweets as they are returned
+data TweetEntity = TweetEntity
+    { _text :: String
+    , _name :: String
+    , _screenName :: Maybe String
+    , _tweetId :: Int
+    , _isQuoteStatus :: Bool
+    , _retweets :: Int
+    , _favorites :: Int
+    } deriving (Generic, Default)
+
+-- | Stores data like (name, text, favoriteCount, retweetCount)
+type Timeline = [(String, String, String, String)]
+
+makeLenses ''Tweet
+
+makeLenses ''TweetEntity
diff --git a/src/Web/Tweet/Utils.hs b/src/Web/Tweet/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/Utils.hs
@@ -0,0 +1,149 @@
+-- | Miscellaneous functions that don't fit the project directly
+module Web.Tweet.Utils where
+
+import qualified Data.ByteString.Char8 as BS
+import Text.Megaparsec.String
+import Text.Megaparsec.Lexer as L
+import Text.Megaparsec
+import Data.Char
+import Data.List
+import Data.Monoid
+import Data.Maybe
+import Web.Tweet.Types
+import Control.Monad
+import Control.Lens.Tuple
+import Control.Lens hiding (noneOf)
+import Data.Function
+import Web.Tweet.Utils.Colors
+
+-- `FIXME` 
+parseDMs = zip <$> (extractEvery 2 <$> filterStr "screen_name") <*> (filterStr "text")
+    where extractEvery n = map snd . filter ((== n) . fst) . zip (cycle [1..n])
+
+-- | Display Timeline without color
+displayTimeline :: Timeline -> String
+displayTimeline ((user,content,fave,rts):rest) = (user <> ":\n    " <> content) <> "\n    " <> "♥ " {-- ♡💛--} <> fave <> " ♺ " <> rts <> "\n\n" <> (displayTimeline rest) -- 
+displayTimeline [] = []
+
+-- | Display Timeline in color
+displayTimelineColor :: Timeline -> String
+displayTimelineColor ((user,content,fave,rts):rest) = ((toYellow user) <> ":\n    " <> content) <> "\n    " <> (toRed "♥ ") {-- ♡💛--} <> fave <> (toGreen " ♺ ") <> rts <> "\n\n" <> (displayTimelineColor rest) -- 
+displayTimelineColor [] = []
+
+-- | Get a list of tweets from a response, returning author, favorites, retweets, and content. 
+getTweets = parse parseTweet "" 
+
+-- | Parse some number of tweets
+parseTweet :: Parser Timeline
+parseTweet = concat <$> many (try getData <|> (const (pure ("","","","")) <$> eof))
+
+hits = sortFaves . filterRTs
+
+filterRTs :: Timeline -> Timeline
+filterRTs = filter ((/="RT") . take 2 . (view _2))
+
+sortFaves :: Timeline -> Timeline
+sortFaves = sortBy compareFavorites
+    where compareFavorites = on compare ((read :: String -> Int) . (view _3))
+
+-- | Parse a single tweet's: name, text, fave count, retweet count
+getData :: Parser [(String, String, String, String)]
+getData = do
+    text <- filterStr "text"
+    skipMentions
+    name <- filterStr "name"
+    isQuote <- filterStr "is_quote_status"
+    case isQuote of
+        "false" -> do
+            rts <- filterStr "retweet_count"
+            faves <- filterStr "favorite_count"
+            pure $ pure (name, text, faves, rts)
+        "true" -> do
+            textQuoted <- filterStr "text"
+            skipMentions
+            nameQuoted <- filterStr "name"
+            rtsQuoted <- filterStr "retweet_count"
+            favesQuoted <- filterStr "favorite_count"
+            rts <- filterStr "retweet_count"
+            faves <- filterStr "favorite_count"
+            pure [(name, text, faves, rts), (nameQuoted, textQuoted, favesQuoted, rtsQuoted)]
+
+-- TODO make it work when user names include ]
+skipInsideBrackets :: Parser ()
+skipInsideBrackets =void (between (char '[') (char ']') $ many (skipInsideBrackets <|> void (noneOf "[]")))
+
+skipMentions :: Parser ()
+skipMentions = do
+    many $ try $ anyChar >> notFollowedBy (string ("\"user_mentions\":"))
+    char ','
+    string "\"user_mentions\":"
+    skipInsideBrackets --between (char '[') (char ']') (many $ anyChar)
+    pure ()
+
+-- | Throw out input until we get to a relevant tag.
+filterStr :: String -> Parser String
+filterStr str = do
+    many $ try $ anyChar >> notFollowedBy (string ("\"" <> str <> "\":"))
+    char ','
+    filterTag str
+
+-- | Parse a field given its tag
+filterTag :: String -> Parser String
+filterTag str = do
+    string $ "\"" <> str <> "\":"
+    open <- optional $ char '\"'
+    let forbidden = if (isJust open) then "\\\"" else "\\\","
+    want <- many $ noneOf forbidden <|> specialChar '\"' <|> specialChar '/' <|> newlineChar <|> unicodeChar -- specialChar 'u'
+    pure want
+
+-- | Parse a newline
+newlineChar :: Parser Char
+newlineChar = do
+    string "\\n"
+    pure '\n'
+
+-- TODO
+--emoji :: Parser Char
+--emoji = do
+--    string "\\u"
+    --d83d dc8c 💌
+
+-- | Parser for unicode; twitter will give us something like "/u320a"
+unicodeChar :: Parser Char
+unicodeChar = do
+    string "\\u"
+    num <- fromHex . filterEmoji <$> count 4 anyChar
+    pure . toEnum . fromIntegral $ num
+
+-- | ignore emoji 
+filterEmoji str = if head str == 'd' then "FFFD" else str
+
+-- | Parse escaped characters
+specialChar :: Char -> Parser Char
+specialChar c = do
+    string $ "\\" ++ pure c
+    pure c
+
+-- | Convert a string of four hexadecimal digits to an integer.
+fromHex :: String -> Integer
+fromHex = fromRight . (parse (L.hexadecimal :: Parser Integer) "")
+    where fromRight (Right a) = a
+
+-- | helper function to get the key as read from a file
+keyLinePie :: String -> String
+keyLinePie = takeWhile (/=':')
+
+-- | Pick out a key value from a key
+lineByKey :: BS.ByteString -> [(BS.ByteString, BS.ByteString)] -> BS.ByteString
+lineByKey key = snd . head . (filter (\i -> fst i == key))
+
+-- | Filter a line of a file for only the actual data and no descriptors
+filterLine :: String -> String
+filterLine = reverse . (takeWhile (not . (`elem` (" :" :: String)))) . reverse
+
+-- | Get pairs of "key" to search for and actual values
+getConfigData :: FilePath -> IO [(BS.ByteString, BS.ByteString)]
+getConfigData filepath = zip <$> keys <*> content
+    where content = (map (BS.pack . filterLine)) . lines <$> file
+          keys    = (map (BS.pack . keyLinePie)) . lines <$> file
+          file    = readFile filepath
diff --git a/src/Web/Tweet/Utils/Colors.hs b/src/Web/Tweet/Utils/Colors.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/Utils/Colors.hs
@@ -0,0 +1,12 @@
+module Web.Tweet.Utils.Colors where
+
+import Text.PrettyPrint.ANSI.Leijen
+
+toRed :: String -> String
+toRed = show . red . text
+
+toYellow :: String -> String
+toYellow = show . dullyellow . text
+
+toGreen :: String -> String
+toGreen = show . dullgreen . text
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,66 @@
+# This file was automatically generated by 'stack init'
+#
+# Some commonly used options have been documented as comments in this file.
+# For advanced use and comprehensive documentation of the format, please see:
+# http://docs.haskellstack.org/en/stable/yaml_configuration/
+
+# Resolver to choose a 'specific' stackage snapshot or a compiler version.
+# A snapshot resolver dictates the compiler version and the set of packages
+# to be used for project dependencies. For example:
+#
+# resolver: lts-3.5
+# resolver: nightly-2015-09-21
+# resolver: ghc-7.10.2
+# resolver: ghcjs-0.1.0_ghc-7.10.2
+# resolver:
+#  name: custom-snapshot
+#  location: "./custom-snapshot.yaml"
+resolver: lts-8.5
+
+# User packages to be built.
+# Various formats can be used as shown in the example below.
+#
+# packages:
+# - some-directory
+# - https://example.com/foo/bar/baz-0.0.2.tar.gz
+# - location:
+#    git: https://github.com/commercialhaskell/stack.git
+#    commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
+#   extra-dep: true
+#  subdirs:
+#  - auto-update
+#  - wai
+#
+# A package marked 'extra-dep: true' will only be built if demanded by a
+# non-dependency (i.e. a user package), and its test suites and benchmarks
+# will not be run. This is useful for tweaking upstream packages.
+packages:
+- '.'
+# Dependency packages to be pulled from upstream that are not in the resolver
+# (e.g., acme-missiles-0.3)
+extra-deps: []
+
+# Override default flag values for local packages and extra-deps
+flags: {}
+
+# Extra package databases containing global packages
+extra-package-dbs: []
+
+# Control whether we use the GHC we find on the path
+# system-ghc: true
+#
+# Require a specific version of stack, using version ranges
+# require-stack-version: -any # Default
+# require-stack-version: ">=1.4"
+#
+# Override the architecture used by stack, especially useful on Windows
+# arch: i386
+# arch: x86_64
+#
+# Extra directories used by stack for building
+# extra-include-dirs: [/path/to/dir]
+# extra-lib-dirs: [/path/to/dir]
+#
+# Allow a newer minor version of GHC than the snapshot specifies
+# compiler-check: newer-minor
diff --git a/tweet-hs.cabal b/tweet-hs.cabal
new file mode 100644
--- /dev/null
+++ b/tweet-hs.cabal
@@ -0,0 +1,70 @@
+name: tweet-hs
+version: 0.4.0.7
+cabal-version: >=1.10
+build-type: Simple
+license: BSD3
+license-file: LICENSE
+copyright: 2016 Vanessa McHale
+maintainer: tmchale@wisc.edu
+stability: stable
+homepage: https://github.com/vmchale/command-line-tweeter#readme
+synopsis: Post tweets from stdin
+description:
+    a Command Line Interface Tweeter
+category: Web
+author: Vanessa McHale
+extra-source-files:
+    README.md
+    stack.yaml
+    bash/mkCompletions
+
+source-repository head
+    type: git
+    location: https://github.com/vmchale/command-line-tweeter
+
+flag llvm-fast
+    description:
+        Enable build with llvm backend
+    default: False
+
+library
+    exposed-modules:
+        Web.Tweet
+        Web.Tweet.Exec
+    build-depends:
+        base >=4.7 && <5,
+        http-client-tls >=0.3.4 && <0.4,
+        http-client >=0.5.6.1 && <0.6,
+        http-types >=0.9.1 && <0.10,
+        authenticate-oauth ==1.6.*,
+        bytestring >=0.10.8.1 && <0.11,
+        split >=0.2.3.1 && <0.3,
+        optparse-applicative >=0.13.2.0 && <0.14,
+        lens >=4.15.1 && <4.16,
+        data-default >=0.7.1.1 && <0.8,
+        text >=1.2.2.1 && <1.3,
+        megaparsec >=5.2.0 && <5.3,
+        ansi-wl-pprint >=0.6.7.3 && <0.7,
+        MissingH >=1.4.0.1 && <1.5,
+        directory >=1.3.0.0 && <1.4
+    default-language: Haskell2010
+    hs-source-dirs: src
+    other-modules:
+        Web.Tweet.Types
+        Web.Tweet.Utils
+        Web.Tweet.Utils.Colors
+        Web.Tweet.Sign
+
+executable tweet
+    
+    if flag(llvm-fast)
+        ghc-options: -threaded -rtsopts -with-rtsopts=-N -fllvm -optlo-O3 -O3
+    else
+        ghc-options: -threaded -rtsopts -with-rtsopts=-N
+    main-is: Main.hs
+    build-depends:
+        base >=4.9.1.0 && <4.10,
+        tweet-hs >=0.4.0.7 && <0.5
+    default-language: Haskell2010
+    hs-source-dirs: app
+
