diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for tinyapp
+
+## 0.1.0.0 -- 2024-07-18
+
+* First version.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2024 Brian J. Cardiff
+
+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/src/TinyApp/Repl.hs b/src/TinyApp/Repl.hs
new file mode 100644
--- /dev/null
+++ b/src/TinyApp/Repl.hs
@@ -0,0 +1,48 @@
+-- | Build REPL apps
+module TinyApp.Repl where
+
+import Control.Exception
+import Control.Monad
+import System.IO
+
+-- | Signals whether the REPL should continue asking input from the user or exit.
+data ContinueExit = Continue | Exit
+  deriving (Eq, Show)
+
+-- | Defines a REPL application that is not allows to perform arbitrary IO while executing.
+data Sandbox state = Sandbox
+  { -- | Initial state
+    initialize :: state,
+    -- | The prompt to show. It can depend on the state
+    prompt :: state -> String,
+    -- | Process the user input given the current state
+    -- Returns the next state, the output and whether to continue or not the program
+    update :: String -> state -> (state, String, ContinueExit)
+  }
+
+-- | Executes the REPL application.
+runRepl :: Sandbox s -> IO ()
+runRepl = Control.Monad.void . runRepl'
+
+-- | Executes the REPL application returning its final state.
+runRepl' :: forall s. Sandbox s -> IO s
+runRepl' config =
+  let go :: s -> IO s
+      go state = do
+        System.IO.putStr (config.prompt state)
+        -- Since the prompt does not finish in a newline we force a flush right after
+        hFlush stdout
+        -- When Control-D an exception is thrown
+        input <- Control.Exception.catch @IOException (Just <$> System.IO.getLine) (\_ -> pure Nothing)
+        case input of
+          Nothing -> pure state
+          Just input' -> do
+            let (state', output, continue) = config.update input' state
+            System.IO.putStrLn output
+            case continue of
+              Continue ->
+                go state'
+              Exit ->
+                pure state'
+   in do
+        go config.initialize
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,4 @@
+module Main (main) where
+
+main :: IO ()
+main = putStrLn "Test suite not yet implemented."
diff --git a/tinyapp.cabal b/tinyapp.cabal
new file mode 100644
--- /dev/null
+++ b/tinyapp.cabal
@@ -0,0 +1,43 @@
+cabal-version:      3.0
+name:               tinyapp
+version:            0.1.0.0
+synopsis:           Library to build tiny apps in Haskell
+description:        Library to build tiny apps in Haskell such a REPLs
+homepage:           https://github.com/bcardiff/haskell-tinyapp
+license:            MIT
+license-file:       LICENSE
+author:             Brian J. Cardiff
+maintainer:         bcardiff@gmail.com
+-- copyright:
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+-- extra-source-files:
+category:           REPL, Command Line
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:
+        TinyApp.Repl
+    -- other-modules:
+    -- other-extensions:
+    build-depends:
+        base ^>=4.17.2.1
+    hs-source-dirs:   src
+    default-language: GHC2021
+    default-extensions:
+        OverloadedRecordDot
+
+test-suite tinyapp-test
+    import:           warnings
+    default-language: GHC2021
+    -- other-modules:
+    -- other-extensions:
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    build-depends:
+        base ^>=4.17.2.1,
+        tinyapp
