diff --git a/InitTj.hs b/InitTj.hs
new file mode 100644
--- /dev/null
+++ b/InitTj.hs
@@ -0,0 +1,44 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Main where
+
+import System.Console.CmdArgs
+import Data.Data
+
+import Text.RegexPR (matchRegexPR)
+import Data.Maybe (isJust)
+import System.IO (hFlush, stdout)
+
+import Trajectory.Private.Config (writeKey)
+
+main = do
+  args <- cmdArgs initTjArgDefinition
+  key <- getKey
+  writeKey (profileName args) key
+  return ()
+
+getKey = promptWhile isBlank "API key: " 
+
+data InitTjArg = InitTjArg {
+   profileName :: String
+} deriving (Show, Data, Typeable)
+
+initTjArgDefinition = InitTjArg {
+   profileName = "default"
+     &= explicit
+     &= name "profile"
+     &= help "The profile name to use [default]"
+} &= program "initrj"
+
+-- generally useful functions below; maybe they exist elsewhere:
+
+promptWhile p prompt = loop
+  where
+    loop = do
+      putStr prompt
+      hFlush stdout
+      result <- getLine
+      if p result
+         then loop
+         else return result
+
+isBlank s = isJust $ matchRegexPR "^\\s*$" s
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2011, Mike Burns
+
+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 Mike Burns 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/LsStory.hs b/LsStory.hs
new file mode 100644
--- /dev/null
+++ b/LsStory.hs
@@ -0,0 +1,288 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Main where
+
+import System.Console.CmdArgs
+import Data.Data
+
+import Data.List (intercalate)
+import Data.Maybe (fromMaybe, isNothing)
+import Control.Applicative( (<*>) )
+import Data.Monoid (mconcat)
+
+import Trajectory.Private.Config (withKey)
+import Trajectory.API (getStories, Story(..), Iteration(..))
+
+main = do
+  args <- cmdArgs lsStoryArgDefinition
+  withKey (profileName args) $ \key -> do
+      stories <- getStories key (accountName args) (projectName args)
+      putStrLn $ either (\error -> "Error: " ++ show error)
+                        (handle args)
+                        stories
+
+handle :: LsStoryArg -> ([Story],[Iteration]) -> String
+handle args (stories,iterations) =
+  let filters = buildFilters args iterations
+      renderer = buildRenderer args in
+    renderer $ filters `pipe` stories
+  where
+    pipe :: [ a -> a ] -> (a -> a)
+    pipe = foldr (.) id
+
+buildRenderer args [] = ""
+buildRenderer args stories
+  | onlyNext args && null nonMilestones = ""
+  | detailedOutput args && onlyNext args =
+    head $ map detailedFormatter nonMilestones
+  | detailedOutput args =
+    intercalate "\n\n" $ map detailedFormatter stories
+  | onlyNext args =
+    head $ map simpleFormatter nonMilestones
+  | otherwise =
+    intercalate "\n" $ map simpleFormatter stories
+    where
+      nonMilestones = skipMilestones stories
+      skipMilestones = filter $ ("Milestone" /=) . storyTaskType
+
+detailedFormatter story =
+  title ++ "\n" ++ origins ++ "\n" ++ overall
+  where
+    title = milestonePrefix story ++ storyTitle story
+    overall = intercalate "\t" $ [
+       "id: " ++ (show $ storyId story)
+      ,"points: " ++ (show $ storyPoints story)
+      ,"comments: " ++ (show $ storyCommentsCount story)
+      ]
+    origins = intercalate "\t" $ [
+       (fromMaybe "unassigned" $ storyAssigneeName story)
+      ,(maybe "free-floating" ("idea: " ++) $ storyIdeaSubject story)
+      ]
+
+simpleFormatter story = milestonePrefix story ++ storyTitle story
+
+milestonePrefix story
+  | storyTaskType story == "Milestone" = "^^^ "
+  | otherwise = ""
+
+buildFilters :: LsStoryArg -> [Iteration] -> [ [Story] -> [Story] ]
+buildFilters args iterations = filters <*> [args]
+  where filters = [
+          ideaFilter
+          ,beforeMilestoneFilter
+          ,estimationFilter
+          ,assignmentFilter
+          ,stateFilter
+          ,neededFilter
+          ,iterationFilter iterations
+          ,deletionsFilter
+          ]
+
+ideaFilter args
+  | null summaries && not limitedToNullIdeas = id
+  | null summaries && limitedToNullIdeas =
+    filter (isNothing . storyIdeaSubject)
+  | otherwise = filter ideaElements
+    where
+      summaries = ideaSummaries args
+      limitedToNullIdeas = showNullIdeas args
+      ideaElements story =
+        maybe limitedToNullIdeas
+              (\summary -> summary `elem` summaries)
+              (storyIdeaSubject story)
+
+beforeMilestoneFilter args
+  | isNothing $ beforeMilestone args = id
+  | otherwise = takeWhile notTheMilestone
+    where
+      (Just milestoneTitle) = beforeMilestone args
+      notTheMilestone story =
+        (storyTaskType story /= "Milestone") &&
+          (storyTitle story /= milestoneTitle)
+
+estimationFilter args
+  | null (showEstimate args) && not (showUnestimated args) = id
+  | null $ showEstimate args =
+    filter (\story -> (storyPoints story) == 0)
+  | not $ showUnestimated args =
+    filter (\story -> (storyPoints story) `elem` (showEstimate args))
+  | otherwise =
+    filter (\story -> (storyPoints story) `elem` [0]++(showEstimate args))
+
+assignmentFilter args
+  | null assignments && not limitedToUnassigned = id
+  | null assignments = filter (isNothing . storyAssigneeName)
+  | otherwise = filter assignmentElements
+    where
+      limitedToUnassigned = showUnassigned args
+      assignments = showAssigned args
+      assignmentElements story =
+        maybe limitedToUnassigned
+              (\assignment -> assignment `elem` assignments)
+              (storyAssigneeName story)
+
+stateFilter args =
+  filter (\story -> (storyState story) `elem` states)
+  where
+    allStates = 
+      ["unstarted", "started", "finished", "done", "accepted", "rejected"]
+    stateBits = [(showUnstarted args)
+                ,(showStarted args)
+                ,(showFinished args)
+                ,(showDone args)
+                ,(showAccepted args)
+                ,(showRejected args)]
+    states =
+      if or stateBits
+         then map fst $ filter snd $ zip allStates stateBits
+         else allStates
+
+neededFilter args
+  | showDesignNeeded args && showDevelopmentNeeded args =
+    filter (\story -> storyDesignNeeded story && storyDevelopmentNeeded story)
+  | showDesignNeeded args = filter storyDesignNeeded
+  | showDevelopmentNeeded args = filter storyDevelopmentNeeded
+  | otherwise = id
+
+iterationFilter iterations args
+  | showAllIterations args = id
+  | showCurrentIteration args && (present $ showIteration args) =
+    filter (storyInIterations $ [currentIteration] ++ desiredIterations)
+  | present $ showIteration args = filter (storyInIterations desiredIterations)
+  | otherwise = filter (storyInIterations [currentIteration])
+  where
+    currentIteration = head $ filter iterationIsCurrent iterations
+    present = not . null
+    desiredIterations = filter (\iteration ->
+      (iterationStartsOn iteration) `elem` (showIteration args)
+      ) iterations
+    storyInIterations its story =
+      (storyIterationId story) `elem` (map iterationId its)
+
+deletionsFilter args = filter (not . storyDeleted)
+
+data LsStoryArg = LsStoryArg {
+   profileName :: String
+
+  ,ideaSummaries :: [String]
+  ,showNullIdeas :: Bool
+  ,beforeMilestone :: Maybe String
+  ,showEstimate :: [Int]
+  ,showUnestimated :: Bool
+  ,showAssigned :: [String]
+  ,showUnassigned :: Bool
+  ,showDesignNeeded :: Bool
+  ,showDevelopmentNeeded :: Bool
+
+  ,showUnstarted :: Bool
+  ,showStarted :: Bool
+  ,showFinished :: Bool
+  ,showDone :: Bool
+  ,showAccepted :: Bool
+  ,showRejected :: Bool
+
+  ,showAllIterations :: Bool
+  ,showCurrentIteration :: Bool
+  ,showIteration :: [String]
+
+  ,detailedOutput :: Bool
+  ,onlyNext :: Bool
+
+  ,accountName :: String
+  ,projectName :: String
+} deriving (Show, Data, Typeable)
+
+lsStoryArgDefinition = LsStoryArg {
+   profileName = "default"
+     &= explicit
+     &= name "profile"
+     &= groupname "Common flags"
+     &= help "The profile name to use [default]"
+  ,ideaSummaries = def
+     &= explicit
+     &= name "idea"
+     &= groupname "Filtering"
+     &= help "Stories matching this idea"
+  ,showNullIdeas = def
+     &= explicit
+     &= name "unset-idea"
+     &= help "Stories without an idea"
+  ,beforeMilestone = def
+     &= explicit
+     &= name "before-milestone"
+     &= help "Stories before the given milestone"
+  ,showUnestimated = def
+     &= explicit
+     &= name "unestimated"
+     &= help "Unestimated stories"
+  ,showEstimate = def
+     &= explicit
+     &= name "estimate"
+     &= help "Stories with the given estimate"
+  ,showUnassigned = def
+     &= explicit
+     &= name "unassigned"
+     &= help "Unassigned stories"
+  ,showAssigned = def
+     &= explicit
+     &= name "assigned"
+     &= help "Stories assigned to the named person"
+  ,showDesignNeeded = def
+     &= explicit
+     &= name "design"
+     &= help "Stories that need design"
+  ,showDevelopmentNeeded = def
+     &= explicit
+     &= name "development"
+     &= help "Stories that need development"
+  ,showUnstarted = def
+     &= explicit
+     &= name "unstarted"
+     &= groupname "Filtering on state"
+     &= help "Unstarted stories"
+  ,showStarted = def
+     &= explicit
+     &= name "started"
+     &= help "Started stories"
+  ,showFinished = def
+     &= explicit
+     &= name "finished"
+     &= help "Finished stories"
+  ,showDone = def
+     &= explicit
+     &= name "done"
+     &= help "Stories in the done state"
+  ,showAccepted = def
+     &= explicit
+     &= name "accepted"
+     &= help "Stories which have been accepted"
+  ,showRejected = def
+     &= explicit
+     &= name "rejected"
+     &= help "Stories which have been rejected"
+  ,showAllIterations = def
+     &= explicit
+     &= name "all-iterations"
+     &= groupname "Iteration filtering"
+     &= help "Stories from all iterations"
+  ,showCurrentIteration = def
+     &= explicit
+     &= name "current-iteration"
+     &= help "Stories from the current iteration"
+  ,showIteration = def
+     &= explicit
+     &= name "iteration"
+     &= help "Stories from the named iteration"
+  ,onlyNext = def
+     &= explicit
+     &= name "next"
+     &= groupname "Formatting"
+     &= help "Only show the next matching story"
+  ,detailedOutput = def
+     &= explicit
+     &= name "long"
+     &= name "l"
+     &= groupname "Formatting"
+     &= help "Show story details"
+  ,accountName = def &= argPos 0
+  ,projectName = def &= argPos 1
+} &= program "lsstory" &= helpArg [groupname "Common flags"]
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+Trajectory
+----------
+
+Tools and a library for [Trajectory](http://apptrajectory.com/).
+
+Installation
+============
+
+    % cabal install trajectory
+    % export PATH=~/.cabal/bin
+    % inittj
+
+You can find your API key in [the Trajectory settings](https://www.apptrajectory.com/profile/edit).
+
+The account name and project name come from the URL:
+
+    https://www.apptrajectory.com/ACCOUNTNAME/PROJECTNAME/stories
+
+It probably makes a ton of sense to use a shell alias:
+
+    % alias lsos='lsstory thoughtbot opensource'
+
+Synopsis
+========
+
+Done:
+
+    % inittj
+    % inittj --profile thoughtbot-support
+
+    % lsstory accountname projectname
+    % lsstory accountname projectname --idea "Spam is out of control"
+    % lsstory accountname projectname --next
+    % lsstory accountname projectname --unstarted
+    % lsstory accountname projectname --unestimated
+    % lsstory accountname projectname --unstarted --next --unassigned --incomplete
+    % lsstory accountname projectname --within-milestone "Fully Backboned"
+    % lsstory accountname projectname --profile thoughtbot-support --project "Trajectory"
+    % lsstory accountname projectname --completed
+
+    % lsstory accountname projectname --development
+    % lsstory accountname projectname --design
+    % lsstory accountname projectname --design --development
+
+    % lsstory accountname projectname --all-iterations
+    % lsstory accountname projectname --current-iteration
+    % lsstory accountname projectname --iteration 2012-01-02
+
+To do:
+
+    % lsidea
+    % lsmilestone
+    % lsmilestone --unstarted --next
+
+    % mkstory "As a user I want to always be signed in so I don't have to care"
+    % mkstory "As an admin I want to delete comments so I can remove spam" --idea "Spam is out of control"
+    % mkstory "As a visitor I am not able to see private messages so I can maintain security" -m -a screenshot.png --top
+
+Copyright
+=========
+
+Copyright 2011 Mike Burns
+
+Trajectory is copyright 2011 thoughtbot
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/Trajectory/API.hs b/Trajectory/API.hs
new file mode 100644
--- /dev/null
+++ b/Trajectory/API.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}
+
+-- | The Trajectory API, or a subset of it at least. This mirrors the
+-- underlying implementation, which ties stories to iterations.
+
+module Trajectory.API (
+ getStories
+,module Trajectory.Types
+) where
+
+import Data.Data
+import Data.Aeson
+import Control.Applicative ( (<$>), (<*>) )
+
+import Data.List (intercalate)
+import Data.Attoparsec.ByteString.Lazy
+import qualified Data.ByteString.Char8 as BS
+import qualified Data.ByteString.Lazy.Char8 as LBS
+import qualified Network.HTTP.Types as Types
+import Network.HTTP.Enumerator
+import Text.URI
+import qualified Control.Exception as E
+import Data.Maybe (fromMaybe)
+
+import Trajectory.Types
+
+-- | Get all the incomplete stories and iterations for a given user key,
+-- account name, and project name. Since stories and iterations are tied
+-- together in the underlying API, this produces them as a pair.
+--
+-- It produces an IO of either an error or the stories/iterations pair. The
+-- error can come from the HTTP, or from non-JSON input, or from a change to
+-- the JSON.
+--
+-- > do
+-- >   possibleStories <- getStories "abcdefg" "thoughtbot" "opensource"
+-- >   case possibleStories of
+-- >     (Left error) -> putStrLn $ "got the error: " ++ show error
+-- >     (Right (stories,iterations)) ->
+-- >       putStrLn $ intercalate "\n" $
+-- >         (map formatStory stories) ++ (map formatIteration iterations)
+getStories :: String -> String -> String -> IO (Either Error ([Story], [Iteration]))
+getStories key accountName projectName = do
+  let url = buildUrl [key, "accounts", accountName, "projects", projectName, "stories.json"]
+  result <- doHttps (BS.pack "GET") url Nothing
+  return $ either (Left . HTTPConnectionError)
+                  (extractStories . parseJson . responseBody)
+                  result
+  where
+    extractStories :: (Either Error Stories) -> (Either Error ([Story],[Iteration]))
+    extractStories (Left l) = Left l
+    extractStories (Right (Stories stories iterations)) = Right (stories, iterations)
+
+
+data Stories = Stories [Story] [Iteration]
+  deriving (Show, Eq, Typeable, Data)
+
+instance FromJSON Story where
+  parseJSON (Object o) =
+    Story <$> o .: "archived"
+          <*> o .:? "assignee_id"
+          <*> o .:? "branch"
+          <*> o .: "created_at"
+          <*> o .: "deleted"
+          <*> o .: "design_needed"
+          <*> o .: "development_needed"
+          <*> o .: "id"
+          <*> o .:? "idea_id"
+          <*> o .: "iteration_id"
+          <*> o .: "points"
+          <*> o .: "position"
+          <*> o .: "state"
+          <*> o .: "task_type"
+          <*> o .: "title"
+          <*> o .: "updated_at"
+          <*> o .: "user_id"
+          <*> o .: "comments_count"
+          <*> o .:? "assignee_name"
+          <*> o .: "user_name"
+          <*> o .: "state_events"
+          <*> o .:? "idea_subject"
+  parseJSON _          = fail "Could not build a Story"
+
+instance FromJSON Iteration where
+  parseJSON (Object o) =
+    Iteration <$> o .: "accepted_points"
+              <*> o .: "complete"
+              <*> o .: "created_at"
+              <*> o .: "estimated_points"
+              <*> o .: "estimated_velocity"
+              <*> o .: "id"
+              <*> o .: "starts_on"
+              <*> o .: "stories_count"
+              <*> o .: "team_strength"
+              <*> o .: "updated_at"
+              <*> o .: "percent_complete"
+              <*> o .: "current?"
+              <*> o .: "unstarted_stories_count"
+              <*> o .: "accepted_stories_count"
+              <*> o .: "started_stories_count"
+              <*> o .: "delivered_stories_count"
+              <*> o .: "comments_count"
+  parseJSON _ = fail "Could not build an Iteration"
+
+instance FromJSON Stories where
+  parseJSON (Object o) =
+    Stories <$> o .: "stories" <*> o .: "iterations"
+  parseJSON _          = fail "Could not build Stories"
+
+
+buildUrl :: [String] -> String
+buildUrl paths = "https://www.apptrajectory.com/api/" ++ intercalate "/" paths
+
+
+doHttps :: BS.ByteString -> String -> Maybe (RequestBody IO) -> IO (Either E.IOException Response)
+doHttps method url body = do
+  let (Just uri) = parseURI url
+      (Just host) = uriRegName uri
+      requestBody = fromMaybe (RequestBodyBS $ BS.pack "") body
+      queryString = Types.parseQuery $ BS.pack $ fromMaybe "" $ uriQuery uri
+      request = def { method = method
+                    , secure = True
+                    , host = BS.pack host
+                    , port = 443
+                    , path = BS.pack $ uriPath uri
+                    , requestBody = requestBody
+                    , queryString = queryString
+                    }
+
+  (getResponse request >>= return . Right) `catch` (return . Left)
+  where
+    getResponse request = withManager $ \manager -> httpLbs request manager
+
+parseJson :: (FromJSON b, Show b) => LBS.ByteString -> Either Error b
+parseJson jsonString =
+  let parsed = parse (fromJSON <$> json) jsonString in
+  case parsed of
+       Data.Attoparsec.ByteString.Lazy.Done _ jsonResult -> do
+         case jsonResult of
+              (Success s) -> Right s
+              (Error e) -> Left $ JsonError $ e ++ " on the JSON: " ++ LBS.unpack jsonString
+       (Fail _ _ e) -> Left $ ParseError e
diff --git a/Trajectory/Private/Config.hs b/Trajectory/Private/Config.hs
new file mode 100644
--- /dev/null
+++ b/Trajectory/Private/Config.hs
@@ -0,0 +1,70 @@
+module Trajectory.Private.Config where
+
+import qualified Data.ByteString.Char8 as BS (readFile, ByteString)
+import qualified Data.ByteString.Lazy as LBS
+import System.Environment (getEnv)
+import qualified Data.HashMap.Lazy as M
+import qualified Data.Text as T (pack, unpack)
+import Data.Aeson (json, Value(..), encode, toJSON)
+import Data.Attoparsec (parse, IResult(..))
+import Data.Text as T
+
+withKey :: String -> (String -> IO ()) -> IO ()
+withKey profileName doThis = do
+  maybeKey <- getKey profileName
+  maybe showError doThis maybeKey
+  where showError = putStrLn $ "Unknown profile name: " ++ profileName
+
+getKey profileName = do
+  possibleConfig <- decodeConfig
+  return $ maybe Nothing
+                 (maybe Nothing (Just . extractKey) . lookupKey)
+                 possibleConfig
+  where
+    extractKey (String k) = T.unpack k
+    lookupKey = M.lookup (T.pack profileName)
+
+readConfig :: IO (Maybe BS.ByteString)
+readConfig = getConfigFileName >>= readConfigFrom
+
+readConfigFrom :: String -> IO (Maybe BS.ByteString)
+readConfigFrom configFileName =
+  (BS.readFile configFileName >>= return . Just) `catch` (const $ return Nothing)
+
+decodeConfig :: IO (Maybe (M.HashMap T.Text Value))
+decodeConfig = getConfigFileName >>= decodeConfigFrom
+
+decodeConfigFrom :: String -> IO (Maybe (M.HashMap T.Text Value))
+decodeConfigFrom configFileName = do
+  possibleJsonString <- readConfigFrom configFileName
+  case possibleJsonString of
+    Nothing -> return Nothing
+    (Just jsonString) -> do
+      return $ Just mapping
+      where
+        (Done _ config) = parse json jsonString
+        (Object mapping) = config
+
+writeKey :: String -> String -> IO ()
+writeKey profileName key = do
+  configFileName <- getConfigFileName
+  possiblePriorConfig <- decodeConfigFrom configFileName
+  let newConfig = maybe (M.singleton encodedProfileName encodedKey)
+                        (M.insert encodedProfileName encodedKey)
+                        possiblePriorConfig
+  LBS.writeFile configFileName $ encode $ toJSON newConfig
+  where
+    encodedProfileName = T.pack profileName
+    encodedKey = String $ T.pack key
+
+getConfigFileName = do
+  getEnv "TRAJECTORY_CONFIG_FILE"
+   `catch`
+   (const (getEnv "HOME" >>= return . withHomeDir))
+   `catch`
+   (const (getEnv "USER" >>= return . withUserName))
+   `catch`
+   (const (return "/.trajectory"))
+  where
+    withHomeDir homeDir   = homeDir ++ "/.trajectory"
+    withUserName userName = "/usr/"++userName++"/.trajectory"
diff --git a/Trajectory/Types.hs b/Trajectory/Types.hs
new file mode 100644
--- /dev/null
+++ b/Trajectory/Types.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module Trajectory.Types where
+
+import Control.Exception (IOException)
+import Data.Data (Typeable, Data)
+
+-- | Errors have been tagged according to their source, so you can more easily
+-- dispatch and handle them.
+data Error =
+    HTTPConnectionError IOException -- ^ A HTTP error occurred. The actual caught error is included, if available.
+  | ParseError String -- ^ An error in the parser itself.
+  | JsonError String -- ^ The JSON is malformed or unexpected.
+  | UserError String -- ^ Incorrect input.
+  deriving (Show, Eq)
+
+-- | A Trajectory story.
+data Story = Story {
+   storyArchived :: Bool
+  ,storyAssigneeId :: Maybe Int
+  ,storyBranch :: Maybe String
+  ,storyCreatedAt :: String
+  ,storyDeleted :: Bool
+  ,storyDesignNeeded :: Bool
+  ,storyDevelopmentNeeded :: Bool
+  ,storyId :: Int
+  ,storyIdeaId :: Maybe Int
+  ,storyIterationId :: Int
+  ,storyPoints :: Int
+  ,storyPosition :: Int
+  ,storyState :: String
+  ,storyTaskType :: String
+  ,storyTitle :: String
+  ,storyUpdatedAt :: String
+  ,storyUserId :: Int
+  ,storyCommentsCount :: Int
+  ,storyAssigneeName :: Maybe String
+  ,storyUserName :: String
+  ,storyStateEvents :: [String]
+  ,storyIdeaSubject :: Maybe String
+} deriving (Show, Eq, Typeable, Data)
+
+-- | An iteration in Trajectory. The iterationStartsOn is the most
+-- user-identifying string, though it changes with time. The @storyIterationId@
+-- is the same as the @iterationId@.
+data Iteration = Iteration {
+   iterationAcceptedPoints :: Int
+  ,iterationIsComplete :: Bool
+  ,iterationCreatedAt :: String
+  ,iterationEstimatedPoints :: Int
+  ,iterationEstimatedVelocity :: Int
+  ,iterationId :: Int
+  ,iterationStartsOn :: String
+  ,iterationStoriesCount :: Int
+  ,iterationTeamStrength :: Int
+  ,iterationUpdatedAt :: String
+  ,iterationPercentComplete :: Int
+  ,iterationIsCurrent :: Bool
+  ,iterationUnstartedStoriesCount :: Int
+  ,iterationAcceptedStoriesCount :: Int
+  ,iterationStartedStoriesCount :: Int
+  ,iterationDeliveredStoriesCount :: Int
+  ,iterationCommentsCount :: Int
+} deriving (Show, Eq, Typeable, Data)
diff --git a/trajectory.cabal b/trajectory.cabal
new file mode 100644
--- /dev/null
+++ b/trajectory.cabal
@@ -0,0 +1,138 @@
+-- trajectory.cabal auto-generated by cabal init. For additional
+-- options, see
+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.
+-- The name of the package.
+Name:                trajectory
+
+-- The package version. See the Haskell package versioning policy
+-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for
+-- standards guiding when and how versions should be incremented.
+Version:             0.1.0.0
+
+-- A short (one-line) description of the package.
+Synopsis:            Tools and a library for working with Trajectory.
+
+-- A longer description of the package.
+Description:         Trajectory <http://apptrajectory.com/> is a project
+                     estimation and management tool for project managers,
+                     product managers, developers, and designers. It is heavily
+                     inspired by, but improves upon, Basecamp and Pivotal
+                     Tracker. Like Basecamp, it has a place to organize ideas
+                     and exchange free-form feedback; like Pivotal Tracker it
+                     has the concept of stories with points instead of time
+                     estimations, and it calculates time estimations based on
+                     past performance. It also weds the two concepts,
+                     connecting stories with ideas.
+                     .
+                     This is a collection of tools and libraries for interacting with Trajectory.
+                     .
+                     It provides the @lsstory@ tool, a command-line app that
+                     can list stories, filtering by source idea, milestone,
+                     point, assignment, whether design or development is
+                     needed; it can filter on the state (unstarted, started,
+                     finished, done); it can filter on the iteration. It can
+                     also be used simply to tell you the next unstarted,
+                     unassigned development story to work on.
+                     .
+                     To build more with this package, look into the
+                     @Trajectory.API@ module.
+                     .
+                     Trajectory is made by thoughtbot <http://thoughtbot.com/>.
+
+-- The license under which the package is released.
+License:             BSD3
+
+-- The file containing the license text.
+License-file:        LICENSE
+
+-- The package author(s).
+Author:              Mike Burns
+
+-- An email address to which users can send suggestions, bug reports,
+-- and patches.
+Maintainer:          mike@mike-burns.com
+
+Homepage:            https://github.com/mike-burns/trajectory
+
+-- A copyright notice.
+Copyright:           Copyright 2011 Mike Burns
+
+Category:            Network APIs
+
+Build-type:          Simple
+
+-- Extra files to be distributed with the package, such as examples or
+-- a README.
+Extra-source-files:  README.md, LICENSE
+
+-- Constraint on the version of Cabal needed to build this package.
+Cabal-version:       >=1.6
+
+source-repository head
+  type: git
+  location: git://github.com/mike-burns/trajectory.git
+
+
+Executable inittj
+  -- .hs or .lhs file containing the Main module.
+  Main-is:             InitTj.hs
+  
+  -- Packages needed in order to build this package.
+  Build-depends:      base >= 4.0 && < 5.0
+                     ,regexpr
+                     ,aeson == 0.5.0.0
+                     ,attoparsec == 0.10.1.0
+                     ,bytestring
+                     ,text
+                     ,containers
+                     ,cmdargs
+                     ,unordered-containers == 0.1.4.3
+  
+  -- Modules not exported by this package.
+  Other-modules:       Trajectory.Private.Config
+  
+  -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.
+  -- Build-tools:         
+  
+Executable lsstory
+  -- .hs or .lhs file containing the Main module.
+  Main-is:             LsStory.hs
+  
+  -- Packages needed in order to build this package.
+  Build-depends:      base >= 4.0 && < 5.0
+                     ,uri
+                     ,http-enumerator == 0.7.2.1
+                     ,bytestring
+                     ,aeson == 0.5.0.0
+                     ,attoparsec == 0.10.1.0
+                     ,http-types
+                     ,text
+                     ,containers
+                     ,cmdargs
+                     ,unordered-containers == 0.1.4.3
+  
+  -- Modules not exported by this package.
+  Other-modules:       Trajectory.Private.Config
+  
+  -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.
+  -- Build-tools:         
+
+Library
+  -- Modules exported by the library.
+  Exposed-modules: Trajectory.Types, Trajectory.API
+
+  -- Packages needed in order to build this package.
+  Build-depends:  base >= 4.0 && < 5.0
+                 ,aeson == 0.5.0.0
+                 ,attoparsec == 0.10.1.0
+                 ,bytestring
+                 ,http-enumerator == 0.7.2.1
+                 ,uri
+                 ,http-types
+  
+  -- Modules not exported by this package.
+  -- Other-modules:       
+  
+  -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.
+  -- Build-tools:         
+  
