diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,8 +4,15 @@
 [![CircleCI](https://circleci.com/gh/collegevine/confcrypt/tree/master.svg?style=svg)](https://circleci.com/gh/collegevine/confcrypt/tree/master)
 
 ## Installing confcrypt
-#### Mac OSX
+#### Mac OSX via Brew
+```
+brew tap collegevine/brew
+brew cask install confcrypt
+```
+#### Windows, Linux, & OSX native
 
+1. If you don't have Haskell installed and working already, you'll need it.  Install `stack` from [haskellstack.org](https://haskellstack.org/).
+2. At the root of this repo, run `stack install`.  (Takes 10-15 minutes.)
 
 ## Using confcrypt
 - create a config
@@ -68,5 +75,7 @@
     # TIMEOUT_MS : Int
     # TIMEOUT_MS = 300
     ```
+
+    *Note* confcrypt files must end with a trailing newline.
 
     While the default config created via `confcrypt new ...` places the schema on line `n` and parameters on `n+1`, there's no required ordering for the file. In fact, you can choose to entirely omit the schema and only store configuration paraemters in an `econf` file, but this will cause `confcrypt validate` to fail.
diff --git a/app/ConfCrypt/CLI/API.hs b/app/ConfCrypt/CLI/API.hs
--- a/app/ConfCrypt/CLI/API.hs
+++ b/app/ConfCrypt/CLI/API.hs
@@ -13,6 +13,8 @@
 import Options.Applicative (ParserInfo, Parser, progDesc, command, fullDesc, long, flag, metavar,
     help, strOption, short, info, header, footer, strArgument, hsubparser, helper, (<**>))
 import qualified Data.Text as T
+import Paths_confcrypt (version)
+import Data.Version (showVersion)
 
 data KeyAndConf = KeyAndConf {key :: ParsedKey, provider :: KeyProvider, conf :: FilePath}
     deriving (Eq, Show)
@@ -26,6 +28,7 @@
     | EC KeyAndConf EditConfCrypt
     | DC Conf DeleteConfCrypt
     | VC KeyAndConf
+    | VER T.Text
     | NC
     deriving (Eq, Show)
 
@@ -57,6 +60,8 @@
         command "new" new
         <>
         command "delete" delete
+        <>
+        command "version" vers
     )
 
 awsCmds :: ParserInfo AnyCommand
@@ -93,6 +98,10 @@
     fullDesc <>
     (header "Run using a local RSA key. This overloads the --key option to accept a file path to the public key.")
 
+
+vers :: ParserInfo AnyCommand
+vers = info (pure . VER . T.pack $ showVersion version)
+               (progDesc "The current version of confcrypt" <> fullDesc)
 
 add ::
     KeyProvider
diff --git a/app/ConfCrypt/CLI/Engine.hs b/app/ConfCrypt/CLI/Engine.hs
--- a/app/ConfCrypt/CLI/Engine.hs
+++ b/app/ConfCrypt/CLI/Engine.hs
@@ -35,6 +35,7 @@
     either (\e -> print e *> exitFailure)
            pure
            res
+run (VER version) = pure [version]
 run parsedArguments = do
     let filePath = confFilePath parsedArguments
     lines <- T.readFile filePath
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -12,3 +12,6 @@
     parsedArguments <- execParser cliParser
     results <- run parsedArguments
     traverse_ (putStrLn . unpack) results
+    -- The ^ `putStrLn` call is important to preserve the trailing newline. Consider
+    -- moving this into the library to make the code read more clearly.
+    -- There's no reason that `writeFullContentsToBuffer` can't tag each line with a trailing newline
diff --git a/confcrypt.cabal b/confcrypt.cabal
--- a/confcrypt.cabal
+++ b/confcrypt.cabal
@@ -1,11 +1,13 @@
--- This file has been generated from package.yaml by hpack version 0.28.2.
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.1.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 78b2963f823c24783db5be9543ec39e215f9a07298d3987ac7dc95bf5249ca0b
+-- hash: ba0d2b5403892dd88223717284c0e5acb3e489ed1e7071a545b4f24587ad8c89
 
 name:           confcrypt
-version:        0.2.0.0
+version:        0.2.3.0
 description:    Please see the README on GitHub at <https://github.com/CollegeVine/confcrypt#readme>
 homepage:       https://github.com/collegevine/confcrypt#readme
 bug-reports:    https://github.com/collegevine/confcrypt/issues
@@ -15,10 +17,9 @@
 license:        MIT
 license-file:   LICENSE
 build-type:     Simple
-cabal-version:  >= 1.10
 extra-source-files:
-    ChangeLog.md
     README.md
+    ChangeLog.md
 
 source-repository head
   type: git
@@ -38,6 +39,7 @@
   hs-source-dirs:
       src
   default-extensions: MultiParamTypeClasses OverloadedStrings FlexibleContexts FlexibleInstances NamedFieldPuns TupleSections DeriveGeneric DeriveAnyClass FunctionalDependencies TypeApplications UndecidableInstances GADTs ConstraintKinds
+  ghc-options: -optP-Wno-nonportable-include-path
   build-depends:
       amazonka >=1.6 && <1.7
     , amazonka-kms >=1.6 && <1.7
@@ -64,10 +66,11 @@
   other-modules:
       ConfCrypt.CLI.API
       ConfCrypt.CLI.Engine
+      Paths_confcrypt
   hs-source-dirs:
       app
   default-extensions: MultiParamTypeClasses OverloadedStrings FlexibleContexts FlexibleInstances NamedFieldPuns TupleSections ExistentialQuantification TypeApplications UndecidableInstances BangPatterns ViewPatterns GADTs
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  ghc-options: -optP-Wno-nonportable-include-path -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       amazonka >=1.6 && <1.7
     , amazonka-kms >=1.6 && <1.7
@@ -94,12 +97,12 @@
   type: exitcode-stdio-1.0
   main-is: Tests.hs
   other-modules:
-      ConfCrypt.Parser.Tests, ConfCrypt.Commands.Tests, ConfCrypt.Encryption.Tests, ConfCrypt.CLI.API.Tests, ConfCrypt.Common
+      ConfCrypt.Parser.Tests, ConfCrypt.Commands.Tests, ConfCrypt.Encryption.Tests, ConfCrypt.CLI.API.Tests, ConfCrypt.Common, ConfCrypt.CLI.API
   hs-source-dirs:
       test
       app
   default-extensions: MultiParamTypeClasses OverloadedStrings FlexibleContexts FlexibleInstances NamedFieldPuns TupleSections
-  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  ghc-options: -optP-Wno-nonportable-include-path -threaded -rtsopts -with-rtsopts=-N
   build-depends:
       HUnit
     , QuickCheck
diff --git a/src/ConfCrypt/Commands.hs b/src/ConfCrypt/Commands.hs
--- a/src/ConfCrypt/Commands.hs
+++ b/src/ConfCrypt/Commands.hs
@@ -46,8 +46,6 @@
 -- | All confcrypt commands can be generalized into an 'evaluate' call. In reality, instances likely
 -- need to provide some environment, although that's not required as everything could be contained
 -- as record fields of the command argument itself.
---
--- In reality the return type of 'evalutate' is 'Text', this needs to be cleaned up in the upcoming version.
 class Monad m => Command a m where
     evaluate :: a -> m [T.Text]
 
diff --git a/src/ConfCrypt/Parser.hs b/src/ConfCrypt/Parser.hs
--- a/src/ConfCrypt/Parser.hs
+++ b/src/ConfCrypt/Parser.hs
@@ -5,7 +5,7 @@
 import ConfCrypt.Types
 
 import Control.Applicative ((<|>))
-import Control.Applicative.Combinators (manyTill, many, some)
+import Control.Applicative.Combinators (manyTill, many, some, sepEndBy)
 import Data.Maybe (listToMaybe)
 import Text.Megaparsec (Parsec, parse, getSourcePos, SourcePos(..), unPos, (<?>), try, failure, oneOf, anySingle)
 import Text.Megaparsec.Char (char, space, eol, string', digitChar, alphaNumChar,
@@ -47,9 +47,9 @@
 
 confCryptParser :: Parser [(ConfCryptElement, LineNumber)]
 confCryptParser =
-    many lineParser
+    sepEndBy lineParser (many eol)
     where
-        lineParser = try parseComment <|> try parseSchema <|> try parseParameter
+        lineParser = try parseComment <|> try parseSchema <|> parseParameter
 
 parseComment :: Parser (ConfCryptElement, LineNumber)
 parseComment = do
@@ -58,7 +58,6 @@
     _ <- char '#'
     _ <- char ' '
     line <- T.pack <$> manyTill anySingle eol
-    _ <- many eol
     pure (CommentLine line, lineNum)
 
 parseSchema :: Parser (ConfCryptElement, LineNumber)
@@ -67,7 +66,6 @@
     _ <- char ':'
     _ <- char ' '
     tpe <- parseType
-    _ <- many eol
     pure (SchemaLine Schema {sName= name, sType= tpe}, lineNum)
 
 parseType :: Parser SchemaType
@@ -89,7 +87,6 @@
     _ <- char '='
     _ <- char ' '
     value <- validValue
-    _ <- many eol
     pure (ParameterLine ParamLine {pName= name, pValue = value}, lineNum)
 
 beginsWithName :: Parser (LineNumber, T.Text)
diff --git a/test/ConfCrypt/CLI/API/Tests.hs b/test/ConfCrypt/CLI/API/Tests.hs
--- a/test/ConfCrypt/CLI/API/Tests.hs
+++ b/test/ConfCrypt/CLI/API/Tests.hs
@@ -290,3 +290,4 @@
             Failure _ -> assertFailure "Should have parsed an VC"
             CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
     ]
+
diff --git a/test/ConfCrypt/Commands/Tests.hs b/test/ConfCrypt/Commands/Tests.hs
--- a/test/ConfCrypt/Commands/Tests.hs
+++ b/test/ConfCrypt/Commands/Tests.hs
@@ -64,8 +64,8 @@
 bufferWriteProperties  = testGroup "Buffer write" [
     testProperty "parse (writerBuffer xs) == xs" $ \(ValidCCF ccf)-> let
         fc = fileContents ccf
-        output = (<> "\n") . T.intercalate "\n" $ join (writeFullContentsToBuffer True fc)
-        parseRes = parseConfCrypt "" output
+        output = T.intercalate "\n" $ join (writeFullContentsToBuffer True fc)
+        parseRes = parseConfCrypt "" (if T.null output then output else output <> "\n")
         in either (const False)
                   (\ccf' -> fileContents ccf' == fc)
                   parseRes
diff --git a/test/ConfCrypt/Parser/Tests.hs b/test/ConfCrypt/Parser/Tests.hs
--- a/test/ConfCrypt/Parser/Tests.hs
+++ b/test/ConfCrypt/Parser/Tests.hs
@@ -26,7 +26,9 @@
 explicitFiles = testGroup "specific test files" [
     testCase "default config" $ do
         let res = parseConfCrypt "default config" defaultConf
-        isRight res @=? True
+        either  ( assertFailure . show )
+                ( const $ assertBool "" True)
+                res
 
    ,testCase "empty config" $ do
         let res = parseConfCrypt "empty conf" ""
@@ -38,7 +40,9 @@
 
     ,testCase "multiple line breaks" $ do
         let res = parseConfCrypt "line breaks" multipleLineBreaks
-        isRight res @=? True
+        either  ( assertFailure . show )
+                ( const $ assertBool "" True)
+                res
 
    ,testCase "requires a trailing newline" $ do
         let Right res = parseConfCrypt "No newline" "# {"
@@ -70,6 +74,12 @@
                         (pure . head . parameters)
                         res
         paramValue param @=? "foobar"
+    ,testCase "Invalid files fail completely, no results are returned" $ do
+        let res = parseConfCrypt "invalid" invalidFile
+        parseResult <- either (const $ pure True)
+                              (const $ assertFailure "Expected a parser failure")
+                              res
+        parseResult @=? True
     ]
 
 
@@ -91,7 +101,7 @@
     \ USE_SSL : Boolean\n\
     \ USE_SSL = True\n\
     \ TIMEOUT_MS : Int\n\
-    \ TIMEOUT_MS = 300"
+    \ TIMEOUT_MS = 300\n"
 
 allComments :: T.Text
 allComments = "# Configuration parameters may be either a String, Int, or Boolean\n\
@@ -110,7 +120,12 @@
     \ USE_SSL : Boolean\n\n\n\n\
     \ USE_SSL = True\n\n\
     \ TIMEOUT_MS : Int\n\
-    \ TIMEOUT_MS = 300"
+    \ TIMEOUT_MS = 300\n"
+
+invalidFile :: T.Text
+invalidFile = "TIMEOUT_MS : Int\n\
+    \ TIMEOUT_MS = 300\n\
+    \ not a legitiamte file\n"
 
 wrappedParameter:: T.Text
 wrappedParameter = "Test : String\n\
