diff --git a/Shelly.hs b/Shelly.hs
--- a/Shelly.hs
+++ b/Shelly.hs
@@ -25,7 +25,7 @@
 module Shelly
        (
          -- * Entering Sh.
-         Sh, ShIO, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands
+         Sh, ShIO, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands, tracing
 
          -- * Running external commands.
          , run, run_, cmd, (-|-), lastStderr, setStdin
@@ -33,7 +33,7 @@
          , sshPairs, sshPairs_
  
          -- * Modifying and querying environment.
-         , setenv, getenv, getenv_def, appendToPath
+         , setenv, get_env, get_env_text, getenv, getenv_def, appendToPath
 
          -- * Environment directory
          , cd, chdir, pwd
@@ -109,6 +109,7 @@
 import qualified Filesystem.Path.CurrentOS as FP
 
 import System.Directory ( setPermissions, getPermissions, Permissions(..), getTemporaryDirectory, findExecutable ) 
+import Data.Char (isDigit)
 
 {- GHC won't default to Text with this, even with extensions!
  - see: http://hackage.haskell.org/trac/ghc/ticket/6030
@@ -156,6 +157,7 @@
 instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where
     cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x])
 
+
 -- | variadic argument version of run.
 -- The syntax is more convenient, but more importantly it also allows the use of a FilePath as a command argument.
 -- So an argument can be a Text or a FilePath.
@@ -315,9 +317,14 @@
 -- repercussions if you are doing hundreds of thousands of filesystem
 -- operations. You will want to handle these issues differently in those cases.
 cd :: FilePath -> Sh ()
-cd = absPath >=> \dir -> do
-            trace $ "cd " `mappend` toTextIgnore dir
-            modify $ \st -> st { sDirectory = dir }
+cd = canonic >=> cd'
+  where
+    cd' dir = do
+        trace $ "cd " `mappend` tdir
+        unlessM (test_d dir) $ errorExit $ "not a directory: " `mappend` tdir
+        modify $ \st -> st { sDirectory = dir }
+      where
+        tdir = toTextIgnore dir
 
 -- | "cd", execute a Sh action in the new directory and then pop back to the original directory
 chdir :: FilePath -> Sh a -> Sh a
@@ -326,6 +333,10 @@
   cd dir
   action `finally_sh` cd d
 
+-- | chdir, but first create the directory if it does not exit
+chdir_p :: FilePath -> Sh a -> Sh a
+chdir_p d action = mkdir_p d >> chdir d action
+
   
 -- | apply a String IO operations to a Text FilePath
 {-
@@ -360,6 +371,7 @@
 exit 0 = liftIO (exitWith ExitSuccess) `tag` "exit 0"
 exit n = liftIO (exitWith (ExitFailure n)) `tag` ("exit " `mappend` LT.pack (show n))
 
+-- | echo a message and exit with status 1
 errorExit :: Text -> Sh ()
 errorExit msg = echo msg >> exit 1
 
@@ -389,10 +401,6 @@
   (trace . mappend "which " . toTextIgnore) fp
   (liftIO . findExecutable . unpack >=> return . fmap pack) fp
 
--- | A monadic-conditional version of the "when" guard.
-whenM :: Monad m => m Bool -> m () -> m ()
-whenM c a = c >>= \res -> when res a
-
 -- | A monadic-conditional version of the "unless" guard.
 unlessM :: Monad m => m Bool -> m () -> m ()
 unlessM c a = c >>= \res -> unless res a
@@ -440,6 +448,7 @@
 rm :: FilePath -> Sh ()
 rm = absPath >=> \f -> do
   trace $ "rm" `mappend` toTextIgnore f
+  -- TODO: better error message for removeFile (give filename)
   canonic f >>= liftIO . removeFile
 
 -- | Set an environment variable. The environment is maintained in Sh
@@ -460,17 +469,30 @@
   where
     path_env = "PATH"
 
--- | Fetch the current value of an environment variable. Both empty and
--- non-existent variables give empty string as a result.
+-- | Fetch the current value of an environment variable.
+-- if non-existant or empty text, will be Nothing
+get_env :: Text -> Sh (Maybe Text)
+get_env k = do
+  mval <- return . fmap LT.pack . lookup (LT.unpack k) =<< gets sEnvironment
+  return $ case mval of
+    Nothing -> Nothing
+    j@(Just val) -> if LT.null val then Nothing else j
+
 getenv :: Text -> Sh Text
 getenv k = getenv_def k ""
+{-# DEPRECATED getenv "use get_env or get_env_text" #-}
 
 -- | Fetch the current value of an environment variable. Both empty and
--- non-existent variables give the default value as a result
+-- non-existent variables give empty string as a result.
+get_env_text :: Text -> Sh Text
+get_env_text k = getenv_def k ""
+
+-- | Fetch the current value of an environment variable. Both empty and
+-- non-existent variables give the default Text value as a result
 getenv_def :: Text -> Text -> Sh Text
-getenv_def k d = gets sEnvironment >>=
-  return . LT.pack . fromMaybe (LT.unpack d) . lookup (LT.unpack k)
+getenv_def d = get_env >=> return . fromMaybe d
 
+
 -- | Create a sub-Sh in which external command outputs are not echoed and
 -- commands are not printed.
 -- See 'sub'.
@@ -507,6 +529,14 @@
       newState <- get
       put oldState { sTrace = sTrace oldState `mappend` sTrace newState  }
 
+-- | Create a sub-Sh where commands are not traced
+-- Defaults to True.
+-- You should only set to False temporarily for very specific reasons
+tracing :: Bool -> Sh a -> Sh a
+tracing shouldTrace action = sub $ do
+  modify $ \st -> st { sTracing = shouldTrace }
+  action
+
 -- | Create a sub-Sh with shell character escaping on or off.
 -- Defaults to True.
 -- Setting to False allows for shell wildcard such as * to be expanded by the shell along with any other special shell characters.
@@ -534,6 +564,7 @@
                    , sPrintCommands = False
                    , sRun = runCommand
                    , sEnvironment = environment
+                   , sTracing = True
                    , sTrace = B.fromText ""
                    , sDirectory = dir }
   stref <- liftIO $ newIORef def
@@ -549,10 +580,33 @@
   liftIO $ runSh caught stref
   where
     throwExplainedException :: Exception exception => exception -> Sh a
-    throwExplainedException ex = get >>=
-      liftIO . throwIO . ReThrownException ex . errorMsg . LT.unpack .  B.toLazyText . sTrace
-    errorMsg trc = "Ran commands: \n" `mappend` trc
+    throwExplainedException ex = get >>= errorMsg >>= liftIO . throwIO . ReThrownException ex
+    errorMsg st = do
+      let trc = B.toLazyText . sTrace $ st
+      d <- pwd
+      sf <- shellyFile
+      let f = d</>shelly_dir</>sf
+      (writefile f trc >> return ("log of commands saved to: " `mappend` encodeString f))
+        `catchany_sh` (\_ -> return $ "Ran commands: \n" `mappend` LT.unpack trc)
 
+    shelly_dir = ".shelly"
+    shellyFile = chdir_p shelly_dir $ do
+      fs <- ls "."
+      return $ pack $ show (nextNum fs) `mappend` ".txt"
+
+    nextNum :: [FilePath] -> Int
+    nextNum [] = 1
+    nextNum fs = (+ 1) . maximum . map (readDef 1 . filter isDigit . unpack . filename) $ fs
+
+-- from safe package
+readDef :: Read a => a -> String -> a
+readDef def = fromMaybe def . readMay
+  where
+    readMay :: Read a => String -> Maybe a
+    readMay s = case [x | (x,t) <- reads s, ("","") <- lex t] of
+                  [x] -> Just x
+                  _ -> Nothing
+
 data RunFailed = RunFailed FilePath [Text] Int Text deriving (Typeable)
 
 instance Show RunFailed where
@@ -719,19 +773,21 @@
 -- | Copy a file, or a directory recursively.
 cp_r :: FilePath -> FilePath -> Sh ()
 cp_r from' to' = do
-    fromDir <- absPath from'
-    from_d <- (test_d fromDir)
-    if not from_d then cp from' to' else do
+    from <- absPath from'
+    fromIsDir <- (test_d from)
+    if not fromIsDir then cp from' to' else do
        to <- absPath to'
-       trace $ "cp -r " `mappend` toTextIgnore fromDir `mappend` " " `mappend` toTextIgnore to
+       trace $ "cp -r " `mappend` toTextIgnore from `mappend` " " `mappend` toTextIgnore to
        toIsDir <- test_d to
-       toDir <- if toIsDir
-               then return $ fromDir </> to
-               else mkdir to >> return to
-       when (fromDir == toDir) $ liftIO $ throwIO $ userError $ LT.unpack $ "cp_r: " `mappend`
-         toTextIgnore fromDir `mappend` " and " `mappend` toTextIgnore toDir `mappend` " are identical"
 
-       ls fromDir >>= mapM_ (\item -> cp_r (fromDir FP.</> filename item) (toDir FP.</> filename item))
+       when (from == to) $ liftIO $ throwIO $ userError $ LT.unpack $ "cp_r: " `mappend`
+         toTextIgnore from `mappend` " and " `mappend` toTextIgnore to `mappend` " are identical"
+
+       finalTo <- if not toIsDir then mkdir to >> return to else do
+                   let d = to </> dirname (addTrailingSlash from)
+                   mkdir_p d >> return d 
+
+       ls from >>= mapM_ (\item -> cp_r (from FP.</> filename item) (finalTo FP.</> filename item))
 
 -- | Copy a file. The second path could be a directory, in which case the
 -- original file name is used, in that directory.
diff --git a/Shelly/Base.hs b/Shelly/Base.hs
--- a/Shelly/Base.hs
+++ b/Shelly/Base.hs
@@ -13,7 +13,10 @@
     echo, echo_n, echo_err, echo_n_err, inspect, inspect_err,
     catchany,
     liftIO, (>=>),
-    eitherRelativeTo, relativeTo, maybeRelativeTo
+    eitherRelativeTo, relativeTo, maybeRelativeTo,
+    whenM
+    -- * utitlities not yet exported
+    , addTrailingSlash
   ) where
 
 import Prelude hiding ( FilePath, catch )
@@ -21,7 +24,8 @@
 import System.Process( ProcessHandle )
 import System.IO ( Handle, hFlush, stderr, stdout )
 
-import Control.Monad ( (>=>) ) 
+import Control.Monad (when, (>=>) )
+import Control.Applicative(Applicative)
 import Filesystem (isDirectory, listDirectory)
 import System.PosixCompat.Files( getSymbolicLinkStatus, isSymbolicLink )
 import Filesystem.Path.CurrentOS (FilePath, encodeString, relative)
@@ -45,7 +49,7 @@
 
 newtype Sh a = Sh {
       unSh :: ReaderT (IORef State) IO a
-  } deriving (Monad, MonadIO, MonadReader (IORef State), Functor)
+  } deriving (Applicative, Monad, MonadIO, MonadReader (IORef State), Functor)
 
 runSh :: Sh a -> IORef State -> IO a
 runSh = runReaderT . unSh
@@ -58,9 +62,14 @@
                      , sPrintCommands :: Bool -- ^ print command that is executed
                      , sRun :: FilePath -> [Text] -> Sh (Handle, Handle, Handle, ProcessHandle)
                      , sEnvironment :: [(String, String)]
+                     , sTracing :: Bool
                      , sTrace :: B.Builder
                      }
 
+-- | A monadic-conditional version of the "when" guard.
+whenM :: Monad m => m Bool -> m () -> m ()
+whenM c a = c >>= \res -> when res a
+
 -- | Makes a relative path relative to the current Sh working directory.
 -- An absolute path is returned as is.
 -- To create an absolute path, use 'absPath'
@@ -111,6 +120,7 @@
              Left _ -> Nothing
 
 
+-- | add a trailing slash to ensure the path indicates a directory
 addTrailingSlash :: FilePath -> FilePath
 addTrailingSlash p =
   if FP.null (FP.filename p) then p else
@@ -174,7 +184,9 @@
 -- | internally log what occured.
 -- Log will be re-played on failure.
 trace :: Text -> Sh ()
-trace msg = modify $ \st -> st { sTrace = sTrace st `mappend` B.fromLazyText msg `mappend` "\n" }
+trace msg =
+  whenM (gets sTracing) $ modify $
+    \st -> st { sTrace = sTrace st `mappend` B.fromLazyText msg `mappend` "\n" }
 
 -- | List directory contents. Does *not* include \".\" and \"..\", but it does
 -- include (other) hidden files.
diff --git a/Shelly/Pipe.hs b/Shelly/Pipe.hs
new file mode 100644
--- /dev/null
+++ b/Shelly/Pipe.hs
@@ -0,0 +1,676 @@
+{-# LANGUAGE 
+        FlexibleInstances, 
+        TypeSynonymInstances, 
+        TypeFamilies,
+        ExistentialQuantification #-}
+-- | This module is a wrapper for the module "Shelly". 
+-- The only difference is a main type "Sh". In this module 
+-- "Sh" contains a list of results. Actual definition of the type "Sh" is:
+--
+-- > import qualified Shelly as S
+-- >
+-- > newtype Sh a = Sh { unSh :: S.Sh [a] }
+--
+-- This definition can simplify some filesystem commands. 
+-- A monad bind operator becomes a pipe operator and we can write
+--
+-- > findExt ext = findWhen (pure . hasExt ext)
+-- >
+-- > main :: IO ()
+-- > main = shs $ do
+-- >     mkdir "new"
+-- >     findExt "hs"  "." >>= flip cp "new"
+-- >     findExt "cpp" "." >>= rm_f 
+-- >     liftIO $ putStrLn "done"
+--
+-- Monad methods "return" and ">>=" behave like methods for
+-- @ListT Shelly.Sh@, but ">>" forgets the number of 
+-- the empty effects. So the last line prints @\"done\"@ only once. 
+--
+-- I highly recommend putting the following at the top of your program,
+-- otherwise you will likely need either type annotations or type conversions
+--
+-- > {-# LANGUAGE OverloadedStrings #-}
+-- > {-# LANGUAGE ExtendedDefaultRules #-}
+-- > {-# OPTIONS_GHC -fno-warn-type-defaults #-}
+-- > import Shelly
+-- > import Data.Text.Lazy as LT
+-- > default (LT.Text)
+module Shelly.Pipe
+       (
+         -- * Entering Sh.
+         Sh, shs, shelly, sub, silently, verbosely, escaping, print_stdout, print_commands
+         -- * List functions
+         , roll, unroll, liftSh
+         -- * Running external commands.
+         , run, run_, cmd
+         , (-|-), lastStderr, setStdin
+         , command, command_, command1, command1_
+         , sshPairs, sshPairs_
+ 
+         -- * Modifying and querying environment.
+         , setenv, get_env, get_env_text, getenv_def, appendToPath
+
+         -- * Environment directory
+         , cd, chdir, pwd
+
+         -- * Printing
+         , echo, echo_n, echo_err, echo_n_err, inspect, inspect_err
+         , tag, trace, show_command
+
+         -- * Querying filesystem.
+         , ls, lsT, test_e, test_f, test_d, test_s, which
+
+         -- * Filename helpers
+         , absPath, (</>), (<.>), canonic, canonicalize, relPath, relativeTo
+         , hasExt
+
+         -- * Manipulating filesystem.
+         , mv, rm, rm_f, rm_rf, cp, cp_r, mkdir, mkdir_p
+
+         -- * reading/writing Files
+         , readfile, writefile, appendfile, touchfile, withTmpDir
+
+         -- * find functions 
+         , find, findWhen, findFold
+         , findDirFilter, findDirFilterWhen, findFoldDirFilter
+
+         -- * exiting the program
+         , exit, errorExit, terror
+
+         -- * Exceptions
+         , catchany, catch_sh, finally_sh 
+         , ShellyHandler(..), catches_sh
+         , catchany_sh
+
+         -- * convert between Text and FilePath
+         , toTextIgnore, toTextWarn, fromText
+
+         -- * Utilities.
+         , (<$>), (<$$>), whenM, unlessM, time
+
+         -- * Re-exported for your convenience
+         , liftIO, when, unless, FilePath
+
+         -- * internal functions for writing extensions
+         , get, put
+         ) where
+
+import Prelude hiding (FilePath)
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Trans
+import Control.Exception hiding (handle)
+
+import Filesystem.Path(FilePath)
+
+import qualified Shelly as S
+
+import Shelly(
+      (</>), (<.>), hasExt
+    , (<$$>), whenM, unlessM, toTextIgnore
+    , fromText, catchany)
+
+import Shelly.Base(State)
+
+import Data.Text.Lazy as LT hiding (concat, all, find, cons)
+
+default (LT.Text)
+
+
+-- | This type is a simple wrapper for a type "Shelly.Sh".
+-- "Sh" contains a list of results. 
+newtype Sh a = Sh { unSh :: S.Sh [a] }
+
+instance Functor Sh where
+    fmap f = Sh . fmap (fmap f) . unSh    
+
+instance Monad Sh where
+    return  = Sh . return . return 
+    a >>= f = Sh $ fmap concat $ mapM (unSh . f) =<< unSh a
+    a >> b  = Sh $ unSh a >> unSh b
+
+instance Applicative Sh where
+    pure = return
+    (<*>) = ap
+
+instance MonadPlus Sh where
+    mzero = Sh $ return []
+    mplus a b = Sh $ liftA2 (++) (unSh a) (unSh b)
+
+instance MonadIO Sh where
+    liftIO = sh1 liftIO
+
+-------------------------------------------------------
+-- converters
+
+sh0 :: S.Sh a -> Sh a
+sh0 = Sh . fmap return
+
+sh1 :: (a -> S.Sh b) -> (a -> Sh b) 
+sh1 f = \a -> sh0 (f a)
+
+sh2 :: (a1 -> a2 -> S.Sh b) -> (a1 -> a2 -> Sh b) 
+sh2 f = \a b -> sh0 (f a b)
+
+sh3 :: (a1 -> a2 -> a3 -> S.Sh b) -> (a1 -> a2 -> a3 -> Sh b) 
+sh3 f = \a b c -> sh0 (f a b c)
+
+sh4 :: (a1 -> a2 -> a3 -> a4 -> S.Sh b) -> (a1 -> a2 -> a3 -> a4 -> Sh b) 
+sh4 f = \a b c d -> sh0 (f a b c d)
+
+sh0s :: S.Sh [a] -> Sh a
+sh0s = Sh
+
+sh1s :: (a -> S.Sh [b]) -> (a -> Sh b) 
+sh1s f = \a -> sh0s (f a)
+
+{-  Just in case ...
+sh2s :: (a1 -> a2 -> S.Sh [b]) -> (a1 -> a2 -> Sh b) 
+sh2s f = \a b -> sh0s (f a b)
+
+sh3s :: (a1 -> a2 -> a3 -> S.Sh [b]) -> (a1 -> a2 -> a3 -> Sh b) 
+sh3s f = \a b c -> sh0s (f a b c)
+-}
+
+lift1 :: (S.Sh a -> S.Sh b) -> (Sh a -> Sh b)
+lift1 f = Sh . (mapM (f . return) =<< ) . unSh
+
+lift2 :: (S.Sh a -> S.Sh b -> S.Sh c) -> (Sh a -> Sh b -> Sh c)
+lift2 f a b = Sh $ join $ liftA2 (mapM2 f') (unSh a) (unSh b)
+    where f' = \x y -> f (return x) (return y)
+
+mapM2 :: Monad m => (a -> b -> m c)-> [a] -> [b] -> m [c]
+mapM2 f as bs = sequence $ liftA2 f as bs 
+
+-----------------------------------------------------------
+
+-- | Unpack list of results.
+unroll :: Sh a -> Sh [a]
+unroll = Sh . fmap return . unSh 
+
+-- | Pack list of results. It performs "concat" inside "Sh".
+roll :: Sh [a] -> Sh a
+roll = Sh . fmap concat . unSh
+
+-- | Transform result as list. It can be useful for filtering. 
+liftSh :: ([a] -> [b]) -> Sh a -> Sh b
+liftSh f = Sh . fmap f . unSh
+
+------------------------------------------------------------------
+-- Entering Sh
+
+-- | Enter a Sh from (Monad)IO. The environment and working directories are
+-- inherited from the current process-wide values. Any subsequent changes in
+-- processwide working directory or environment are not reflected in the
+-- running Sh.
+shelly :: MonadIO m => Sh a -> m [a]
+shelly = S.shelly . unSh
+
+-- | Performs "shelly" and then an empty action @return ()@. 
+shs :: MonadIO m => Sh () -> m ()
+shs a = shelly a >> return ()
+
+-- | Enter a sub-Sh that inherits the environment
+-- The original state will be restored when the sub-Sh completes.
+-- Exceptions are propagated normally.
+sub :: Sh a -> Sh a
+sub = lift1 S.sub
+
+-- | Create a sub-Sh in which external command outputs are not echoed.
+-- Also commands are not printed.
+-- See "sub".
+silently :: Sh a -> Sh a
+silently = lift1 S.silently
+
+-- | Create a sub-Sh in which external command outputs are echoed.
+-- Executed commands are printed
+-- See "sub".
+verbosely :: Sh a -> Sh a
+verbosely = lift1 S.verbosely
+
+-- | Create a sub-Sh with shell character escaping on or off
+escaping :: Bool -> Sh a -> Sh a
+escaping b = lift1 (S.escaping b)
+
+-- | Create a sub-Sh with stdout printing on or off
+print_stdout :: Bool -> Sh a -> Sh a
+print_stdout b = lift1 (S.print_stdout b)
+
+-- | Create a sub-Sh with command echoing on or off
+print_commands :: Bool -> Sh a -> Sh a
+print_commands b = lift1 (S.print_commands b)
+
+------------------------------------------------------------------
+-- Running external commands. 
+
+-- | Execute an external command. Takes the command name (no shell allowed,
+-- just a name of something that can be found via @PATH@; FIXME: setenv'd
+-- @PATH@ is not taken into account when finding the exe name)
+--
+-- "stdout" and "stderr" are collected. The "stdout" is returned as
+-- a result of "run", and complete stderr output is available after the fact using
+-- "lastStderr" 
+--
+-- All of the stdout output will be loaded into memory
+-- You can avoid this but still consume the result by using "run_",
+-- If you want to avoid the memory and need to process the output then use "runFoldLines".
+run :: FilePath -> [Text] -> Sh Text
+run a b = sh0 $ S.run a b
+
+-- | The same as "run", but return () instead of the stdout content.
+run_ :: FilePath -> [Text] -> Sh ()
+run_ a b = sh0 $ S.run_ a b
+
+-- | Pipe operator. set the stdout the first command as the stdin of the second.
+(-|-) :: Sh Text -> Sh b -> Sh b
+(-|-) = lift2 (S.-|-)
+
+-- | The output of last external command. See "run".
+lastStderr :: Sh Text
+lastStderr = sh0 S.lastStderr
+
+-- | set the stdin to be used and cleared by the next "run".
+setStdin :: Text -> Sh ()
+setStdin = sh1 S.setStdin 
+
+-- | bind some arguments to run for re-use
+-- Example: @monit = command "monit" ["-c", "monitrc"]@
+command :: FilePath -> [Text] -> [Text] -> Sh Text
+command = sh3 S.command
+
+-- | bind some arguments to "run_" for re-use
+-- Example: @monit_ = command_ "monit" ["-c", "monitrc"]@
+command_ :: FilePath -> [Text] -> [Text] -> Sh ()
+command_ = sh3 S.command_
+
+
+-- | bind some arguments to run for re-use, and expect 1 argument
+-- Example: @git = command1 "git" []; git "pull" ["origin", "master"]@
+command1 :: FilePath -> [Text] -> Text -> [Text] -> Sh Text
+command1 = sh4 S.command1
+
+-- | bind some arguments to run for re-use, and expect 1 argument
+-- Example: @git_ = command1_ "git" []; git+ "pull" ["origin", "master"]@
+command1_ :: FilePath -> [Text] -> Text -> [Text] -> Sh ()
+command1_ = sh4 S.command1_
+
+-- | run commands over SSH.
+-- An ssh executable is expected in your path.
+-- Commands are in the same form as 'run', but given as pairs
+--
+-- > sshPairs "server-name" [("cd", "dir"), ("rm",["-r","dir2"])]
+--
+-- This interface is crude, but it works for now.
+--
+-- Please note this sets 'escaping' to False: the commands will not be shell escaped.
+-- Internally the list of commands are combined with the string " && " before given to ssh.
+sshPairs :: Text -> [(FilePath, [Text])] -> Sh Text
+sshPairs = sh2 S.sshPairs
+
+-- | same as 'sshPairs', but returns ()
+sshPairs_ :: Text -> [(FilePath, [Text])] -> Sh ()
+sshPairs_ = sh2 S.sshPairs_
+
+-------------------------------------------------------------------
+-- Modifying and querying environment. 
+
+-- | Set an environment variable. The environment is maintained in Sh
+-- internally, and is passed to any external commands to be executed.
+setenv :: Text -> Text -> Sh ()
+setenv = sh2 S.setenv
+
+-- | Fetch the current value of an environment variable.
+-- if non-existant or empty text, will be Nothing
+get_env :: Text -> Sh (Maybe Text)
+get_env = sh1 S.get_env
+
+-- | Fetch the current value of an environment variable. Both empty and
+-- non-existent variables give empty string as a result.
+get_env_text :: Text -> Sh Text
+get_env_text = sh1 S.get_env_text
+
+-- | Fetch the current value of an environment variable. Both empty and
+-- non-existent variables give the default value as a result
+getenv_def :: Text -> Text -> Sh Text
+getenv_def = sh2 S.getenv_def
+
+-- | add the filepath onto the PATH env variable
+-- FIXME: only effects the PATH once the process is ran, as per comments in 'which'
+appendToPath :: FilePath -> Sh ()
+appendToPath = sh1 S.appendToPath
+
+-------------------------------------------------------------
+-- Environment directory 
+
+-- | Change current working directory of Sh. This does *not* change the
+-- working directory of the process we are running it. Instead, Sh keeps
+-- track of its own working directory and builds absolute paths internally
+-- instead of passing down relative paths. This may have performance
+-- repercussions if you are doing hundreds of thousands of filesystem
+-- operations. You will want to handle these issues differently in those cases.
+cd :: FilePath -> Sh ()
+cd = sh1 S.cd
+
+-- | "cd", execute a Sh action in the new directory and then pop back to the original directory
+chdir :: FilePath -> Sh a -> Sh a
+chdir p = lift1 (S.chdir p)
+
+-- | Obtain the current (Sh) working directory.
+pwd :: Sh FilePath
+pwd = sh0 S.pwd
+
+-----------------------------------------------------------------
+-- Printing 
+
+-- | Echo text to standard (error, when using _err variants) output. The _n
+-- variants do not print a final newline.
+echo, echo_n_err, echo_err, echo_n :: Text -> Sh ()
+
+echo        = sh1 S.echo
+echo_n_err  = sh1 S.echo_n_err
+echo_err    = sh1 S.echo_err
+echo_n      = sh1 S.echo_n
+
+-- | a print lifted into Sh
+inspect :: Show s => s -> Sh ()
+inspect = sh1 S.inspect
+
+-- | a print lifted into Sh using stderr
+inspect_err :: Show s => s -> Sh ()
+inspect_err = sh1 S.inspect_err
+
+-- | same as 'trace', but use it combinator style
+tag :: Sh a -> Text -> Sh a
+tag a t = lift1 (flip S.tag t) a
+
+-- | internally log what occured.
+-- Log will be re-played on failure.
+trace :: Text -> Sh ()
+trace = sh1 S.trace
+
+show_command :: FilePath -> [Text] -> Text
+show_command = S.show_command
+
+------------------------------------------------------------------
+-- Querying filesystem
+
+-- | List directory contents. Does *not* include \".\" and \"..\", but it does
+-- include (other) hidden files.
+ls :: FilePath -> Sh FilePath
+ls = sh1s S.ls
+
+-- | Get back [Text] instead of [FilePath]
+lsT :: FilePath -> Sh Text
+lsT = sh1s S.lsT
+
+-- | Does a path point to an existing filesystem object?
+test_e :: FilePath -> Sh Bool
+test_e = sh1 S.test_e
+
+-- | Does a path point to an existing file?
+test_f :: FilePath -> Sh Bool
+test_f = sh1 S.test_f
+
+-- | Does a path point to an existing directory?
+test_d :: FilePath -> Sh Bool
+test_d = sh1 S.test_d
+
+-- | Does a path point to a symlink?
+test_s :: FilePath -> Sh Bool
+test_s = sh1 S.test_s
+
+-- | Get a full path to an executable on @PATH@, if exists. FIXME does not
+-- respect setenv'd environment and uses @findExecutable@ which uses the @PATH@ inherited from the process
+-- environment.
+-- FIXME: findExecutable does not maintain a hash of existing commands and does a ton of file stats
+which :: FilePath -> Sh (Maybe FilePath)
+which = sh1 S.which
+
+---------------------------------------------------------------------
+-- Filename helpers
+
+-- | Make a relative path absolute by combining with the working directory.
+-- An absolute path is returned as is.
+-- To create a relative path, use 'path'.
+absPath :: FilePath -> Sh FilePath
+absPath = sh1 S.absPath
+
+-- | makes an absolute path.
+-- Like 'canonicalize', but on an exception returns 'path'
+canonic :: FilePath -> Sh FilePath
+canonic = sh1 S.canonic
+
+-- | Obtain a (reasonably) canonic file path to a filesystem object. Based on
+-- "canonicalizePath" in system-fileio.
+canonicalize :: FilePath -> Sh FilePath
+canonicalize = sh1 S.canonicalize
+
+-- | Makes a relative path relative to the current Sh working directory.
+-- An absolute path is returned as is.
+-- To create an absolute path, use 'absPath'
+relPath :: FilePath -> Sh FilePath
+relPath = sh1 S.relPath
+
+-- | make the second path relative to the first
+-- Uses 'Filesystem.stripPrefix', but will canonicalize the paths if necessary
+relativeTo :: FilePath -- ^ anchor path, the prefix
+           -> FilePath -- ^ make this relative to anchor path
+           -> Sh FilePath
+relativeTo = sh2 S.relativeTo
+
+-------------------------------------------------------------
+-- Manipulating filesystem
+
+-- | Currently a "renameFile" wrapper. TODO: Support cross-filesystem
+-- move. TODO: Support directory paths in the second parameter, like in "cp".
+mv :: FilePath -> FilePath -> Sh ()
+mv = sh2 S.mv
+
+-- | Remove a file.
+-- Does fail if the file does not exist (use 'rm_f' instead) or is not a file.
+rm :: FilePath -> Sh ()
+rm = sh1 S.rm
+
+-- | Remove a file. Does not fail if the file does not exist.
+-- Does fail if the file is not a file.
+rm_f :: FilePath -> Sh ()
+rm_f = sh1 S.rm_f
+
+-- | A swiss army cannon for removing things. Actually this goes farther than a
+-- normal rm -rf, as it will circumvent permission problems for the files we
+-- own. Use carefully.
+-- Uses 'removeTree'
+rm_rf :: FilePath -> Sh ()
+rm_rf = sh1 S.rm_rf
+
+-- | Copy a file. The second path could be a directory, in which case the
+-- original file name is used, in that directory.
+cp :: FilePath -> FilePath -> Sh ()
+cp = sh2 S.cp
+
+-- | Copy a file, or a directory recursively.
+cp_r :: FilePath -> FilePath -> Sh ()
+cp_r = sh2 S.cp_r
+
+-- | Create a new directory (fails if the directory exists).
+mkdir :: FilePath -> Sh ()
+mkdir = sh1 S.mkdir
+
+-- | Create a new directory, including parents (succeeds if the directory
+-- already exists).
+mkdir_p :: FilePath -> Sh ()
+mkdir_p = sh1 S.mkdir_p
+
+-- | (Strictly) read file into a Text.
+-- All other functions use Lazy Text.
+-- So Internally this reads a file as strict text and then converts it to lazy text, which is inefficient
+readfile :: FilePath -> Sh Text
+readfile = sh1 S.readfile
+
+-- | Write a Lazy Text to a file.
+writefile :: FilePath -> Text -> Sh ()
+writefile = sh2 S.writefile
+
+-- | Update a file, creating (a blank file) if it does not exist.
+touchfile :: FilePath -> Sh ()
+touchfile = sh1 S.touchfile
+
+-- | Append a Lazy Text to a file.
+appendfile :: FilePath -> Text -> Sh ()
+appendfile = sh2 S.appendfile
+
+-- | Create a temporary directory and pass it as a parameter to a Sh
+-- computation. The directory is nuked afterwards.
+withTmpDir :: (FilePath -> Sh a) -> Sh a
+withTmpDir f = Sh $ S.withTmpDir (unSh . f)
+
+-----------------------------------------------------------------
+-- find
+
+-- | List directory recursively (like the POSIX utility "find").
+-- listing is relative if the path given is relative.
+-- If you want to filter out some results or fold over them you can do that with the returned files.
+-- A more efficient approach is to use one of the other find functions.
+find :: FilePath -> Sh FilePath
+find = sh1s S.find
+
+-- | 'find' that filters the found files as it finds.
+-- Files must satisfy the given filter to be returned in the result.
+findWhen :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath
+findWhen p a = Sh $ S.findWhen (fmap and . unSh . p) a
+
+-- | Fold an arbitrary folding function over files froma a 'find'.
+-- Like 'findWhen' but use a more general fold rather than a filter.
+findFold :: (a -> FilePath -> Sh a) -> a -> FilePath -> Sh a
+findFold cons nil a = Sh $ S.findFold cons' nil' a
+    where nil'  = return nil
+          cons' as dir = unSh $ roll $ mapM (flip cons dir) as
+
+-- | 'find' that filters out directories as it finds
+-- Filtering out directories can make a find much more efficient by avoiding entire trees of files.
+findDirFilter :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath
+findDirFilter p a = Sh $ S.findDirFilter (fmap and . unSh . p) a
+    
+-- | similar 'findWhen', but also filter out directories
+-- Alternatively, similar to 'findDirFilter', but also filter out files
+-- Filtering out directories makes the find much more efficient
+findDirFilterWhen :: (FilePath -> Sh Bool) -- ^ directory filter
+                  -> (FilePath -> Sh Bool) -- ^ file filter
+                  -> FilePath -- ^ directory
+                  -> Sh FilePath
+findDirFilterWhen dirPred filePred a = 
+    Sh $ S.findDirFilterWhen  
+            (fmap and . unSh . dirPred) 
+            (fmap and . unSh . filePred)
+            a
+
+
+-- | like 'findDirFilterWhen' but use a folding function rather than a filter
+-- The most general finder: you likely want a more specific one
+findFoldDirFilter :: (a -> FilePath -> Sh a) -> a -> (FilePath -> Sh Bool) -> FilePath -> Sh a
+findFoldDirFilter cons nil p a = Sh $ S.findFoldDirFilter cons' nil' p' a
+    where p'    = fmap and . unSh . p
+          nil'  = return nil
+          cons' as dir = unSh $ roll $ mapM (flip cons dir) as
+           
+-----------------------------------------------------------
+-- exiting the program 
+
+exit :: Int -> Sh ()
+exit = sh1 S.exit
+
+errorExit :: Text -> Sh ()
+errorExit = sh1 S.errorExit
+
+-- | fail that takes a Text
+terror :: Text -> Sh a
+terror = sh1 S.terror
+
+------------------------------------------------------------
+-- Utilities
+
+-- | Catch an exception in the Sh monad.
+catch_sh :: (Exception e) => Sh a -> (e -> Sh a) -> Sh a
+catch_sh a f = Sh $ S.catch_sh (unSh a) (unSh . f)
+
+-- | Catch an exception in the Sh monad.
+catchany_sh :: Sh a -> (SomeException -> Sh a) -> Sh a
+catchany_sh = catch_sh
+
+
+-- | Catch an exception in the Sh monad.
+finally_sh :: Sh a -> Sh b -> Sh a
+finally_sh = lift2 S.finally_sh
+
+-- | Run a Sh computation and collect timing  information.
+time :: Sh a -> Sh (Double, a)
+time = lift1 S.time
+
+-- | You need this when using 'catches_sh'.
+data ShellyHandler a = forall e . Exception e => ShellyHandler (e -> Sh a)
+
+-- | Catch multiple exceptions in the Sh monad.
+catches_sh :: Sh a -> [ShellyHandler a] -> Sh a
+catches_sh a hs = Sh $ S.catches_sh (unSh a) (fmap convert hs)
+    where convert :: ShellyHandler a -> S.ShellyHandler [a]
+          convert (ShellyHandler f) = S.ShellyHandler (unSh . f)
+
+------------------------------------------------------------
+-- convert between Text and FilePath 
+
+toTextWarn :: FilePath -> Sh Text
+toTextWarn = sh1 S.toTextWarn
+
+-------------------------------------------------------------
+-- internal functions for writing extension 
+
+get :: Sh State
+get = sh0 S.get
+
+put :: State -> Sh ()
+put = sh1 S.put
+
+--------------------------------------------------------
+-- polyvariadic vodoo
+
+-- | Converter for the variadic argument version of 'run' called 'cmd'.
+class ShellArg a where toTextArg :: a -> Text
+instance ShellArg Text     where toTextArg = id
+instance ShellArg FilePath where toTextArg = toTextIgnore
+
+
+-- Voodoo to create the variadic function 'cmd'
+class ShellCommand t where
+    cmdAll :: FilePath -> [Text] -> t
+
+instance ShellCommand (Sh Text) where
+    cmdAll fp args = run fp args
+
+instance (s ~ Text, Show s) => ShellCommand (Sh s) where
+    cmdAll fp args = run fp args
+
+-- note that Sh () actually doesn't work for its case (_<- cmd) when there is no type signature
+instance ShellCommand (Sh ()) where
+    cmdAll fp args = run_ fp args
+
+instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where
+    cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x])
+
+-- | variadic argument version of run.
+-- The syntax is more convenient, but more importantly it also allows the use of a FilePath as a command argument.
+-- So an argument can be a Text or a FilePath.
+-- a FilePath is converted to Text with 'toTextIgnore'.
+-- You will need to add the following to your module:
+--
+-- > {-# LANGUAGE OverloadedStrings #-}
+-- > {-# LANGUAGE ExtendedDefaultRules #-}
+-- > {-# OPTIONS_GHC -fno-warn-type-defaults #-}
+-- > import Shelly
+-- > import Data.Text.Lazy as LT
+-- > default (LT.Text)
+--
+cmd :: (ShellCommand result) => FilePath -> result
+cmd fp = cmdAll fp []
+
diff --git a/shelly.cabal b/shelly.cabal
--- a/shelly.cabal
+++ b/shelly.cabal
@@ -1,6 +1,6 @@
 Name:       shelly
 
-Version:     0.13.0.1
+Version:     0.13.1
 Synopsis:    shell-like (systems) programming in Haskell
 
 Description: Shelly provides convenient systems programming in Haskell,
@@ -19,7 +19,7 @@
              .
              See the shelly-extra package for additional functionality.
              .
-             An overview is available in the README: <https://github.com/yesodweb/Shelly.hs>
+             An overview is available in the README: <https://github.com/yesodweb/Shelly.hs/blob/master/README.md>
 
 
 Homepage:            https://github.com/yesodweb/Shelly.hs
@@ -33,7 +33,7 @@
 
 
 Library
-  Exposed-modules: Shelly
+  Exposed-modules: Shelly, Shelly.Pipe
   other-modules:   Shelly.Base, Shelly.Find
 
   Build-depends: base >= 4 && < 5, time, directory, text, mtl
