diff --git a/elm-init.cabal b/elm-init.cabal
--- a/elm-init.cabal
+++ b/elm-init.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                elm-init
-version:             1.0.0.0
+version:             1.0.0.1
 synopsis:            Set up basic structure for an elm project
 description:
   Initialize a new empty elm project with some basic scaffolding according to 'https://github.com/evancz/elm-architecture-tutorial'.
@@ -28,7 +28,11 @@
 
 executable elm-init
   main-is:             Main.hs
-  -- other-modules:
+  other-modules:
+    ElmInit
+    ElmInit.Types
+    ElmInit.Interact
+    ElmInit.Util
   -- other-extensions:
   build-depends:
       base >=4.8 && <5.0,
diff --git a/src/ElmInit.hs b/src/ElmInit.hs
new file mode 100644
--- /dev/null
+++ b/src/ElmInit.hs
@@ -0,0 +1,15 @@
+module ElmInit
+  ( UserDecisions(..)
+  , CmdArgs(..)
+  , Result
+  , askChoices
+  , askChoicesWithOther
+  , exists
+  , verifyElmVersion
+  , makePackage
+  , flattenMaybe
+  ) where
+
+import ElmInit.Interact
+import ElmInit.Types
+import ElmInit.Util
diff --git a/src/ElmInit/Interact.hs b/src/ElmInit/Interact.hs
new file mode 100644
--- /dev/null
+++ b/src/ElmInit/Interact.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module ElmInit.Interact
+  ( askChoicesWithOther
+  , askChoices
+  ) where
+
+
+import qualified Control.Arrow            as Arrow (first)
+import           Control.Applicative      ((<$>), (<*>))
+import           Data.Text                (append)
+import           Data.Bool                (bool)
+import           ElmInit.Util             (getOr, enumerate)
+import           Data.Text                as Text (Text, intercalate, pack)
+import           Data.Text.IO             as TextIO (getLine, putStrLn)
+import           Prelude                  hiding (getLine, putStrLn)
+
+
+askChoices :: Text -> Int -> [Text] -> IO Text
+askChoices =
+  ((fmap <$> (!!) <*>) .) . askChoices'
+
+
+askChoices' :: Text -> Int -> [Text] -> IO Int
+askChoices' message selected choices =
+  putStrLn message >>
+  ask out
+
+  where
+    (l1, selectedElem : l2tail) = splitAt selected choices
+    out =
+      intercalate
+        "\n"
+        (normFormat 1 l1
+          ++ (selectedFormat selected selectedElem : normFormat (selected + 1) l2tail))
+
+    enumF = flip append " )  " . pack . show
+    enumFn = append "    " . enumF
+    enumFs = append "  * " . enumF
+    normFormat = (map (uncurry append . Arrow.first enumFn) .) . enumerate
+    selectedFormat x y = (flip append y . enumFs) x
+
+    ask a =
+      putStrLn a >>
+      getOr selected >>=
+        (bool
+          (putStrLn "invalid choice, please choose again" >>
+          ask a)
+          <$> return
+          <*> (<= length choices))
+
+
+askChoicesWithOther :: Text -> Int -> (Text -> Either Text a) -> [Text] -> IO a
+askChoicesWithOther m s trans l =
+    askChoices' m s (l ++ ["other (specify)"])
+    >>= (flip bool getAlternative
+          <$> either (const $ error "No parse") return . trans . (l !!)
+          <*> (== length l))
+
+  where
+    getAlternative =
+      putStrLn "please enter an alternative" >>
+      getLine >>=
+        (either
+          (\message ->
+            putStrLn "Invalid input:" >>
+            putStrLn message >>
+            getAlternative
+          )
+          return
+          . trans)
diff --git a/src/ElmInit/Types.hs b/src/ElmInit/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/ElmInit/Types.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module ElmInit.Types
+  ( Result
+  , CmdArgs(..)
+  , UserDecisions(..)
+  , ElmPackage(..)
+  , readVersion
+  , makePackage
+  , verifyElmVersion
+  , readOneVersion
+  ) where
+
+
+import           Data.Aeson                   as Aeson (ToJSON, object, (.=),
+                                              toJSON, Value)
+import           Control.Applicative          ((<*>), (<$>))
+import           Control.Monad                ((<=<))
+import           Data.Version                 (Version(Version), makeVersion,
+                                              showVersion, parseVersion)
+import           Data.Text                    (pack, Text)
+import           Text.ParserCombinators.ReadP (readP_to_S)
+
+
+
+type Result = Either Text ()
+
+
+data CmdArgs = CmdArgs { workingDirectory :: FilePath }
+
+
+data UserDecisions = Default { projectName  :: Text
+                             , sourceFolder :: FilePath
+                             , version      :: Version
+                             , summary      :: Text
+                             , repository   :: Text
+                             , license      :: Text
+                             , elmVersion   :: Text
+                             , mainFileName :: String
+                             , addIndex     :: Bool
+                             }
+
+
+data ElmPackage = ElmPackage { pkgVersion        :: Version
+                             , pkgSummary        :: Text
+                             , pkgRepository     :: Text
+                             , pkgLicense        :: Text
+                             , pkgDependencies   :: Aeson.Value
+                             , pkgExposedModules :: [Text]
+                             , pkgElmVersion     :: Text
+                             , pkgSourceDirs     :: [Text]
+                             }
+
+
+readVersion :: String -> [(Version, String)]
+readVersion = readP_to_S parseVersion
+
+
+instance Aeson.ToJSON ElmPackage where
+  toJSON = object . sequenceA
+    [ ("version" .=)          . showVersion . pkgVersion
+    , ("summary" .=)          . pkgSummary
+    , ("repository" .=)       . pkgRepository
+    , ("license" .=)          . pkgLicense
+    , ("dependencies" .=)     . pkgDependencies
+    , ("exposed-modules" .=)  . pkgExposedModules
+    , ("elm-version" .=)      . pkgElmVersion
+    , ("source-directories".=). pkgSourceDirs
+    ]
+
+
+emptyDecisions :: UserDecisions
+emptyDecisions =
+  Default { summary       = ""
+          , repository    = ""
+          , version       = makeVersion [0, 0, 0]
+          , license       = ""
+          , sourceFolder  = ""
+          , projectName   = ""
+          , elmVersion    = ""
+          , mainFileName  = "Main.elm"
+          , addIndex      = True
+          }
+
+
+makePackage :: UserDecisions -> ElmPackage
+makePackage = ElmPackage
+  <$> version
+  <*> summary
+  <*> repository
+  <*> license
+  <*> const (object [("elm-lang/core", "2.0.0 <= v < 3.0.0")])
+  <*> const []
+  <*> elmVersion
+  <*> (:[]) . pack . sourceFolder
+
+
+readOneVersion :: String -> Either Text Version
+readOneVersion = verif . readVersion
+  where
+    verif ((v, []):_) = return v
+    verif []          = Left "Version must have this structure: 1.2.3"
+    verif (_:xs)      = verif xs
+
+
+verifyElmVersion :: String -> Either Text Version
+verifyElmVersion = hasElmStructure <=< readOneVersion
+  where
+    hasElmStructure v@(Version [ _, _, _ ] []) = Right v
+    hasElmStructure _                          = Left "Version must have this structure: 1.2.3"
diff --git a/src/ElmInit/Util.hs b/src/ElmInit/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/ElmInit/Util.hs
@@ -0,0 +1,38 @@
+module ElmInit.Util
+  ( flattenMaybe
+  , exists
+  , getOr
+  , enumerate
+  ) where
+
+
+import           Control.Applicative ((<$>), (<*>))
+import           Data.Maybe          (fromMaybe)
+import           Data.Bool           (bool)
+import           System.Directory    (doesFileExist, doesDirectoryExist)
+import           Control.Exception   (catch, IOException)
+
+
+flattenMaybe :: Maybe (Maybe a) -> Maybe a
+flattenMaybe = fromMaybe Nothing
+
+
+exists :: FilePath -> IO Bool
+exists =
+  (>>=)
+  <$> doesFileExist
+  <*> (flip bool
+        (return True)
+        . doesDirectoryExist)
+
+
+getOr :: Read a => a -> IO a
+getOr =
+  catch readLn . handler
+  where
+    handler :: a -> IOException -> IO a
+    handler = const . return
+
+
+enumerate :: Int -> [a] -> [(Int,a)]
+enumerate from l = zip [from..(length l)] l
