diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2015, Henry J. Wylde
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of Qux nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/main/Main.hs b/main/Main.hs
new file mode 100644
--- /dev/null
+++ b/main/Main.hs
@@ -0,0 +1,38 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Main
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Main (
+    main
+) where
+
+import Control.Applicative
+
+import Options.Applicative
+import Options.Applicative.Builder
+
+import Qux.Commands
+import qualified Qux.Commands.Build as Build
+import qualified Qux.Commands.Check as Check
+import qualified Qux.Commands.Print as Print
+import qualified Qux.Commands.Run   as Run
+
+
+-- | Main.
+main :: IO ()
+main = customExecParser (prefs $ columns 100) options >>= handle
+
+handle :: Options -> IO ()
+handle options = case argCommand options of
+    Build   options -> Build.handle options
+    Check   options -> Check.handle options
+    Print   options -> Print.handle options
+    Run     options -> Run.handle   options
+
diff --git a/qux.cabal b/qux.cabal
new file mode 100644
--- /dev/null
+++ b/qux.cabal
@@ -0,0 +1,34 @@
+name:           qux
+version:        0.1.0.0
+
+author:         Henry J. Wylde
+maintainer:     public@hjwylde.com
+homepage:       https://github.com/qux-lang/qux
+
+synopsis:       Command line binary for working with the Qux language
+-- description:
+
+license:        BSD3
+license-file:   LICENSE
+
+cabal-version:  >= 1.10
+category:       Qux
+build-type:     Simple
+
+executable qux
+    main-is: Main.hs
+    hs-source-dirs: main/ src/
+    other-modules:
+        Qux.Commands,
+        Qux.Commands.Build,
+        Qux.Commands.Check,
+        Qux.Commands.Print,
+        Qux.Commands.Run
+
+    default-language: Haskell2010
+    build-depends:
+        base >= 4.8 && < 5,
+        language-qux == 0.1.*,
+        mtl == 2.*,
+        optparse-applicative == 0.11.*
+
diff --git a/src/Qux/Commands.hs b/src/Qux/Commands.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands.hs
@@ -0,0 +1,134 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Qux.Commands
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Qux.Commands where
+
+import Language.Qux.Annotated.PrettyPrinter
+
+import Options.Applicative
+import Options.Applicative.Types
+
+import Prelude hiding (print)
+
+import qualified Qux.Commands.Build as Build
+import qualified Qux.Commands.Check as Check
+import qualified Qux.Commands.Print as Print
+import qualified Qux.Commands.Run   as Run
+
+
+-- * Qux command
+
+options :: ParserInfo Options
+options = info (helper <*> version <*> qux) (fullDesc <> noIntersperse)
+    where
+        version = infoOption "0.1.0" $ mconcat [
+            long "version",
+            short 'V',
+            help "Show this binary's version",
+            hidden
+            ]
+
+data Options = Options {
+    argCommand :: Command
+    }
+
+-- TODO (hjw): show the help message if no commands given
+-- TODO (hjw): improve error message to say "Invalid command x"
+qux :: Parser Options
+qux = Options <$> subparser (mconcat [
+    command "build" $ info (helper <*> build)   (fullDesc <> progDesc "Build FILE using composable options"),
+    command "check" $ info (helper <*> check)   (fullDesc <> progDesc "Check FILE for correctness" <> header "Synonymous to `qux build --type-check'"),
+    command "print" $ info (helper <*> print)   (fullDesc <> progDesc "Pretty print FILE"),
+    command "run"   $ info (helper <*> run)     (fullDesc <> progDesc "Run FILE passing in ARGS as program arguments")
+    ])
+
+
+-- * Subcommands
+
+data Command    = Build Build.Options
+                | Check Check.Options
+                | Print Print.Options
+                | Run   Run.Options
+
+
+-- ** Build
+
+build :: Parser Command
+build = fmap Build $ Build.Options
+    <$> switch (mconcat [
+        long "type-check",
+        help "Enable type checking"
+        ])
+    <*> strArgument (mconcat [
+        metavar "FILE"
+        ])
+
+
+-- ** Check
+
+check :: Parser Command
+check = Check . Check.Options <$> strArgument (mconcat [
+    metavar "FILE"
+    ])
+
+
+-- ** Print
+
+print :: Parser Command
+print = fmap Print $ Print.Options
+    <$> option auto (mconcat [
+        long "line-length",
+        short 'l',
+        metavar "LENGTH",
+        value 100,
+        showDefault,
+        help "Specify the maximum line length"
+        ])
+    <*> modeOption (mconcat [
+        long "mode",
+        short 'm',
+        metavar "MODE",
+        value PageMode,
+        showDefaultWith $ const "normal",
+        help "Specify the rendering mode as either 'normal' or 'one-line'"
+        ])
+    <*> option auto (mconcat [
+        long "ribbons-per-line",
+        short 'r',
+        metavar "RIBBONS",
+        value 1.5,
+        showDefault,
+        help "Specify the ratio of line length to ribbon length"
+        ])
+    <*> strArgument (mconcat [
+        metavar "FILE"
+        ])
+    where
+        modeOption :: Mod OptionFields Mode -> Parser Mode
+        modeOption = option $ do
+            opt <- readerAsk
+
+            case opt of
+                "normal"    -> return PageMode
+                "one-line"  -> return OneLineMode
+                _           -> readerError $ "unrecognised mode `" ++ opt ++ "'"
+
+-- ** Run
+
+run :: Parser Command
+run = fmap Run $ Run.Options
+    <$> strArgument (mconcat [
+        metavar "FILE"
+        ])
+    <*> many (strArgument $ mconcat [
+        metavar "ARGS..."
+        ])
+
diff --git a/src/Qux/Commands/Build.hs b/src/Qux/Commands/Build.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands/Build.hs
@@ -0,0 +1,47 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Qux.Commands.Build
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Qux.Commands.Build where
+
+import Control.Monad.Except
+
+import Language.Qux.Annotated.Parser
+import Language.Qux.Annotated.Syntax
+import Language.Qux.Annotated.TypeChecker
+
+import System.Exit
+import System.IO
+
+
+data Options = Options {
+    optTypeCheck    :: Bool,
+    argFilePath     :: String
+    }
+
+handle :: Options -> IO ()
+handle options = do
+    let filePath = argFilePath options
+
+    contents <- readFile $ argFilePath options
+
+    case runExcept $ tryParse filePath contents >>= build options of
+        Left error  -> hPutStrLn stderr error >> exitFailure
+        Right _     -> return ()
+
+tryParse :: FilePath -> String -> Except String (Program SourcePos)
+tryParse filePath contents = withExcept show (parse program filePath contents)
+
+build :: Options -> Program SourcePos -> Except String ()
+build options program = when (optTypeCheck options) (typeCheck program)
+
+typeCheck :: Program SourcePos -> Except String ()
+typeCheck program = withExcept show (check program)
+
diff --git a/src/Qux/Commands/Check.hs b/src/Qux/Commands/Check.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands/Check.hs
@@ -0,0 +1,26 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Qux.Commands.Check
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Qux.Commands.Check where
+
+import qualified Qux.Commands.Build as Build
+
+
+data Options = Options {
+    argFilePath :: String
+    }
+
+handle :: Options -> IO ()
+handle options = Build.handle Build.Options {
+    Build.argFilePath = argFilePath options,
+    Build.optTypeCheck = True
+    }
+
diff --git a/src/Qux/Commands/Print.hs b/src/Qux/Commands/Print.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands/Print.hs
@@ -0,0 +1,47 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Qux.Commands.Print
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Qux.Commands.Print where
+
+import Control.Monad.Except
+
+import Language.Qux.Annotated.PrettyPrinter
+
+import Qux.Commands.Build (tryParse)
+
+import System.Exit
+import System.IO
+
+
+data Options = Options {
+    optLineLength       :: Int,
+    optMode             :: Mode,
+    optRibbonsPerLine   :: Float,
+    argFilePath         :: String
+    }
+
+handle :: Options -> IO ()
+handle options = do
+    let filePath = argFilePath options
+
+    contents <- readFile $ argFilePath options
+
+    case runExcept $ tryParse filePath contents of
+        Left error      -> hPutStrLn stderr (show error) >> exitFailure
+        Right program   -> putStrLn $ renderStyle (style options) (pPrint program)
+
+style :: Options -> Style
+style options = Style {
+    mode            = optMode options,
+    lineLength      = optLineLength options,
+    ribbonsPerLine  = optRibbonsPerLine options
+    }
+
diff --git a/src/Qux/Commands/Run.hs b/src/Qux/Commands/Run.hs
new file mode 100644
--- /dev/null
+++ b/src/Qux/Commands/Run.hs
@@ -0,0 +1,48 @@
+
+{-# OPTIONS_HADDOCK hide, prune #-}
+
+{-|
+Module      : Qux.Commands.Run
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+-}
+
+module Qux.Commands.Run where
+
+import Control.Monad.Except
+
+import Language.Qux.Annotated.Simplify
+import Language.Qux.Annotated.Syntax
+import Language.Qux.Interpreter
+import Language.Qux.PrettyPrinter
+
+import Qux.Commands.Build (tryParse)
+
+import System.Exit
+import System.IO
+
+
+data Options = Options {
+    argFilePath :: FilePath,
+    argsExtra   :: [String]
+    }
+
+handle :: Options -> IO ()
+handle options = do
+    let filePath = argFilePath options
+
+    contents <- readFile $ argFilePath options
+
+    case runExcept $ tryParse filePath contents >>= run options of
+        Left error      -> hPutStrLn stderr error >> exitFailure
+        Right result    -> putStrLn result
+
+run :: Options -> Program a -> Except String String
+run options program = return $ render (pPrint result)
+    where
+        result = exec (sProgram program) main args
+        main = "main"
+        args = map (IntValue . read) (argsExtra options)
+
