diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,27 @@
+## 0.1.1.0
+
+-   Small refactor and quality of life additions.
+    Thanks to [@Vlix](https://github.com/Vlix)
+    [#72](https://github.com/cdepillabout/password/pull/72)
+
+-   Changes include:
+    - More complete explanation of the CLI in the README.
+    - Added more description of commands and options.
+    - Added option to read literal contents of a file.
+    - Hash output now adds a newline when using the CLI interactively. (on Unix)
+    - Added `--version` to only output the version of the CLI.
+
+## 0.1.0.0
+
+-   First minimal working CLI to hash passwords and verify hashes.
+    Thanks to [@blackheaven](https://github.com/blackheaven)
+    [#70](https://github.com/cdepillabout/password/pull/70)
+
+-   Functionality includes:
+    - Hashing (`Argon2`, `bcrypt`, `PBKDF2`, `scrypt`) interactively,
+      piped to `stdin`, or from the first line in a provided file.
+    - Checking a hash (`Argon2`, `bcrypt`, `PBKDF2`, `scrypt`) that is
+      provided through a CLI option, or from a provided file. The password
+      can be entered interactively, piped to `stdin` or from the first
+      line in a provided file.
+    - Option to disable logging to stdout or stderr. `-q|--quiet`
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020
+
+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 Dennis Gosnell, Felix Paulusma nor the names
+      of other 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
+OWNER 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,101 @@
+# `password-cli`
+
+[![Build Status](https://github.com/cdepillabout/password/workflows/password/badge.svg)](http://github.com/cdepillabout/password)
+[![Hackage](https://img.shields.io/hackage/v/password-cli.svg)](https://hackage.haskell.org/package/password-cli)
+[![Stackage LTS](http://stackage.org/package/password-cli/badge/lts)](http://stackage.org/lts/package/password-cli)
+[![Stackage Nightly](http://stackage.org/package/password-cli/badge/nightly)](http://stackage.org/nightly/package/password-cli)
+[![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE)
+
+This package provides a simple CLI for the [`password`](https://hackage.haskell.org/package/password) package.
+As such it supports all the algorithms that the [`password`](https://hackage.haskell.org/package/password)
+package supports, which at the time of writing are `Argon2`, `brypt`, `PBKDF2` and `scrypt`.
+
+At the moment, the default settings are used for each algorithm, but this will probably become configurable in
+a later version of the CLI.
+
+## Example usage
+
+The following sections give examples of how the CLI can be used.
+
+### Hashing a password interactively
+
+Hashing a password interactively is as easy as
+```console
+$ password-cli hash bcrypt
+Enter password:
+```
+where the input is then hidden and the hash is printed to the screen, resulting in
+```console
+$ password-cli hash bcrypt
+Enter password:
+$2b$10$JuNbIWqVQD2EldT481zEEuaVKROrYhsHXLjM/Tx3e7ahJQxVw7N4y
+```
+
+### Hashing a password with pipes
+
+When piping in the password from a file or other program:
+```console
+$ cat password.txt | password-cli hash pbkdf2
+Enter password:
+sha512:25000:8ZJ1T55Y0sPRwltXNe/2fA==:aA0BT1WlTg+t2pSr8E6+l2zJW88rmUiDlKeohSOnzS0nLOumDSyK0FfsiNJBvWvWVkB2r6IMxRqelk4LZR33ow==
+```
+You'll notice the output has no newline, so you can easily pipe the resulting
+hash into a file or other program. When piping the result to a file, you'll
+probably want to use `--quiet` or `-q` to make sure the `Enter password:` prompt
+isn't also saved to the file.
+```console
+$ cat password.txt | password-cli hash pbkdf2 --quiet > password.hash
+$ cat password.hash
+sha512:25000:iFYCOgfOgMPp0NuPXhyucw==:XUMDNnqZo2LH08CIZr+1nbTke3N6pE95FcbZA+4A1Ng4dWHnnl4SMUTn3KXFtB0uZRrEhArLatLAH1Oo8brcVw==
+```
+When piping in the password, the first line of the file (i.e. up to the first newline)
+is read and taken as the password. This is also the case if the password is provided
+from a file, though you can set the `--literal-contents` flag to use the entire literal
+file contents as the password.
+
+### Hashing a password from a file
+
+Instead of piping in the contents of a file, you can also just provide the path
+to the file.
+```console
+$ password-cli hash scrypt --password-file password.txt
+14|8|1|mdSECCGuEMf7GQOp9EX5EYLMW9Jwe6Dma7fwbxuNwvs=|KSh5jxOEiQPMjfng2D05/G1baiF2LyluWgg3Cfzh5arJUF3K7irRIBXoKAT/xCO11oPmsgDD7TT6l6FQth9f4g==
+```
+Here you don't have to pass in the `--quiet` option, since the password is already provided
+so the CLI doesn't print `Enter password:` to the screen.
+
+### Verifying a password hash
+
+Just like when hashing a password, you can input the password manually, through pipes, or
+by providing a `--password-file`.
+```console
+$ # Interactively check password
+$ password-cli check argon2 --hash 'SOME-HASH'
+Enter password:
+Password matches provided hash
+$ echo $?
+0
+```
+If the provided hash doesn't match the password, `Password does not match provided hash`
+will be shown and the exit code will be `1` to indicate a failed match.
+```console
+$ # Pipe in the password.
+$ cat password.txt | password-cli check argon2 --hash 'SOME-HASH' --quiet
+$ echo $?
+0
+$ # Give the WRONG password file.
+$ password-cli check argon2 --hash 'SOME-HASH' --password-file password.txt.wrong --quiet
+$ echo $?
+1
+```
+NOTE: When giving literal hashes as arguments in the terminal,
+it is advised to use single quotes (`'`) instead of double quotes (`"`)
+to prevent any accidental interpolation as some hashes use
+dollar signs (`$`) in the format.
+
+You can also provide the hash from file contents by providing the path to the `--hash-file`
+option. Just like the default of the `--password-file` option, this will only read up to the
+first newline.
+```console
+$ password-cli check argon2 --hash-file password.hash
+```
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Main (main) where
+
+import Control.Exception (bracket_, evaluate)
+import Control.Monad (unless, void, (<=<))
+import qualified Data.ByteString.Char8 as B (readFile)
+import Data.Maybe (isNothing)
+import Data.Password.Bcrypt (PasswordCheck (..))
+import Data.Password.Types (Password, mkPassword)
+import Data.Text as T (Text, strip)
+import qualified Data.Text.Encoding as TE
+import qualified Data.Text.IO as T
+import Options.Applicative
+import System.Exit (exitFailure)
+import System.IO (IOMode (ReadMode), hIsTerminalDevice, hSetEcho, stderr, stdin, stdout, withFile)
+
+import Options
+
+main :: IO ()
+main =
+  customExecParser (prefs showHelpOnEmpty) cliOpts >>= runCmd
+
+runCmd :: CLIOptions -> IO ()
+runCmd CLIOptions{..} =
+    case cmd of
+        HashCmd opts -> runHashCmd quiet opts
+        CheckCmd opts -> runCheckCmd quiet opts
+        HelpCmd cmds -> void $ runHelpCmd cmds
+
+runHashCmd :: Bool -> HashOpts -> IO ()
+runHashCmd quiet HashOpts {..} = do
+    pw <- getPassword quiet hashPassword
+    hash <- hashWithAlgorithm pw hashAlgorithm
+    b <- hIsTerminalDevice stdin
+    -- We only want the extra newline in interactive mode
+    -- and NOT when the password is given via file path.
+    let output =
+            if b && isNothing hashPassword
+                then T.putStrLn
+                else T.putStr
+    output hash
+
+runCheckCmd :: Bool -> CheckOpts -> IO ()
+runCheckCmd quiet CheckOpts {..} = do
+    pw <- getPassword quiet checkPassword
+    hashedPW <- getHash hash
+    case checkWithAlgorithm pw hashedPW checkAlgorithm of
+        PasswordCheckSuccess ->
+            qLog stdout "Password matches provided hash"
+        PasswordCheckFail -> do
+            qLog stderr "Password does not match provided hash"
+            exitFailure
+  where
+    qLog h = unless quiet . T.hPutStrLn h
+
+runHelpCmd :: [String] -> IO CLIOptions
+runHelpCmd cmds =
+  let args = cmds ++ ["-h"]
+   in handleParseResult $ execParserPure defaultPrefs cliOpts args
+
+getPassword :: Bool -> Maybe FromFileOptions -> IO Password
+getPassword quiet = \case
+    Just FromFileOptions{..} ->
+        mkPassword <$> readLine parseLiteralContents fromFile
+    Nothing -> do
+        -- Wondering if we should also gate this behind `hIsTerminalDevice`?
+        -- Though that could mean Windows users would never see this in certain terminals.
+        unless quiet $
+            putStrLn "Enter password:"
+        bracket_
+            (hSetEcho stdin False)
+            (hSetEcho stdin True)
+            (mkPassword <$> T.hGetLine stdin)
+
+getHash :: Either FromFileOptions Text -> IO Text
+getHash = \case
+    Right hash -> pure hash
+    Left FromFileOptions{..} -> readLine parseLiteralContents fromFile
+
+readLine :: Bool -> FilePath -> IO Text
+readLine literal x
+    | literal = readFullFile x
+    -- When not given the 'parseLiteralContents' flag, we want to be
+    -- lenient, and ignore white space around any hash, since those
+    -- will never be part of any hash, and probably also not a password.
+    | otherwise = T.strip <$> withFile x ReadMode T.hGetLine
+
+-- | Copied from newest 'text-2.1' 'Data.Text.IO.Utf8' module
+readFullFile :: FilePath -> IO Text
+readFullFile = evaluate . TE.decodeUtf8 <=< B.readFile
diff --git a/app/Options.hs b/app/Options.hs
new file mode 100644
--- /dev/null
+++ b/app/Options.hs
@@ -0,0 +1,199 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Options where
+
+import qualified Data.Password.Argon2 as Argon2
+import Data.Password.Bcrypt (PasswordCheck (..))
+import qualified Data.Password.Bcrypt as Bcrypt
+import qualified Data.Password.PBKDF2 as PBKDF2
+import qualified Data.Password.Scrypt as Scrypt
+import Data.Password.Types
+import Data.Text (Text)
+import Data.Version (showVersion)
+import Options.Applicative
+import Paths_password_cli (version)
+
+data CLIOptions = CLIOptions
+    { quiet :: Bool
+    -- ^ Prevent any logging to stdout or stderr.
+    , cmd :: Cmd
+    -- ^ What command
+    }
+
+data Cmd
+    = HashCmd HashOpts
+    | CheckCmd CheckOpts
+    | HelpCmd [String]
+
+data FromFileOptions = FromFileOptions
+    { fromFile :: FilePath
+    , parseLiteralContents :: Bool
+    }
+
+data HashOpts = HashOpts
+    { hashPassword :: Maybe FromFileOptions
+    , hashAlgorithm :: HashAlgorithm
+    }
+
+data CheckOpts = CheckOpts
+    { hash :: Either FromFileOptions Text
+    , checkPassword :: Maybe FromFileOptions
+    , checkAlgorithm :: CheckAlgorithm
+    }
+
+data HashAlgorithm
+    = PBKDF2HashAlgo PBKDF2.PBKDF2Params
+    | BcryptHashAlgo Int
+    | ScryptHashAlgo Scrypt.ScryptParams
+    | Argon2HashAlgo Argon2.Argon2Params
+
+hashWithAlgorithm :: Password -> HashAlgorithm -> IO Text
+hashWithAlgorithm pw = \case
+    Argon2HashAlgo p ->
+        unPasswordHash <$> Argon2.hashPasswordWithParams p pw
+    BcryptHashAlgo p ->
+        unPasswordHash <$> Bcrypt.hashPasswordWithParams p pw
+    PBKDF2HashAlgo p ->
+        unPasswordHash <$> PBKDF2.hashPasswordWithParams p pw
+    ScryptHashAlgo p ->
+        unPasswordHash <$> Scrypt.hashPasswordWithParams p pw
+
+data CheckAlgorithm
+    = PBKDF2CheckAlgo
+    | BcryptCheckAlgo
+    | ScryptCheckAlgo
+    | Argon2CheckAlgo
+
+checkWithAlgorithm :: Password -> Text -> CheckAlgorithm -> PasswordCheck
+checkWithAlgorithm pw hashT = \case
+    Argon2CheckAlgo -> Argon2.checkPassword pw hash
+    BcryptCheckAlgo -> Bcrypt.checkPassword pw hash
+    PBKDF2CheckAlgo -> PBKDF2.checkPassword pw hash
+    ScryptCheckAlgo -> Scrypt.checkPassword pw hash
+  where
+    hash :: PasswordHash a
+    hash = PasswordHash hashT
+
+cliOpts :: ParserInfo CLIOptions
+cliOpts =
+    info cliOptsParser infoMods
+  where
+    v = showVersion version
+    infoMods =
+        fullDesc
+            <> header ("Password CLI " <> v)
+            <> progDesc
+                "A command line interface to hash and check passwords in the terminal."
+    cliOptsParser =
+        CLIOptions
+            <$> switch (short 'q' <> long "quiet" <> help "Suppress logging to stdout and stderr")
+            <*> commandsParser
+            <**> versionOpt v
+            <**> helper
+
+-- Copied over from 'optparse-applicative-0.17.1.0's 'simpleVersioner'
+versionOpt :: String -> Parser (a -> a)
+versionOpt v =
+    infoOption v $
+        long "version" <> help "Show version information" <> hidden
+
+commandsParser :: Parser Cmd
+commandsParser =
+    hsubparser $
+        command "hash" hashCmd
+            <> command "check" checkCmd
+            <> command "help" helpCmd
+  where
+    hashCmd =
+        info (HashCmd <$> hashOptsParser) $
+            progDesc
+                "Hash a password from a file, or via stdin. Outputs to stdout.\
+                \ (Will add a newline at the end when used interactively on Unix)"
+    checkCmd =
+        info (CheckCmd <$> checkOptsParser) $
+            progDesc
+                "Verify a hashed password from file or stdin. Returns exit code 0\
+                \ when successful, and exit code 1 when the password did not match."
+    helpCmd =
+        info (HelpCmd <$> many (argument str (metavar "COMMAND"))) $
+            progDesc "Show help text for the given command(s)"
+
+hashOptsParser :: Parser HashOpts
+hashOptsParser =
+    HashOpts <$> passwordFileOption <*> hashParser
+
+checkOptsParser :: Parser CheckOpts
+checkOptsParser =
+    CheckOpts <$> hashOption <*> passwordFileOption <*> checkParser
+  where
+    hashOption =
+        Right <$> strOption (metavar "HASH" <> long "hash" <> help hashOptionMsg)
+            <|> Left <$> hashFileOption
+    hashOptionMsg = "Provide the hash as an option."
+
+fromFileOption :: Bool -> Parser FilePath -> Parser FromFileOptions
+fromFileOption acceptLiteral fileOption =
+    FromFileOptions <$> fileOption <*> literalParseSwitch
+  where
+    literalParseSwitch
+        | acceptLiteral =
+            switch $ long "literal-contents" <> help helpMsg
+        | otherwise = pure False
+    helpMsg =
+        "Hash all contents of the file. (by default only reads first line)"
+
+passwordFileOption :: Parser (Maybe FromFileOptions)
+passwordFileOption =
+    optional . fromFileOption True $
+        strOption (metavar "FILE" <> long "password-file" <> help "Hash password from file contents")
+
+-- | We don't want to allow the '--literal-contents' option,
+-- because hashes never have newlines and 'readLine' reads up to the first newline.
+hashFileOption :: Parser FromFileOptions
+hashFileOption =
+    fromFileOption False $
+        strOption (metavar "FILE" <> long "hash-file" <> help "Use hash from file contents")
+
+
+data AlgoParsers a =
+    AlgoParsers
+        { argon2Parser :: Parser a
+        , bcryptParser :: Parser a
+        , pbkdf2Parser :: Parser a
+        , scryptParser :: Parser a
+        , commandDesc :: String -> String
+        }
+
+algorithmParser :: AlgoParsers a -> Parser a
+algorithmParser AlgoParsers{..} =
+    hsubparser
+        ( commandGroup "Available algorithms:"
+            <> command "argon2" (info argon2Parser (progDesc $ commandDesc "Argon2"))
+            <> command "bcrypt" (info bcryptParser (progDesc $ commandDesc "bcrypt"))
+            <> command "pbkdf2" (info pbkdf2Parser (progDesc $ commandDesc "PBKDF2"))
+            <> command "scrypt" (info scryptParser (progDesc $ commandDesc "scrypt"))
+        )
+
+hashParser :: Parser HashAlgorithm
+hashParser =
+    algorithmParser
+        AlgoParsers
+            { argon2Parser = pure $ Argon2HashAlgo Argon2.defaultParams
+            , bcryptParser = pure $ BcryptHashAlgo Bcrypt.defaultParams
+            , pbkdf2Parser = pure $ PBKDF2HashAlgo PBKDF2.defaultParams
+            , scryptParser = pure $ ScryptHashAlgo Scrypt.defaultParams
+            , commandDesc = ("Hash a password using " <>)
+            }
+
+checkParser :: Parser CheckAlgorithm
+checkParser =
+    algorithmParser
+        AlgoParsers
+            { argon2Parser = pure Argon2CheckAlgo
+            , bcryptParser = pure BcryptCheckAlgo
+            , pbkdf2Parser = pure PBKDF2CheckAlgo
+            , scryptParser = pure ScryptCheckAlgo
+            , commandDesc = \algo -> "Check a " <> algo <> " hash"
+            }
diff --git a/password-cli.cabal b/password-cli.cabal
new file mode 100644
--- /dev/null
+++ b/password-cli.cabal
@@ -0,0 +1,42 @@
+cabal-version:  3.0
+
+name:           password-cli
+version:        0.1.1.0
+category:       CLI
+synopsis:       use password from your CLI
+description:    A simple CLI tool to interact with password
+homepage:       https://github.com/cdepillabout/password/tree/master/password-cli#readme
+bug-reports:    https://github.com/cdepillabout/password/issues
+author:         Dennis Gosnell, Felix Paulusma
+maintainer:     cdep.illabout@gmail.com, felix.paulusma@gmail.com
+copyright:      Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020
+license:        BSD-3-Clause
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/cdepillabout/password
+
+
+executable password-cli
+  -- type: exitcode-stdio-1.0
+  main-is: Main.hs
+  other-modules:
+    Options
+    Paths_password_cli
+  autogen-modules:
+    Paths_password_cli
+  hs-source-dirs: app
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints
+  build-depends:
+      base >= 4.15 && < 5
+    , bytestring >= 0.9 && < 1
+    , password ^>= 3.0.3.0 || ^>= 3.1.0.0
+    , password-types ^>= 1.0
+    , optparse-applicative >= 0.14.3 && < 0.20
+    , text >= 1.2 && < 3
+  default-language: Haskell2010
