diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## MMark CLI 0.0.1.0
+
+* Initial release.
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,28 @@
+Copyright © 2018 Mark Karpov
+
+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 Mark Karpov nor the names of 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 “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 HOLDERS 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,315 @@
+# MMark CLI
+
+[![License BSD3](https://img.shields.io/badge/license-BSD3-brightgreen.svg)](http://opensource.org/licenses/BSD-3-Clause)
+[![Hackage](https://img.shields.io/hackage/v/mmark-cli.svg?style=flat)](https://hackage.haskell.org/package/mmark-cli)
+[![Stackage Nightly](http://stackage.org/package/mmark-cli/badge/nightly)](http://stackage.org/nightly/package/mmark-cli)
+[![Stackage LTS](http://stackage.org/package/mmark-cli/badge/lts)](http://stackage.org/lts/package/mmark-cli)
+[![Build Status](https://travis-ci.org/mmark-md/mmark-cli.svg?branch=master)](https://travis-ci.org/mmark-md/mmark-cli)
+
+* [Templates](#tepmlates)
+* [Extensions](#extensions)
+  * [Comment paragraph](#comment-paragraph)
+  * [Font Awesome](#font-awesome)
+  * [Kbd tags](#kbd-tags)
+  * [Link targets](#link-targets)
+  * [Email obfuscation](#email-obfuscation)
+  * [Punctuation prettifier](#punctuation-prettifier)
+  * [Skylighting](#skylighting)
+  * [Table of contents](#table-of-contents)
+* [Contribution](#contribution)
+* [License](#license)
+
+This is a command line application serving as an interface to MMark markdown
+processor.
+
+```
+mmark—command line interface to MMark markdown processor
+
+Usage: mmark [-v|--version] [-i|--ifile IFILE] [-o|--ofile OFILE] [-j|--json]
+             [-t|--template FILE] [--ext-comment PREFIX] [--ext-font-awesome]
+             [--ext-kbd] [--ext-link-target] [--ext-obfuscate-email CLASS]
+             [--ext-punctuation] [--ext-skylighting] [--ext-toc RANGE]
+  Command line interface to MMark markdown processor
+
+Available options:
+  -h,--help                Show this help text
+  -v,--version             Print version of the program
+  -i,--ifile IFILE         Read markdown source from this file (otherwise read
+                           from stdin)
+  -o,--ofile OFILE         Save rendered HTML document to this file (otherwise
+                           write to stdout)
+  -j,--json                Output parse errors and result in JSON format
+  -t,--template FILE       Use the template located at this path
+  --ext-comment PREFIX     Remove paragraphs that start with the given prefix
+  --ext-font-awesome       Enable support for inserting font awesome icons
+  --ext-kbd                Enable support for wrapping things in kbd tags
+  --ext-link-target        Enable support for specifying link targets
+  --ext-obfuscate-email CLASS
+                           Obfuscate email addresses assigning the specified
+                           class
+  --ext-punctuation        Enable punctuation prettifier
+  --ext-skylighting        Enable syntax highlighting of code snippets with
+                           Skylighting
+  --ext-toc RANGE          Enable generation of table of contents using the
+                           supplied range of headers to include, e.g. "1-6" or
+                           "2-4"
+```
+
+## Templates
+
+By using the `--template` argument, it's possible to create a standalone
+HTML page. The templating system we use is
+[Mustache](https://mustache.github.io/mustache.5.html), as implemented by
+the [stache](https://hackage.haskell.org/package/stache) library. The
+library conforms to the version 1.1.3 of the official [Mustache
+specification](https://github.com/mustache/spec), but does not implement
+lambdas (which is an optional feature is the specification) for simplify and
+other technical reasons we won't touch here.
+
+If markdown source file has a YAML section, its contents will be provided as
+context for rendering of the template. In addition to that, a new top-level
+value bound to the variable named `output` will be available. That variable
+contains the HTML rendition of markdown document. It's best to interpolate
+it without HTML escaping, like so: `{{& output }}`.
+
+## Extensions
+
+Here we list how to use the available extensions. The extensions come from
+the [`mmark-ext`](https://hackage.haskell.org/package/mmark-ext) package.
+
+### Comment paragraph
+
+* Option: `--ext-comment PREFIX`
+
+This extension removes paragraphs that start with the given `PREFIX`. For
+example:
+
+```
+$ mmark --ext-comment REM
+First.
+
+REM Second.
+
+Third.
+----------------------- Control-D
+<p>First.</p>
+<p>Third.</p>
+```
+
+### Font awesome
+
+* Option: `--ext-font-awesome`
+
+This allows to turn autolinks with `fa` scheme into font awesome icons:
+
+```
+$ mmark --ext-font-awesome
+Here is the user icon: <fa:user>.
+
+A more interesting example: <fa:quote-left/3x/pull-left/border>.
+----------------------- Control-D
+<p>Here is the user icon: <span class="fa fa-user"></span>.</p>
+<p>A more interesting example:
+  <span class="fa fa-quote-left fa-3x fa-pull-left fa-border"></span>.
+</p>
+```
+
+In general, all path components in URIs that go after the name of icon will
+be prefixed with `"fa-"` and added as classes, so you can do a lot of fancy
+stuff, see <http://fontawesome.io/examples/>.
+
+### Kbd tags
+
+* Option: `--ext-kbd`
+
+Introduce kbd tags into resulting HTML document by wrapping content in links
+with URL with kbd scheme. For example:
+
+```
+$ mmark --ext-kbd
+To enable that mode press [Ctrl+A][kbd].
+
+[kbd]: kbd:
+----------------------- Control-D
+<p>To enable that mode press <kbd>Ctrl+A</kbd>.</p>
+```
+
+The use of reference-style links seems more aesthetically pleasant to the
+author, but you can of course do something like this instead:
+
+```
+To enable that mode press [Ctrl+A](kbd:).
+```
+
+### Link targets
+
+* Option: `--ext-link-target`
+
+When title of a link starts with the word `"_blank"`, `"_self"`,
+`"_parent"`, or `"_top"`, it's stripped from title (as well as all
+whitespace after it) and added as the value of target attribute of the
+resulting link. For example:
+
+```
+$ mmark --ext-kbd
+This [link](/url '_blank My title') opens in new tab.
+----------------------- Control-D
+<p>This <a href="/url" title="My title" target="_blank">link</a>
+opens in new tab.</p>
+```
+
+### Email obfuscation
+
+* Option: `--obfuscate-email CLASS`
+
+This extension makes email addresses in autolinks be rendered as something
+like this:
+
+```
+[mark@arch ~]$ mmark --ext-obfuscate-email protected-email
+Send all your spam to <someone@example.org>, if you can!
+----------------------- Control-D
+<p>Send all your spam to
+  <a href="javascript:void(0)"
+     class="protected-email"
+     data-email="someone@example.org">
+  Enable JavaScript to see this email</a>, if you can!
+</p>
+```
+
+You'll also need to include jQuery and this bit of JS code for the magic to
+work:
+
+```java-script
+$(document).ready(function () {
+    $(".protected-email").each(function () {
+        var item = $(this);
+        var email = item.data('email');
+        item.attr('href', 'mailto:' + email);
+        item.html(email);
+    });
+});
+```
+
+### Punctuation prettifier
+
+* Option: `--ext-punctuation`
+
+This makes MMark prettify punctuation (only affects plain text in inlines),
+the effect is the following:
+
+* Replace `...` with ellipsis `…`
+* Replace `---` with em-dash `—`
+* Replace `--` with en-dash `–`
+* Replace `"` with left double quote `“` when previous character was a space
+  character, otherwise replace it with right double quote `”`
+* Replace `'` with left single quote `‘` when previous character was a space
+  character, otherwise replace it with right single quote `’` aka apostrophe
+
+For example (not sure this is the correct punctuation to use here, but it
+demonstrates the effect):
+
+```
+[mark@arch ~]$ mmark --ext-punctuation
+Something---we don't know what, happened...
+----------------------- Control-D
+<p>Something—we don’t know what, happened…</p>
+```
+
+### Skylighting
+
+* Option: `--ext-skylighting`
+
+Use the [skylighting](https://hackage.haskell.org/package/skylighting)
+package to render code blocks with info strings that result in a successful
+lookup from syntax table that comes with the library.
+
+The resulting markup is wrapped with spans as described in the docs for
+[`formatHtmlInline`](https://hackage.haskell.org/package/skylighting/docs/Skylighting-Format-HTML.html#v:formatHtmlInline).
+
+Example:
+
+````
+[mark@arch ~]$ mmark --ext-skylighting
+Some Haskell:
+
+```haskell
+main :: IO ()
+main = return ()
+```
+----------------------- Control-D
+<p>Some Haskell:</p>
+<div class="sourceCode">
+  <pre class="sourceCode">
+    <code class="sourceCode">
+      <a class="sourceLine" id="1" data-line-number="1">
+        <span class="ot">main ::</span> <span class="dt">IO</span> ()
+      </a>
+      <a class="sourceLine" id="2" data-line-number="2">main <span class="fu">=</span> return ()</a>
+    </code>
+  </pre>
+</div>
+````
+
+### Table of contents
+
+* Option: `--ext-toc RANGE`
+
+Replace the code block with info string `"toc"` by table of contents
+assembled from headings with levels from `N` to `M`, where `N-M` is `RANGE`.
+
+For example:
+
+````
+[mark@arch ~]$ mmark --ext-toc 2-4
+# Story of my life
+
+```toc
+```
+
+## Charpter 1
+
+Foo.
+
+## Chapter 2
+
+Bar.
+
+### Something
+
+Baz.
+----------------------- Control-D
+<h1 id="story-of-my-life">Story of my life</h1>
+<ul>
+<li>
+<a href="#charpter-1">Charpter 1</a>
+</li>
+<li>
+<a href="#chapter-2">Chapter 2</a>
+<ul>
+<li>
+<a href="#something">Something</a>
+</li>
+</ul>
+</li>
+</ul>
+<h2 id="charpter-1">Charpter 1</h2>
+<p>Foo.</p>
+<h2 id="chapter-2">Chapter 2</h2>
+<p>Bar.</p>
+<h3 id="something">Something</h3>
+<p>Baz.</p>
+````
+
+## Contribution
+
+Issues, bugs, and questions may be reported in [the GitHub issue tracker for
+this project](https://github.com/mmark-md/mmark-cli/issues).
+
+Pull requests are also welcome and will be reviewed quickly.
+
+## License
+
+Copyright © 2018 Mark Karpov
+
+Distributed under BSD 3 clause license.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,246 @@
+{-# LANGUAGE CPP               #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+{-# LANGUAGE TemplateHaskell   #-}
+{-# LANGUAGE TupleSections     #-}
+
+module Main (main) where
+
+import Control.Applicative
+import Control.Monad
+import Data.Aeson ((.=), Value (..))
+import Data.List (intercalate)
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.Text (Text)
+import Data.Version (showVersion)
+import Data.Void
+import Development.GitRev
+import Options.Applicative hiding (ParseError)
+import Paths_mmark_cli (version)
+import Skylighting (defaultFormatOpts)
+import System.Directory (makeAbsolute)
+import System.Exit (exitFailure)
+import Text.MMark (MMarkErr)
+import Text.Megaparsec (Parsec, ParseError, SourcePos (..))
+import qualified Data.Aeson                  as Aeson
+import qualified Data.ByteString.Lazy.Char8  as BL
+import qualified Data.HashMap.Strict         as HM
+import qualified Data.Text                   as T
+import qualified Data.Text.IO                as T
+import qualified Data.Text.Lazy              as TL
+import qualified Lucid                       as L
+import qualified Text.MMark                  as MMark
+import qualified Text.MMark.Extension.Common as Ext
+import qualified Text.Megaparsec             as M
+import qualified Text.Megaparsec.Char        as MC
+import qualified Text.Megaparsec.Char.Lexer  as MCL
+import qualified Text.Mustache               as U
+
+-- | Entry point of the program.
+
+main :: IO ()
+main = do
+  Opts {..} <- execParser optsParserInfo
+  (mdFileName, mdInput) <-
+    case optInputFile of
+      Nothing -> ("<stdin>",) <$> T.getContents
+      Just file -> do
+        absFile <- makeAbsolute file
+        (absFile,) <$> T.readFile absFile
+  case MMark.parse mdFileName mdInput of
+    Left errs -> do
+      if optJson
+        then (BL.putStrLn . Aeson.encode . parseErrorsJson) errs
+        else putStr (MMark.parseErrorsPretty mdInput errs)
+      exitFailure
+    Right doc -> do
+      let exts = mconcat
+            [ g optExtComment               Ext.commentParagraph
+            , f optExtFontAwesome           Ext.fontAwesome
+            , f optExtKbd                   Ext.kbd
+            , f optExtLinkTarget            Ext.linkTarget
+            , g optExtObfuscateEmail        Ext.obfuscateEmail
+            , f optExtPunctuationPrettifier Ext.punctuationPrettifier
+            , f optExtSkylighting           Ext.skylighting defaultFormatOpts
+            , g optExtToc $ \(from,to) ->
+               Ext.toc "toc" . MMark.runScanner doc . Ext.tocScanner $ \x ->
+                 from <= x && x <= to
+            ]
+          f p x = if p then x else mempty
+          g p x = maybe mempty x p
+          applyTemplate tfile output = do
+            t <- U.compileMustacheFile tfile
+            return . TL.toStrict . U.renderMustache t $
+              case MMark.projectYaml doc of
+                Just (Object m) -> Object $
+                  HM.insert "output" (String output) m
+                _ -> Aeson.object
+                  [ "output" .= output
+                  ]
+      htmlOutput <- maybe return applyTemplate optTemplate
+        . TL.toStrict
+        . L.renderText
+        . MMark.render
+        . MMark.useExtension exts
+        $ doc
+      if optJson
+        then maybe BL.putStrLn BL.writeFile optOutputFile $
+               Aeson.encode (htmlDocJson htmlOutput)
+        else maybe T.putStr T.writeFile optOutputFile htmlOutput
+
+----------------------------------------------------------------------------
+-- Command line options parsing
+
+-- | Command line options.
+
+data Opts = Opts
+  { optInputFile  :: !(Maybe FilePath)
+    -- ^ File from which to read input (otherwise use stdin)
+  , optOutputFile :: !(Maybe FilePath)
+    -- ^ File to which to save output (otherwise use stdout)
+  , optJson :: !Bool
+    -- ^ Whether to output JSON
+  , optTemplate :: !(Maybe FilePath)
+    -- ^ Use the template located at this path
+
+  , optExtComment :: !(Maybe Text)
+    -- ^ Enable extension: 'Ext.commentParagraph'
+  , optExtFontAwesome :: !Bool
+    -- ^ Enable extension: 'Ext.fontAwesome'
+  , optExtKbd :: !Bool
+    -- ^ Enable extension: 'Ext.kbd'
+  , optExtLinkTarget :: !Bool
+    -- ^ Enable extension: 'Ext.linkTarget'
+  , optExtObfuscateEmail :: !(Maybe Text)
+    -- ^ Enable extension: 'Ext.obfuscateEmail'
+  , optExtPunctuationPrettifier :: !Bool
+    -- ^ Enable extension: 'Ext.punctuationPrettifier'
+  , optExtSkylighting :: !Bool
+    -- ^ Enable extension: 'Ext.skylighting'
+  , optExtToc :: !(Maybe (Int, Int))
+    -- ^ Enable extension: 'Ext.toc'
+  }
+
+optsParserInfo :: ParserInfo Opts
+optsParserInfo = info (helper <*> ver <*> optsParser) . mconcat $
+  [ fullDesc
+  , progDesc "Command line interface to MMark markdown processor"
+  , header   "mmark—command line interface to MMark markdown processor"
+  ]
+  where
+    ver :: Parser (a -> a)
+    ver = infoOption verStr . mconcat $
+      [ long  "version"
+      , short 'v'
+      , help  "Print version of the program"
+      ]
+    verStr = intercalate "\n"
+      [ unwords
+        [ "mmark"
+        , showVersion version
+        , $gitBranch
+        , $gitHash
+        ]
+      , "using mmark     (library) " ++ VERSION_mmark
+      , "using mmark-ext (library) " ++ VERSION_mmark_ext
+      ]
+
+optsParser :: Parser Opts
+optsParser = Opts
+  <$> (optional . strOption . mconcat)
+    [ long    "ifile"
+    , short   'i'
+    , metavar "IFILE"
+    , help    "Read markdown source from this file (otherwise read from stdin)"
+    ]
+  <*> (optional . strOption . mconcat)
+    [ long    "ofile"
+    , short   'o'
+    , metavar "OFILE"
+    , help    "Save rendered HTML document to this file (otherwise write to stdout)"
+    ]
+  <*> (switch . mconcat)
+    [ long    "json"
+    , short   'j'
+    , help    "Output parse errors and result in JSON format"
+    ]
+  <*> (optional . strOption . mconcat)
+    [ long    "template"
+    , short   't'
+    , metavar "FILE"
+    , help    "Use the template located at this path"
+    ]
+  <*> (optional . fmap T.pack . strOption . mconcat)
+    [ long    "ext-comment"
+    , metavar "PREFIX"
+    , help    "Remove paragraphs that start with the given prefix"
+    ]
+  <*> (switch . mconcat)
+    [ long    "ext-font-awesome"
+    , help    "Enable support for inserting font awesome icons"
+    ]
+  <*> (switch . mconcat)
+    [ long    "ext-kbd"
+    , help    "Enable support for wrapping things in kbd tags"
+    ]
+  <*> (switch . mconcat)
+    [ long    "ext-link-target"
+    , help    "Enable support for specifying link targets"
+    ]
+  <*> (optional . fmap T.pack . strOption . mconcat)
+    [ long    "ext-obfuscate-email"
+    , metavar "CLASS"
+    , help    "Obfuscate email addresses assigning the specified class"
+    ]
+  <*> (switch . mconcat)
+    [ long    "ext-punctuation"
+    , help    "Enable punctuation prettifier"
+    ]
+  <*> (switch . mconcat)
+    [ long    "ext-skylighting"
+    , help    "Enable syntax highlighting of code snippets with Skylighting"
+    ]
+  <*> (optional . option parseRange . mconcat)
+    [ long    "ext-toc"
+    , metavar "RANGE"
+    , help    ("Enable generation of table of contents using the supplied "
+      ++       "range of headers to include, e.g. \"1-6\" or \"2-4\"")
+    ]
+
+----------------------------------------------------------------------------
+-- Helpers
+
+-- | Represent the given collection of parse errors as 'Value'.
+
+parseErrorsJson :: NonEmpty (ParseError Char MMarkErr) -> Value
+parseErrorsJson = Aeson.toJSON . fmap parseErrorObj
+  where
+    parseErrorObj :: ParseError Char MMarkErr -> Value
+    parseErrorObj err = let (SourcePos {..}:|_) = M.errorPos err in Aeson.object
+      [ "file"   .= sourceName
+      , "line"   .= M.unPos sourceLine
+      , "column" .= M.unPos sourceColumn
+      , "text"   .= M.parseErrorTextPretty err
+      ]
+
+-- | Represent the given rendered HTML document as 'Aeson.Value'.
+
+htmlDocJson :: Text -> Value
+htmlDocJson html = Aeson.object
+  [ "html" .= html
+  ]
+
+-- | Parse a range as two positive numbers separated by a hyphen.
+
+parseRange :: ReadM (Int, Int)
+parseRange = eitherReader $ \s ->
+  case M.parse p "" s of
+    Left err -> Left (M.parseErrorTextPretty err)
+    Right x  -> Right x
+  where
+    p :: Parsec Void String (Int, Int)
+    p = do
+      from <- MCL.decimal
+      void (MC.char '-')
+      to   <- MCL.decimal
+      return (from, to)
diff --git a/mmark-cli.cabal b/mmark-cli.cabal
new file mode 100644
--- /dev/null
+++ b/mmark-cli.cabal
@@ -0,0 +1,49 @@
+name:                 mmark-cli
+version:              0.0.1.0
+cabal-version:        >= 1.18
+tested-with:          GHC==8.0.2, GHC==8.2.2
+license:              BSD3
+license-file:         LICENSE.md
+author:               Mark Karpov <markkarpov92@gmail.com>
+maintainer:           Mark Karpov <markkarpov92@gmail.com>
+homepage:             https://github.com/mmark-md/mmark-cli
+bug-reports:          https://github.com/mmark-md/mmark-cli/issues
+category:             Categories
+synopsis:             Description
+build-type:           Simple
+description:          Description.
+extra-doc-files:      CHANGELOG.md
+                    , README.md
+
+source-repository head
+  type:               git
+  location:           https://github.com/mmark-md/mmark-cli.git
+
+flag dev
+  description:        Turn on development settings.
+  manual:             True
+  default:            False
+
+executable mmark
+  main-is:            Main.hs
+  hs-source-dirs:     app
+  build-depends:      aeson            >= 0.11  && < 1.3
+                    , base             >= 4.9   && < 5.0
+                    , bytestring       >= 0.9.2 && < 0.11
+                    , directory        >= 1.2.2 && < 1.4
+                    , gitrev           >= 1.3   && < 1.4
+                    , lucid            >= 2.6   && < 3.0
+                    , megaparsec       >= 6.4   && < 7.0
+                    , mmark            >= 0.0.4 && < 0.1
+                    , mmark-ext        >= 0.1   && < 0.2
+                    , optparse-applicative >= 0.14 && < 0.15
+                    , skylighting      >= 0.5   && < 0.6
+                    , stache           >= 1.2   && < 1.3
+                    , text             >= 0.2   && < 1.3
+                    , unordered-containers >= 0.2.5 && < 0.3
+  other-modules:      Paths_mmark_cli
+  if flag(dev)
+    ghc-options:      -Wall -Werror
+  else
+    ghc-options:      -O2 -Wall
+  default-language:   Haskell2010
