diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -10,28 +10,30 @@
 
 ```haskell
 import AutoGUI
-main = runAutoGUI $ write "Hello, world!"
+main = do
+  initialize
+  write "Hello, world!"
 ```
 
 This doesn't just print `Hello, world!` to the screen, but instead simulates a user typing it in.
 
 ## Language Comparison
 
-When using this library, the following Haskell and Python examples do the same thing:
+The following Haskell and Python examples do the same thing:
 
 ```haskell
 import AutoGUI
-import Control.Monad.IO.Class (liftIO)
+import Control.Monad (forM_)
 
 main :: IO ()
 main = do
+  initialize
   putStrLn "3 seconds until beeping begins"
   sleep 3
-  runAutoGUI $
-    forM_ [1..100] $ \i -> do
-      write "beep"
-      press [key|enter|]
-      liftIO $ sleep 0.5
+  forM_ [1..100] $ \i -> do
+    write "beep"
+    press [key|enter|]
+    sleep 0.5
 ```
 
 ```python
@@ -57,27 +59,25 @@
 
 ### General
 
-- `runAutoGUI :: AutoGUI a -> IO a` runs `AutoGUI` actions in `IO`
-
 ### Debug
-- `pause :: Double -> AutoGUI ()` - Set a number of seconds to wait in between autogui actions
-- `failsafe :: Bool -> AutoGUI ()` - When set to true, move the mouse to the upper-left corner of the screen to throw a Python exception, and quit the program
+- `pause :: Double -> IO ()` - Set a number of seconds to wait in between autogui actions
+- `failsafe :: Bool -> IO ()` - When set to true, move the mouse to the upper-left corner of the screen to throw a Python exception, and quit the program
 - `sleep :: Double -> IO ()` - Sleep for a given fractional number of seconds
 
 ### Info
-- `size :: AutoGUI (Integer, Integer)` - (screenWidth, screenHeight) of the primary monitor in pixels
-- `position :: AutoGUI (Integer, Integer)` - (x, y) position of the mouse
-- `onScreen :: Integer -> Integer -> AutoGUI Bool` - Test whether (x, y) is within the screen size
+- `size :: IO (Integer, Integer)` - (screenWidth, screenHeight) of the primary monitor in pixels
+- `position :: IO (Integer, Integer)` - (x, y) position of the mouse
+- `onScreen :: Integer -> Integer -> IO Bool` - Test whether (x, y) is within the screen size
 
 ### Keyboard
-- `write :: Text -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard
-- `typewrite :: Text -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, newline is enter
-- `typewriteKeys :: [Key] -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, newline is enter
-- `writeWithInterval :: Text -> Double -> AutoGUI ()` - Write out some Text as though it were entered with the keyboard, with a specified number of seconds between keypresses
-- `press :: Key -> AutoGUI ()` - Simulate a keypress
-- `keyDown :: Key -> AutoGUI ()` - Simulate holding a key down
-- `keyUp :: Key -> AutoGUI ()` - Simulate releasing a key
-- `hotkey :: [Key] -> AutoGUI ()` - Press a key combination
+- `write :: Text -> IO ()` - Write out some Text as though it were entered with the keyboard
+- `typewrite :: Text -> IO ()` - Write out some Text as though it were entered with the keyboard, newline is enter
+- `typewriteKeys :: [Key] -> IO ()` - Write out some Text as though it were entered with the keyboard, newline is enter
+- `writeWithInterval :: Text -> Double -> IO ()` - Write out some Text as though it were entered with the keyboard, with a specified number of seconds between keypresses
+- `press :: Key -> IO ()` - Simulate a keypress
+- `keyDown :: Key -> IO ()` - Simulate holding a key down
+- `keyUp :: Key -> IO ()` - Simulate releasing a key
+- `hotkey :: [Key] -> IO ()` - Press a key combination
 
 ### Keys
 - `key :: QuasiQuoter` - This quasiquoter lets you use [key|enter|] at compile time, so you don't get a Maybe as you would from mkKey
@@ -86,35 +86,35 @@
 - `isValidKey :: Text -> Bool`
 - `keys :: Set Key`
 
-### MessageBoxes
-- `alert :: Text -> AutoGUI ()` - Show a box onscreen until dismissed
-- `confirm :: Text -> AutoGUI Bool` - Show a box onscreen until a user hits OK or Cancel. Return True on OK, False on Cancel, and False if user closes the box
-- `password :: Text -> AutoGUI Text` - Show a box onscreen, allowing user to enter some screened text. Return empty string if user closes the box
-- `prompt :: Text -> AutoGUI Text` - Show a box onscreen, allowing user to enter some text. Return empty string if user closes the box
+### Message Boxes
+- `alert :: Text -> IO ()` - Show a box onscreen until dismissed
+- `confirm :: Text -> IO Bool` - Show a box onscreen until a user hits OK or Cancel. Return True on OK, False on Cancel, and False if user closes the box
+- `password :: Text -> IO Text` - Show a box onscreen, allowing user to enter some screened text. Return empty string if user closes the box
+- `prompt :: Text -> IO Text` - Show a box onscreen, allowing user to enter some text. Return empty string if user closes the box
 
 ### Mouse
-- `moveTo :: Integer -> Integer -> AutoGUI ()` - Move the mouse to an (x, y) position
-- `moveToDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Move the mouse to an (x, y) position, over a number of seconds
-- `moveRel :: Integer -> Integer -> AutoGUI ()` - Move the mouse relative to where it is now
-- `moveRelDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Move the mouse relative to where it is now, over a number of seconds
-- `click :: AutoGUI ()` - Click the mouse
-- `leftClick :: AutoGUI ()` - Left click the mouse
-- `doubleClick :: AutoGUI ()` - Double click the mouse
-- `tripleClick :: AutoGUI ()` - Triple click the mouse
-- `rightClick :: AutoGUI ()` - Right click the mouse
-- `middleClick :: AutoGUI ()` - Middle click the mouse
-- `moveAndClick :: Integer -> Integer -> AutoGUI ()` - Move the mouse to some (x, y) position and click there
-- `drag :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
-- `dragDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y), over a number of seconds
-- `dragTo :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse to the position (x, y)
-- `dragToDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse to the position (x, y), over a number of seconds
-- `dragRel :: Integer -> Integer -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
-- `dragRelDuration :: Integer -> Integer -> Double -> AutoGUI ()` - Clicks and drags the mouse through a motion of (x, y)
-- `scroll :: Integer -> AutoGUI ()` - Scroll up (positive) or down (negative)
-- `mouseDown :: AutoGUI ()` - Press the mouse button down
-- `mouseUp :: AutoGUI ()` - Release the mouse button
+- `moveTo :: Integer -> Integer -> IO ()` - Move the mouse to an (x, y) position
+- `moveToDuration :: Integer -> Integer -> Double -> IO ()` - Move the mouse to an (x, y) position, over a number of seconds
+- `moveRel :: Integer -> Integer -> IO ()` - Move the mouse relative to where it is now
+- `moveRelDuration :: Integer -> Integer -> Double -> IO ()` - Move the mouse relative to where it is now, over a number of seconds
+- `click :: MouseButton -> IO ()` - Click the specified mouse button
+- `leftClick :: IO ()` - Left click the mouse
+- `doubleClick :: IO ()` - Double click the mouse
+- `tripleClick :: IO ()` - Triple click the mouse
+- `rightClick :: IO ()` - Right click the mouse
+- `middleClick :: IO ()` - Middle click the mouse
+- `moveAndClick :: Integer -> Integer -> IO ()` - Move the mouse to some (x, y) position and click there
+- `drag :: Integer -> Integer -> IO ()` - Clicks and drags the mouse through a motion of (x, y)
+- `dragDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse through a motion of (x, y), over a number of seconds
+- `dragTo :: Integer -> Integer -> IO ()` - Clicks and drags the mouse to the position (x, y)
+- `dragToDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse to the position (x, y), over a number of seconds
+- `dragRel :: Integer -> Integer -> IO ()` - Clicks and drags the mouse through a motion of (x, y)
+- `dragRelDuration :: Integer -> Integer -> Double -> IO ()` - Clicks and drags the mouse through a motion of (x, y)
+- `scroll :: Integer -> IO ()` - Scroll up (positive) or down (negative)
+- `mouseDown :: IO ()` - Press the mouse button down
+- `mouseUp :: IO ()` - Release the mouse button
 
 ### Screen
-- `locateOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer, Integer, Integer))` - Return (left, top, width, height) of first place the image is found
-- `locateCenterOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer))` - Return (x, y) of center of an image, if the image is found
+- `locateOnScreen :: FilePath -> IO (Maybe (Integer, Integer, Integer, Integer))` - Return (left, top, width, height) of first place the image is found
+- `locateCenterOnScreen :: FilePath -> IO (Maybe (Integer, Integer))` - Return (x, y) of center of an image, if the image is found
 
diff --git a/app/Main.hs b/app/Main.hs
deleted file mode 100644
--- a/app/Main.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module Main where
-
-import AutoGUI
-import Control.Monad.IO.Class (liftIO)
-
-main :: IO ()
-main = do
-  putStrLn "3 seconds until beeping begins"
-  sleep 3
-  runAutoGUI $
-    forM_ [1..100] $ \i -> do
-      write "beep"
-      press [key|enter|]
-      liftIO $ sleep 0.5
-
-{- # The above is equivalent to this Python code:
-
-import pyautogui
-import time
-
-print('3 seconds until beeping begins')
-time.sleep(3)
-
-for i in range(1, 101):
-    pyautogui.write('beep')
-    pyautogui.press('enter')
-    time.sleep(0.5)
--}
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,9 @@
 # ChangeLog
 
+## 0.3.0
+
+Use the new CPython.Simple API
+
 ## 0.2.0
 
 Update cpython version to be >= 3.5
diff --git a/hsautogui.cabal b/hsautogui.cabal
--- a/hsautogui.cabal
+++ b/hsautogui.cabal
@@ -4,10 +4,10 @@
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: c820d39c8cb3bc4809c69dc07f83f8d8acfc9dc85e35fe48959ba344c966e88c
+-- hash: afddd7eba7bc5790dc58b9e8518daf7be96aa5123b503b0f420f9272aff74185
 
 name:           hsautogui
-version:        0.2.0
+version:        0.3.0
 synopsis:       Haskell bindings for PyAutoGUI, a library for automating user interaction
 description:    Please see the README on GitHub at <https://github.com/mitchellvitez/hsautogui#readme>
 category:       Automation
@@ -32,40 +32,39 @@
       AutoGUI
       AutoGUI.Call
       AutoGUI.Debug
+      AutoGUI.Discard
       AutoGUI.Info
       AutoGUI.Keyboard
       AutoGUI.Keys
       AutoGUI.MessageBoxes
       AutoGUI.Mouse
-      AutoGUI.Run
       AutoGUI.Screen
   other-modules:
       Paths_hsautogui
   hs-source-dirs:
       src
   default-extensions: DeriveLift GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses OverloadedStrings RecordWildCards QuasiQuotes ScopedTypeVariables TemplateHaskell
-  extra-libraries:
-      python
   build-depends:
       base
     , containers
-    , cpython >=3.5
+    , cpython >=3.5.1
     , mtl
     , template-haskell
     , text
   default-language: Haskell2010
 
-executable hsautogui-sample-exe
+test-suite hsautogui-test
+  type: exitcode-stdio-1.0
   main-is: Main.hs
   other-modules:
       Paths_hsautogui
   hs-source-dirs:
-      app
+      test
   default-extensions: OverloadedStrings QuasiQuotes
   ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -W
-  extra-libraries:
-      python
   build-depends:
       base >=4.7 && <5
+    , cpython >=3.5.1
     , hsautogui
+    , hspec
   default-language: Haskell2010
diff --git a/src/AutoGUI.hs b/src/AutoGUI.hs
--- a/src/AutoGUI.hs
+++ b/src/AutoGUI.hs
@@ -1,7 +1,5 @@
 module AutoGUI
-  ( AutoGUI(..)
-  , AutoGUIT(..)
-  , Key
+  ( Key
   , alert
   , click
   , confirm
@@ -14,6 +12,7 @@
   , dragToDuration
   , failsafe
   , hotkey
+  , initialize
   , isValidKey
   , key
   , keyDown
@@ -39,7 +38,6 @@
   , press
   , prompt
   , rightClick
-  , runAutoGUI
   , scroll
   , size
   , sleep
@@ -57,5 +55,5 @@
 import AutoGUI.Keys
 import AutoGUI.MessageBoxes
 import AutoGUI.Mouse
-import AutoGUI.Run
 import AutoGUI.Screen
+import CPython.Simple (initialize)
diff --git a/src/AutoGUI/Call.hs b/src/AutoGUI/Call.hs
--- a/src/AutoGUI/Call.hs
+++ b/src/AutoGUI/Call.hs
@@ -1,41 +1,10 @@
-module AutoGUI.Call where
-
-import AutoGUI.Run
-import AutoGUI.Keys
+module AutoGUI.Call
+  ( pyautogui )
+where
 
-import Control.Monad.Reader
+import CPython.Simple
+import CPython.Simple.Instances
 import Data.Text (Text)
-import qualified Data.Text as T
 
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
-
-data Arg
-  = IntArg Integer
-  | TextArg Text
-  | KeyArg Key
-  | DoubleArg Double
-  | BoolArg Bool
-
-argToPy :: Arg -> IO Py.SomeObject
-argToPy = \case
-  IntArg    i -> Py.toObject <$> Py.toInteger i
-  TextArg   t -> Py.toObject <$> Py.toUnicode t
-  KeyArg    k -> Py.toObject <$> Py.toUnicode (keyToText k)
-  DoubleArg d -> Py.toObject <$> Py.toFloat d
-  -- there isn't a bool equivalent to the above in haskell-cpython, just convert to int
-  BoolArg   b -> Py.toObject <$> Py.toInteger (if b then 1 else 0)
-
-call' :: Text -> [Arg] -> AutoGUI Py.SomeObject
-call' func args = do
-  autoguiModule <- ask
-  liftIO $ do
-    pyFunc <- Py.getAttribute autoguiModule =<< Py.toUnicode func
-    pyArgs <- mapM argToPy args
-    Py.callArgs pyFunc pyArgs
-
-call :: Text -> [Arg] -> AutoGUI ()
-call func args = call' func args >> pure ()
+pyautogui :: FromPy a => Text -> [Arg] -> [(Text, Arg)] -> IO a
+pyautogui = call "pyautogui"
diff --git a/src/AutoGUI/Debug.hs b/src/AutoGUI/Debug.hs
--- a/src/AutoGUI/Debug.hs
+++ b/src/AutoGUI/Debug.hs
@@ -5,39 +5,25 @@
   )
 where
 
-import AutoGUI.Call
-import AutoGUI.Run
+import AutoGUI.Discard
 
 import Control.Concurrent
 import Control.Monad.IO.Class
 import Control.Monad.Reader
+import CPython.Simple
 import Data.Text (Text)
 import qualified Data.Text as T
 import GHC.Float.RealFracMethods
 
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
-
 -- | Set a number of seconds to wait in between autogui actions
-pause :: Double -> AutoGUI ()
-pause seconds = setAttribute "PAUSE" $ DoubleArg seconds
+pause :: Double -> IO ()
+pause = setAttribute "pyautogui" "PAUSE"
 
 -- | When set to true, move the mouse to the upper-left corner of the screen to throw
 --   a Python exception, and quit the program
-failsafe :: Bool -> AutoGUI ()
-failsafe value = setAttribute "FAILSAFE" $ BoolArg value
+failsafe :: Bool -> IO ()
+failsafe = setAttribute "pyautogui" "FAILSAFE"
 
 -- | Sleep for a given fractional number of seconds
 sleep :: Double -> IO ()
 sleep n = threadDelay . fromInteger . roundDoubleInteger $ n * 1000000
-
-setAttribute :: Text -> Arg -> AutoGUI ()
-setAttribute name value = do
-  autoguiModule <- ask
-  liftIO $ do
-    pyName <- Py.toUnicode name
-    pyValue <- argToPy value
-    Py.setAttribute autoguiModule pyName pyValue
diff --git a/src/AutoGUI/Discard.hs b/src/AutoGUI/Discard.hs
new file mode 100644
--- /dev/null
+++ b/src/AutoGUI/Discard.hs
@@ -0,0 +1,38 @@
+module AutoGUI.Discard where
+
+import CPython.Simple.Instances
+import qualified CPython.Types.Tuple as Py (fromTuple, toTuple)
+import qualified CPython.Types.Unicode as Py
+import Data.Typeable
+import qualified Data.Text as T
+
+-- TODO: Cpython.Simple should export the stuff from CPython.Simple.Instances
+
+-- TODO toPy Bool
+instance ToPy Bool where
+  toPy b = error "nope"
+
+-- TODO fromPy Bool
+instance FromPy Bool where
+  fromPy _ = pure False
+
+-- TODO FilePath
+-- instance {-# OVERLAPS #-} ToPy String where
+  -- toPy = easyToPy Py.toUnicode . T.pack
+
+-- TODO: fromPy (a, b, c, d)
+instance (FromPy a, FromPy b, FromPy c, FromPy d) => FromPy (a, b, c, d) where
+  fromPy val = do
+    [pyA, pyB, pyC, pyD] <- easyFromPy Py.fromTuple Proxy val
+    a <- fromPy pyA
+    b <- fromPy pyB
+    c <- fromPy pyC
+    d <- fromPy pyD
+    pure (a, b, c, d)
+
+instance (ToPy a, ToPy b, ToPy c) => ToPy (a, b, c) where
+  toPy (a, b, c) = do
+    pyA <- toPy a
+    pyB <- toPy b
+    pyC <- toPy c
+    easyToPy Py.toTuple [pyA, pyB, pyC]
diff --git a/src/AutoGUI/Info.hs b/src/AutoGUI/Info.hs
--- a/src/AutoGUI/Info.hs
+++ b/src/AutoGUI/Info.hs
@@ -7,40 +7,20 @@
 where
 
 import AutoGUI.Call
-import AutoGUI.Run
-
+import AutoGUI.Discard
 import Control.Monad.IO.Class
+import CPython.Simple
 import Data.Text (Text)
 import qualified Data.Text as T
 
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
-
 -- | (screenWidth, screenHeight) of the primary monitor in pixels
-size :: AutoGUI (Integer, Integer)
-size = getXY "size"
+size :: IO (Integer, Integer)
+size = pyautogui "size" [] []
 
 -- | (x, y) position of the mouse
-position :: AutoGUI (Integer, Integer)
-position = getXY "position"
+position :: IO (Integer, Integer)
+position = pyautogui "position" [] []
 
 -- | Test whether (x, y) is within the screen size
-onScreen :: Integer -> Integer -> AutoGUI Bool
-onScreen x y = do
-  pyObjBool <- call' "onScreen" [IntArg x, IntArg y]
-  liftIO $ Py.toBool pyObjBool
-
-getXY :: Text -> AutoGUI (Integer, Integer)
-getXY func = do
-  pyObjTuple <- call' func []
-  liftIO $ do
-    Just tuple <- Py.cast pyObjTuple
-    [pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
-    Just pyWidth <- Py.cast pyObjWidth
-    Just pyHeight <- Py.cast pyObjHeight
-    width <- Py.fromInteger pyWidth
-    height <- Py.fromInteger pyHeight
-    pure (width, height)
+onScreen :: Integer -> Integer -> IO Bool
+onScreen x y = pyautogui "onScreen" [arg x, arg y] []
diff --git a/src/AutoGUI/Keyboard.hs b/src/AutoGUI/Keyboard.hs
--- a/src/AutoGUI/Keyboard.hs
+++ b/src/AutoGUI/Keyboard.hs
@@ -10,42 +10,43 @@
   )
 where
 
-import AutoGUI.Call
 import AutoGUI.Keys
-import AutoGUI.Run
+import AutoGUI.Call
 
+import CPython.Simple
+import CPython.Simple.Instances
 import Data.Text (Text)
 import qualified Data.Text as T
 
 -- | Write out some Text as though it were entered with the keyboard
-write :: Text -> AutoGUI ()
-write msg = call "write" [TextArg msg]
+write :: Text -> IO ()
+write msg = pyautogui "write" [arg msg] []
 
 -- | Write out some Text as though it were entered with the keyboard, newline is enter
-typewrite :: Text -> AutoGUI ()
-typewrite msg = call "typewrite" [TextArg msg]
+typewrite :: Text -> IO ()
+typewrite msg = pyautogui "typewrite" [arg msg] []
 
 -- | Write out some Text as though it were entered with the keyboard, newline is enter
-typewriteKeys :: [Key] -> AutoGUI ()
-typewriteKeys keys = call "typewrite" $ KeyArg <$> keys
+typewriteKeys :: [Key] -> IO ()
+typewriteKeys keys = pyautogui "typewrite" (map arg keys) []
 
 -- | Write out some Text as though it were entered with the keyboard, with a specified
 --   number of seconds between keypresses
-writeWithInterval :: Text -> Double -> AutoGUI ()
-writeWithInterval msg interval = call "write" [TextArg msg, DoubleArg interval]
+writeWithInterval :: Text -> Double -> IO ()
+writeWithInterval msg interval = pyautogui "write" [arg msg, arg interval] []
 
 -- | Simulate a keypress
-press :: Key -> AutoGUI ()
-press key = call "press" [KeyArg key]
+press :: Key -> IO ()
+press key = pyautogui "press" [arg key] []
 
 -- | Simulate holding a key down
-keyDown :: Key -> AutoGUI ()
-keyDown key = call "keyDown" [KeyArg key]
+keyDown :: Key -> IO ()
+keyDown key = pyautogui "keyDown" [arg key] []
 
 -- | Simulate releasing a key
-keyUp :: Key -> AutoGUI ()
-keyUp key = call "keyUp" [KeyArg key]
+keyUp :: Key -> IO ()
+keyUp key = pyautogui "keyUp" [arg key] []
 
 -- | Press a key combination
-hotkey :: [Key] -> AutoGUI ()
-hotkey keys = call "hotkey" $ KeyArg <$> keys
+hotkey :: [Key] -> IO ()
+hotkey keys = pyautogui "hotkey" (map arg keys) []
diff --git a/src/AutoGUI/Keys.hs b/src/AutoGUI/Keys.hs
--- a/src/AutoGUI/Keys.hs
+++ b/src/AutoGUI/Keys.hs
@@ -8,6 +8,7 @@
   )
 where
 
+import CPython.Simple.Instances
 import Data.Set (Set)
 import qualified Data.Set as Set
 import Data.Text (Text)
@@ -18,6 +19,10 @@
 
 newtype Key = Key String
   deriving (Eq, Show, Ord, Lift)
+
+-- TODO make the string overlapping instance nicer
+instance ToPy Key where
+  toPy (Key str) = toPy $ T.pack str
 
 mkKey :: Text -> Maybe Key
 mkKey key = if isValidKey key then Just (Key (T.unpack key)) else Nothing
diff --git a/src/AutoGUI/MessageBoxes.hs b/src/AutoGUI/MessageBoxes.hs
--- a/src/AutoGUI/MessageBoxes.hs
+++ b/src/AutoGUI/MessageBoxes.hs
@@ -7,51 +7,36 @@
 where
 
 import AutoGUI.Call
-import AutoGUI.Run
-
+import CPython.Simple
 import Control.Monad
 import Control.Monad.IO.Class
 import Data.Text (Text)
 import qualified Data.Text as T
 
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
-
 -- | Show a box onscreen until dismissed
-alert :: Text -> AutoGUI ()
-alert msg = call "alert" [TextArg msg]
+alert :: Text -> IO ()
+alert msg = pyautogui "alert" [arg msg] []
 
 -- | Show a box onscreen until a user hits OK or Cancel
 --   Return True on OK, False on Cancel, and False if user closes the box
-confirm :: Text -> AutoGUI Bool
+confirm :: Text -> IO Bool
 confirm msg = do
-  pyObjOkOrCancel <- call' "confirm" [TextArg msg]
-  liftIO $ do
-    pyOkOrCancelCasted <- Py.cast pyObjOkOrCancel
-    case pyOkOrCancelCasted of
-      Nothing -> pure False
-      Just pyOkOrCancel -> do
-        okOrCancel <- Py.fromUnicode pyOkOrCancel
-        pure $ if okOrCancel == "OK" then True else False
+  okOrCancel :: Text <- pyautogui "confirm" [arg msg] []
+  pure $ okOrCancel == "OK"
 
 -- | Show a box onscreen, allowing user to enter some screened text
 --   Return empty string if user closes the box
-password :: Text -> AutoGUI Text
+password :: Text -> IO Text
 password msg = textInput "password" msg
 
 -- | Show a box onscreen, allowing user to enter some text
 --   Return empty string if user closes the box
-prompt :: Text -> AutoGUI Text
+prompt :: Text -> IO Text
 prompt msg = textInput "prompt" msg
 
-textInput :: Text -> Text -> AutoGUI Text
+textInput :: Text -> Text -> IO Text
 textInput func msg = do
-  pyObjPrompt <- call' func [TextArg msg]
-  liftIO $ do
-    pyPrompt <- Py.cast pyObjPrompt
-    case pyPrompt of
-      Just promptText -> Py.fromUnicode promptText
-      Nothing -> pure ""
+  pyPrompt :: Maybe Text <- pyautogui func [arg msg] []
+  pure $ case pyPrompt of
+    Just promptText -> promptText
+    Nothing -> ""
diff --git a/src/AutoGUI/Mouse.hs b/src/AutoGUI/Mouse.hs
--- a/src/AutoGUI/Mouse.hs
+++ b/src/AutoGUI/Mouse.hs
@@ -1,5 +1,8 @@
+{-# LANGUAGE TypeApplications #-}
+
 module AutoGUI.Mouse
-  ( moveTo
+  ( MouseButton(..)
+  , moveTo
   , moveToDuration
   , moveRel
   , moveRelDuration
@@ -23,88 +26,109 @@
 where
 
 import AutoGUI.Call
-import AutoGUI.Run
+import CPython.Simple
+import CPython.Simple.Instances
+import Data.Text
 
+data MouseButton
+  = LeftMouseButton
+  | RightMouseButton
+  | MiddleMouseButton
+
+-- TODO: try to make this as easy/automatic as possible
+instance ToPy MouseButton where
+  toPy LeftMouseButton   = toPy @Text "left"
+  toPy RightMouseButton  = toPy @Text "right"
+  toPy MiddleMouseButton = toPy @Text "middle"
+
 -- | Move the mouse to an (x, y) position
-moveTo :: Integer -> Integer -> AutoGUI ()
-moveTo x y = call "moveTo" [IntArg x, IntArg y]
+moveTo :: Integer -> Integer -> IO ()
+moveTo x y = pyautogui "moveTo" [arg x, arg y] []
 
 -- | Move the mouse to an (x, y) position, over a number of seconds
-moveToDuration :: Integer -> Integer -> Double -> AutoGUI ()
-moveToDuration x y duration = call "moveTo" [IntArg x, IntArg y, DoubleArg duration]
+moveToDuration :: Integer -> Integer -> Double -> IO ()
+moveToDuration x y duration =
+  pyautogui "moveTo" [arg x, arg y] [("duration", arg duration)]
 
 -- | Move the mouse relative to where it is now
-moveRel :: Integer -> Integer -> AutoGUI ()
-moveRel xOffset yOffset = call "moveRel" [IntArg xOffset, IntArg yOffset]
+moveRel :: Integer -> Integer -> IO ()
+moveRel xOffset yOffset =
+  pyautogui "moveRel" [arg xOffset, arg yOffset] []
 
 -- | Move the mouse relative to where it is now, over a number of seconds
-moveRelDuration :: Integer -> Integer -> Double -> AutoGUI ()
+moveRelDuration :: Integer -> Integer -> Double -> IO ()
 moveRelDuration xOffset yOffset duration =
-  call "moveRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]
-
--- | Click the mouse
-click :: AutoGUI ()
-click = call "click" []
+  pyautogui "moveRel" [arg xOffset, arg yOffset, arg duration] []
 
--- | Left click the mouse
-leftClick :: AutoGUI ()
-leftClick = click
+-- | Click a specified mouse button
+click :: MouseButton -> IO ()
+click button = pyautogui "click" [] [("button", arg button)]
 
 -- | Double click the mouse
-doubleClick :: AutoGUI ()
-doubleClick = call "doubleClick" []
+doubleClick :: IO ()
+doubleClick = pyautogui "doubleClick" [] []
 
 -- | Triple click the mouse
-tripleClick :: AutoGUI ()
-tripleClick = call "tripleClick" []
+tripleClick :: IO ()
+tripleClick = pyautogui "tripleClick" [] []
 
+-- | Left click the mouse
+leftClick :: IO ()
+leftClick = click LeftMouseButton
+
 -- | Right click the mouse
-rightClick :: AutoGUI ()
-rightClick = call "rightClick" []
+rightClick :: IO ()
+rightClick = click RightMouseButton
 
 -- | Middle click the mouse
-middleClick :: AutoGUI ()
-middleClick = call "middleClick" []
+middleClick :: IO ()
+middleClick = click MiddleMouseButton
 
 -- | Move the mouse to some (x, y) position and click there
-moveAndClick :: Integer -> Integer -> AutoGUI ()
-moveAndClick x y = moveTo x y >> click
+moveAndClick :: Integer -> Integer -> IO ()
+moveAndClick x y = moveTo x y >> click LeftMouseButton
 
 -- | Clicks and drags the mouse through a motion of (x, y)
-drag :: Integer -> Integer -> AutoGUI ()
-drag x y = call "drag" [IntArg x, IntArg y]
+drag :: Integer -> Integer -> IO ()
+drag x y = pyautogui "drag" [arg x, arg y] []
 
 -- | Clicks and drags the mouse through a motion of (x, y), over a number of seconds
-dragDuration :: Integer -> Integer -> Double -> AutoGUI ()
-dragDuration x y duration = call "drag" [IntArg x, IntArg y, DoubleArg duration]
+dragDuration :: Integer -> Integer -> Double -> IO ()
+dragDuration x y duration = pyautogui "drag" [arg x, arg y, arg duration] []
 
 -- | Clicks and drags the mouse to the position (x, y)
-dragTo :: Integer -> Integer -> AutoGUI ()
-dragTo x y = call "dragTo" [IntArg x, IntArg y]
+dragTo :: Integer -> Integer -> IO ()
+dragTo x y = pyautogui "dragTo" [arg x, arg y] []
 
 -- | Clicks and drags the mouse to the position (x, y), over a number of seconds
-dragToDuration :: Integer -> Integer -> Double -> AutoGUI ()
-dragToDuration x y duration = call "dragTo" [IntArg x, IntArg y, DoubleArg duration]
+dragToDuration :: Integer -> Integer -> Double -> IO ()
+dragToDuration x y duration = pyautogui "dragTo" [arg x, arg y, arg duration] []
 
 -- | Clicks and drags the mouse through a motion of (x, y)
-dragRel :: Integer -> Integer -> AutoGUI ()
-dragRel xOffset yOffset = call "dragRel" [IntArg xOffset, IntArg yOffset]
+dragRel :: Integer -> Integer -> IO ()
+dragRel xOffset yOffset = pyautogui "dragRel" [arg xOffset, arg yOffset] []
 
 -- | Clicks and drags the mouse through a motion of (x, y)
-dragRelDuration :: Integer -> Integer -> Double -> AutoGUI ()
+dragRelDuration :: Integer -> Integer -> Double -> IO ()
 dragRelDuration xOffset yOffset duration =
-  call "dragRel" [IntArg xOffset, IntArg yOffset, DoubleArg duration]
+  pyautogui "dragRel" [arg xOffset, arg yOffset, arg duration] []
 
 -- | Scroll up (positive) or down (negative)
-scroll :: Integer -> AutoGUI ()
-scroll amount = call "scroll" [IntArg amount]
+scroll :: Integer -> IO ()
+scroll amount = pyautogui "scroll" [arg amount] []
 
--- TODO: support mouseDown/mouseUp for other mouse buttons besides LMB
+-- | Press the left mouse button down
+mouseDown :: IO ()
+mouseDown = pyautogui "mouseDown" [] []
 
--- | Press the mouse button down
-mouseDown :: AutoGUI ()
-mouseDown = call "mouseDown" []
+-- | Release the left mouse button
+mouseUp :: IO ()
+mouseUp = pyautogui "mouseUp" [] []
 
--- | Release the mouse button
-mouseUp :: AutoGUI ()
-mouseUp = call "mouseUp" []
+-- | Press a specified mouse button
+mouseButtonDown :: MouseButton -> IO ()
+mouseButtonDown button = pyautogui "mouseDown" [] [("button", arg button)]
+
+-- | Press a specified mouse button
+mouseButtonUp :: MouseButton -> IO ()
+mouseButtonUp button = pyautogui "mouseUp" [] [("button", arg button)]
diff --git a/src/AutoGUI/Run.hs b/src/AutoGUI/Run.hs
deleted file mode 100644
--- a/src/AutoGUI/Run.hs
+++ /dev/null
@@ -1,28 +0,0 @@
-module AutoGUI.Run
-  ( runAutoGUI
-  , AutoGUIT(..)
-  , AutoGUI(..)
-  )
-where
-
-import Control.Monad
-import Control.Monad.IO.Class
-import Control.Monad.Reader
-
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
-
-newtype AutoGUIT m a =
-  AutoGUIT { unAutoGUI :: ReaderT Py.Module m a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadReader Py.Module)
-
-type AutoGUI = AutoGUIT IO
-
-runAutoGUI :: AutoGUI a -> IO a
-runAutoGUI autogui = do
-  Py.initialize
-  autoguiModule <- Py.importModule "pyautogui"
-  runReaderT (unAutoGUI autogui) autoguiModule
diff --git a/src/AutoGUI/Screen.hs b/src/AutoGUI/Screen.hs
--- a/src/AutoGUI/Screen.hs
+++ b/src/AutoGUI/Screen.hs
@@ -1,64 +1,41 @@
 module AutoGUI.Screen
   ( locateOnScreen
   , locateCenterOnScreen
+  , Color(..)
   )
 where
 
 import AutoGUI.Call
-import AutoGUI.Run
-
+import AutoGUI.Discard
+import CPython.Simple
 import Control.Monad.IO.Class
 import Data.Text (Text)
 import qualified Data.Text as T
 
-import qualified CPython                  as Py
-import qualified CPython.Constants        as Py
-import qualified CPython.Protocols.Object as Py
-import qualified CPython.Types            as Py
-import qualified CPython.Types.Module     as Py
+type Color = (Integer, Integer, Integer)
 
--- TODO: Returns a Pillow/PIL Image object. Not nicely supported yet.
+-- TODO: Returns a Pillow/PIL Image object. Not supported yet.
 -- screenshot :: AutoGUI Py.SomeObject
 -- screenshot = call' "screenshot" []
 
 -- | Return (left, top, width, height) of first place the image is found
-locateOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer, Integer, Integer))
-locateOnScreen path = do
-  pyObjTuple <- call' "locateOnScreen" [TextArg $ T.pack path]
-  isNone <- liftIO $ Py.isNone pyObjTuple
-  if isNone
-    then pure Nothing
-    else liftIO $ do
-      Just tuple <- Py.cast pyObjTuple
-      [pyObjLeft, pyObjTop, pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
-      Just pyLeft <- Py.cast pyObjLeft
-      Just pyTop <- Py.cast pyObjTop
-      Just pyWidth <- Py.cast pyObjWidth
-      Just pyHeight <- Py.cast pyObjHeight
-      left <- Py.fromInteger pyLeft
-      top <- Py.fromInteger pyTop
-      width <- Py.fromInteger pyWidth
-      height <- Py.fromInteger pyHeight
-      pure $ Just (left, top, width, height)
+locateOnScreen :: FilePath -> IO (Maybe (Integer, Integer, Integer, Integer))
+locateOnScreen path =
+  pyautogui "locateOnScreen" [arg $ T.pack path] []
 
--- TODO: Returns a Python generator. Convert that to a Haskell list.
--- locateAllOnScreen :: FilePath -> AutoGUI [(Integer, Integer, Integer, Integer)]
--- locateAllOnScreen path = call' "locateAllOnScreen" [TextArg $ T.pack path]
+-- TODO: Returns a Python generator. Convert that to a Haskell list. Not supported yet.
+-- locateAllOnScreen :: FilePath -> IO [(Integer, Integer, Integer, Integer)]
+-- locateAllOnScreen path = pyautogui "locateAllOnScreen" [arg $ T.pack path] []
 
 -- | Return (x, y) of center of an image, if the image is found
-locateCenterOnScreen :: FilePath -> AutoGUI (Maybe (Integer, Integer))
-locateCenterOnScreen path = do
-  pyObjTuple <- call' "locateCenterOnScreen" [TextArg $ T.pack path]
-  isNone <- liftIO $ Py.isNone pyObjTuple
-  if isNone
-    then pure Nothing
-    else liftIO $ do
-      Just tuple <- Py.cast pyObjTuple
-      [pyObjWidth, pyObjHeight] <- Py.fromTuple tuple
-      Just pyWidth <- Py.cast pyObjWidth
-      Just pyHeight <- Py.cast pyObjHeight
-      width <- Py.fromInteger pyWidth
-      height <- Py.fromInteger pyHeight
-      pure $ Just (width, height)
+locateCenterOnScreen :: FilePath -> IO (Maybe (Integer, Integer))
+locateCenterOnScreen path =
+  pyautogui "locateCenterOnScreen" [arg $ T.pack path] []
 
--- TODO pixelMatchesColor
+pixelMatchesColor :: Integer -> Integer -> Color -> IO Bool
+pixelMatchesColor x y color =
+  pyautogui "pixelMatchesColor" [arg x, arg y, arg color] []
+
+pixelMatchesColorWithTolerance :: Integer -> Integer -> Color -> Double -> IO Bool
+pixelMatchesColorWithTolerance x y color tolerance =
+  pyautogui "pixelMatchesColor" [arg x, arg y, arg color] [("tolerance", arg tolerance)]
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,37 @@
+module Main where
+
+import Test.Hspec
+
+import AutoGUI
+import CPython.Simple
+import Control.Monad (forM_)
+import Control.Monad.IO.Class (liftIO)
+
+main :: IO ()
+main = hspec $ do
+  describe "basic" $ do
+    it "runs" $ do
+      initialize
+      pause 1.0
+
+    it "sample program" $ do
+      putStrLn "3 seconds until beeping begins"
+      sleep 3
+      forM_ [1..100] $ \i -> do
+        write "beep"
+        press [key|enter|]
+      sleep 0.5
+
+      {- # The above is equivalent to this Python code:
+
+      import pyautogui
+      import time
+
+      print('3 seconds until beeping begins')
+      time.sleep(3)
+
+      for i in range(1, 101):
+          pyautogui.write('beep')
+          pyautogui.press('enter')
+          time.sleep(0.5)
+      -}
