diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Sendy Halim <sendyhalim93@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# Bunz
+JSON beautifier cli tool written in Haskell
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/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,32 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+module Main where
+
+import qualified Beautifier             as B
+import           System.Console.CmdArgs ((&=))
+import qualified System.Console.CmdArgs as CA
+import qualified System.Posix.IO        as IO
+import qualified System.Posix.Terminal  as T
+
+data Args = Args { jsonString :: String }
+  deriving (Show, CA.Data, CA.Typeable)
+
+
+args = Args { jsonString = CA.def &= CA.typ "JSON String" &= CA.argPos 0 }
+    &= CA.summary "JSON beautifier tool version 0.0.1"
+
+main :: IO ()
+main = T.queryTerminal IO.stdInput >>= run
+
+printBeautified :: String -> IO ()
+printBeautified = putStrLn . B.beautify
+
+runFromStdInput :: IO ()
+runFromStdInput = getContents >>= printBeautified
+
+runFromArgs :: Args -> IO ()
+runFromArgs (Args { jsonString = str }) = printBeautified str
+
+run :: Bool -> IO ()
+run False = runFromStdInput
+run True = CA.cmdArgs args >>= runFromArgs
diff --git a/bunz.cabal b/bunz.cabal
new file mode 100644
--- /dev/null
+++ b/bunz.cabal
@@ -0,0 +1,49 @@
+name:                bunz
+version:             0.0.1
+synopsis:            CLI tool to beautify JSON string.
+description:         CLI tool to beautify JSON string.
+homepage:            https://github.com/sendyhalim/bunz
+license:             MIT
+license-file:        LICENSE
+author:              Sendy Halim <sendyhalim93@gmail.com>
+maintainer:          Sendy Halim <sendyhalim93@gmail.com>
+copyright:           (c) 2017 Sendy Halim
+category:            CLI, JSON
+build-type:          Simple
+extra-source-files:  README.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Character
+                     , Beautifier
+  build-depends:       base >= 4.7 && < 5
+                     , text == 1.2.2.2
+  default-language:    Haskell2010
+
+executable bunz-exe
+  hs-source-dirs:      app
+  main-is:             Main.hs
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  build-depends:       base
+                     , bunz
+                     , unix == 2.7.2.1
+                     , cmdargs >= 0.10.18
+  default-language:    Haskell2010
+
+test-suite bunz-test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             MainSpec.hs
+  other-modules:       CharacterSpec
+                     , BeautifierSpec
+  build-depends:       base
+                     , bunz
+                     , doctest >= 0.11
+                     , hspec == 2.4.4
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/githubuser/bunz
diff --git a/src/Beautifier.hs b/src/Beautifier.hs
new file mode 100644
--- /dev/null
+++ b/src/Beautifier.hs
@@ -0,0 +1,121 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ViewPatterns      #-}
+
+module Beautifier
+  ( beautify
+  , indent
+  , firstString
+  , splitAtHead
+  ) where
+
+import           Data.Monoid ((<>))
+import qualified Data.String as S
+import qualified Data.Text   as T
+
+-- Doctest setup
+-- $setup
+-- >>> :set -XOverloadedStrings
+
+type IndentationLevel = Int
+
+indentation :: T.Text
+indentation = "  "
+
+newline :: T.Text
+newline = "\n"
+
+indent :: IndentationLevel -> T.Text -> T.Text
+indent level str = T.replicate level indentation <> str
+
+trimmedTail :: T.Text -> T.Text
+trimmedTail = T.stripStart . T.tail
+
+-- | Split the given string at its head while honouring escaped string.
+-- Examples:
+--
+-- >>> splitAtHead "whatsup"
+-- ("w","hatsup")
+--
+-- >>> splitAtHead "\nyeay"
+-- ("\n","yeay")
+splitAtHead :: T.Text -> (T.Text, T.Text)
+splitAtHead "" = ("", "")
+splitAtHead str@(T.head -> '\\') = T.splitAt 2 str
+splitAtHead str = T.splitAt 1 str
+
+-- | Extract string within double quotes `"` including the double quotes ignoring
+-- the suffix after closing quote `"`
+--
+-- Examples:
+--
+-- >>> string False "\"test\"whatuppp"
+-- "\"test\""
+string :: Bool -> T.Text -> T.Text
+string False str@(T.head -> '"') = "\"" <> string True (T.tail str)
+string True str@(T.head -> '"') = "\""
+string True str = let (head, tail) = splitAtHead str in head <> string True tail
+
+-- | Extract the first string value starting from the front.
+firstString :: T.Text -> T.Text
+firstString str = string False str
+
+
+-- | Check if the given character is a whitespace
+isWhitespace x
+  | x == ' ' = True
+  | x == '\r' = True
+  | x == '\n' = True
+  | x == '\t' = True
+  | otherwise = False
+
+-- | Check if the given character is end of json value
+isEndOfValue x
+ | isWhitespace x = True
+ | x == ',' = True
+ | x == '}' = True
+ | x == ']' = True
+ | otherwise = False
+
+-- | Extract json value from the given text
+--
+-- Examples
+--
+-- >>> extractJsonValue "123131}"
+-- "123131"
+--
+-- >>> extractJsonValue "false,true,false]"
+-- "false"
+extractJsonValue :: T.Text -> T.Text
+extractJsonValue = T.takeWhile (not . isEndOfValue)
+
+-- |Beautify the given JSON text based on the current indentation level.
+beautifyText :: IndentationLevel -> T.Text -> T.Text
+beautifyText i str
+  | str == "" = ""
+  | head == ' ' = stripStartThenParse str
+  | head == '\n' = stripStartThenParse str
+  | head == '\t' = stripStartThenParse str
+  | head == '{' = nextLineAfterOpening "{"
+  | head == '[' = nextLineAfterOpening "["
+  | head == '}' = nextLineAfterClosing "}"
+  | head == ']' = nextLineAfterClosing "]"
+  | head == ',' = "," <> newline <> indent i (beautifyText i (trimmedTail str))
+  | head == '"' = let groupedString = firstString str
+                      restOfTheString = T.drop (T.length groupedString) str
+                  in groupedString <> beautifyText i restOfTheString
+  | head == ':' = ": " <> beautifyText i (trimmedTail str)
+  | otherwise = let value = extractJsonValue str
+                in value <> beautifyText i (T.drop (T.length value) str)
+  where
+    head = T.head str
+    stripStartThenParse = beautifyText i . T.stripStart
+    nextLineAfterOpening token = token
+      <> newline
+      <> indent (i + 1) (beautifyText (i + 1) (trimmedTail str))
+    nextLineAfterClosing token = newline
+      <> indent (i - 1) token
+      <> beautifyText (i - 1) (trimmedTail str)
+
+
+beautify :: String -> String
+beautify = T.unpack . beautifyText 0 . T.pack
diff --git a/src/Character.hs b/src/Character.hs
new file mode 100644
--- /dev/null
+++ b/src/Character.hs
@@ -0,0 +1,9 @@
+module Character
+    ( isEscaped
+    ) where
+
+import           Control.Monad
+
+isEscaped :: String -> Bool
+isEscaped ('\\':x) = True
+isEscaped _ = False
diff --git a/test/BeautifierSpec.hs b/test/BeautifierSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/BeautifierSpec.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module BeautifierSpec (main, spec) where
+
+import qualified Beautifier as B
+import           Test.Hspec
+
+main :: IO()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "Beautifier.indent" $ do
+    context "When given negative indent level" $ do
+      it "should not indent" $ do
+        B.indent (-1) "{" `shouldBe` "{"
+
+    context "When given 0 indent level" $ do
+      it "should not indent" $ do
+        B.indent 0 "{" `shouldBe` "{"
+
+    context "When given 2 indent level" $ do
+      it "should indent by 2 levels" $ do
+        B.indent 2 "{" `shouldBe` "    {"
+
+  describe "Beautifier.splitAtHead" $ do
+    context "when text first character is escaped" $ do
+      it "should split escaped character as the head" $ do
+        B.splitAtHead "\\\"hithere" `shouldBe` ("\\\"", "hithere")
+
+    context "when the first character is not escaped" $ do
+      it "should split correctly" $ do
+        B.splitAtHead "testtest" `shouldBe` ("t", "esttest")
+
+    context "when given empty text" $ do
+      it "should split into empty strings" $ do
+        B.splitAtHead "" `shouldBe` ("", "")
diff --git a/test/CharacterSpec.hs b/test/CharacterSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/CharacterSpec.hs
@@ -0,0 +1,18 @@
+module CharacterSpec (main, spec) where
+
+import qualified Character  as C
+import           Test.Hspec
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "Character.isEscaped" $ do
+    context "Given character \\t" $ do
+      it "should return True" $ do
+        C.isEscaped "\\t" `shouldBe` True
+
+    context "Given character \t" $ do
+      it "should return False" $ do
+        C.isEscaped "\t" `shouldBe` False
diff --git a/test/MainSpec.hs b/test/MainSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/MainSpec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
