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/handsy.cabal b/handsy.cabal
new file mode 100644
--- /dev/null
+++ b/handsy.cabal
@@ -0,0 +1,26 @@
+name:          handsy
+version:       0.0.1
+synopsis:      A DSL to describe common shell operations and interpeters for running them locally and remotely.
+-- description:   
+license:       BSD3
+author:        Utku Demir
+maintainer:    utdemir@gmail.com
+category:      System
+build-type:    Simple
+cabal-version: >=1.10
+
+source-repository head
+  type:     git
+  location: https://github.com/utdemir/master
+
+library
+  exposed-modules:  System.Handsy
+                    System.Handsy.Remote
+  build-depends:    base >=4.7 && <4.8
+                  , bytestring
+                  , process
+                  , transformers
+                  , free
+                  , process-extras 
+  hs-source-dirs:   src
+  default-language: Haskell2010
diff --git a/src/System/Handsy.hs b/src/System/Handsy.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Handsy.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE DeriveFunctor     #-}
+{-# LANGUAGE FlexibleContexts  #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TemplateHaskell   #-}
+
+module System.Handsy where
+
+import           Prelude                   hiding (readFile, writeFile)
+
+import           Control.Monad.IO.Class
+import qualified Data.ByteString.Char8     as B
+import           System.Exit
+
+import           Control.Monad.Free.TH
+import           Control.Monad.Trans.Free
+import           System.Process.ByteString
+
+-- | Base functor for our dsl
+data HandsyF k = Command      String [String] B.ByteString ((ExitCode, B.ByteString, B.ByteString) -> k)
+               | ReadFile     String                       (B.ByteString -> k)
+               | WriteFile    String B.ByteString          (() -> k)
+             deriving (Functor)
+
+makeFree ''HandsyF
+
+type Handsy = FreeT HandsyF IO
+
+run :: Handsy a -> IO a
+run h = do
+  x <- runFreeT h
+  case x of
+    Pure r -> return r
+    Free (ReadFile fp next)
+      -> B.readFile fp >>= run . next
+    Free (WriteFile fp str next)
+      -> B.writeFile fp str >>= run . next
+    Free (Command prg args stdin next)
+      -> readProcessWithExitCode prg args stdin >>= run . next
diff --git a/src/System/Handsy/Remote.hs b/src/System/Handsy/Remote.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Handsy/Remote.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module System.Handsy.Remote where
+
+import           Prelude                  hiding (readFile, writeFile)
+
+import           System.Handsy
+
+import qualified Data.ByteString.Char8    as B
+
+import           Control.Monad.IO.Class
+import           Control.Monad.Trans.Free
+
+data RemoteOptions =
+  RemoteOptions {
+    sshCommand :: (String, [String])
+  }
+
+runRemote :: RemoteOptions -> Handsy a -> IO a
+runRemote opts h = do
+  x <- runFreeT h
+  case x of
+    Pure r -> return r
+    Free (ReadFile fp next) -> do
+      (_, stdin,  _) <- runSsh "cat" [fp] ""
+      runRemote opts (next stdin)
+    Free (WriteFile fp str next) -> do
+      _ <- runSsh "dd" ["of=" ++ fp] str -- TODO: Escape necessary?
+      runRemote opts (next ())
+    Free (Command prg args stdin next) -> runSsh prg args stdin >>= run . next
+
+  where
+    (ssh, sshOpts) = sshCommand opts
+    runSsh prg args stdin = run (command ssh (sshOpts ++ prg : args) stdin)
