diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
 # Changelog
 All notable changes to this project will be documented in this file.
 
+## 0.1.3.0 (released 2020-03-23)
+- [#24] Added _Init_ command that automatically creates initial _Headroom_ configuration and set of templates.
+- bump _LTS Haskell_ to `15.5`
+
 ## 0.1.2.0 (released 2020-03-06)
 - FIXED: missing `test-data/` folder in dist tarball prevents tests execution
 - bump _LTS Haskell_ to `15.2`
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -8,28 +8,34 @@
     - [3.1. From Source Code](#31-from-source-code)
         - [3.1.1. Using Cabal](#311-using-cabal)
         - [3.1.2. Using Stack](#312-using-stack)
-- [4. Case Example](#4-case-example)
-    - [4.1. Adding License Header Templates](#41-adding-license-header-templates)
-    - [4.2. Adding Headroom Configuration](#42-adding-headroom-configuration)
-    - [4.3. Running Headroom](#43-running-headroom)
-- [5. Command Line Interface Overview](#5-command-line-interface-overview)
-    - [5.1. Run Command](#51-run-command)
-    - [5.2. Generator Command](#52-generator-command)
-        - [5.2.1. Supported License Types](#521-supported-license-types)
-        - [5.2.2. Supported File Types](#522-supported-file-types)
-
+- [4. Basic Overview](#4-basic-overview)
+    - [4.1. Main Concepts](#41-main-concepts)
+    - [4.2. License Header Detection](#42-license-header-detection)
+- [5. Quick Start Guide](#5-quick-start-guide)
+    - [5.1. Initializing Headroom](#51-initializing-headroom)
+        - [5.1.1. Automatic Initialization](#511-automatic-initialization)
+        - [5.1.2. Manual Initialization](#512-manual-initialization)
+    - [5.2. YAML Configuration File](#52-yaml-configuration-file)
+    - [5.3. Running Headroom](#53-running-headroom)
+- [6. Command Line Interface Overview](#6-command-line-interface-overview)
+    - [6.1. Init Command](#61-init-command)
+    - [6.2. Run Command](#62-run-command)
+    - [6.3. Generator Command](#63-generator-command)
+        - [6.3.1. Supported License Types](#631-supported-license-types)
+        - [6.3.2. Supported File Types](#632-supported-file-types)
 
 ## 1. Main Features
 - __License Header Management__ - allows to add, replace or drop license headers in source code files.
 - __License Header Autodetection__ - you can even replace or drop license headers that weren't generated by Headroom, as they are automatically detected from source code files, not from template files.
 - __Template Generator__ - generates license header templates for most popular _open source_ licenses. You can use these as-is, customize them or ignore them and use your custom templates.
+- __Automatic Initialization__ - using the _Init_ command, _Headroom_ can detect what source code files you have in your project and generate initial configuration file and appropriate template skeletons.
 
 ## 2. Planned Features
-- [[#24]][i24] __Init Command__ - automates initial Headroom setup for your project (generates config files, detects source code file types and generates license template stubs for them)
+- [[#25]][i25] __Content-aware Templates__ - license header templates will be able to extract some template variables from source code file for which the template is rendered
 - __Binary Distribution__ - pre-built binaries will be generated for each release for major OS platforms
 
 ## 3. Installation
-> Binary distribution, pre-built packages and installation from Stackage will be available soon.
+> Binary distribution will be available soon, there are also plans to add _Headroom_ to popular package managers.
 
 ### 3.1. From Source Code
 Headroom is written in [Haskell][web:haskell], so you can install it from source code either using [Cabal][web:cabal] or [Stack][web:stack].
@@ -41,13 +47,22 @@
 
 #### 3.1.2. Using Stack
 1. install [Stack][web:stack] for your platform
-1. clone this repository
-1. run `stack install` inside the `headroom/` directory
+1. run `stack install headroom`
 1. add `$HOME/.local/bin` to your `$PATH`
 
-## 4. Case Example
-Let's demonstrate how to use Headroom in real world example: imagine you have small source code repository with following structure and you'd like to setup Headroom for it:
+## 4. Basic Overview
 
+### 4.1. Main Concepts
+1. __Template files__ - Template files contains templates in [Mustache][web:mustache] format, used for rendering license headers. During rendering phase, all variables are replaced by values loaded from _YAML_ configuration file. Each supported source code file has unique template, because license headers may differ for individual programming languages (i.e. different syntax for comments, etc.).
+1. __Source Code Files__ - Headroom automatically discovers individual source code files in configured location and can manage (add/remove/drop) license headers in supported types of source code files.
+1. __Variables__ - are defined in _YAML_ configuration file and these values are used to replace variables present in the template files.
+
+### 4.2. License Header Detection
+Unlike many other tools, Headroom can not only add, but also replace or drop existing license headers in source code files, even if there is already some header not generated by Headroom. This is done by implementing unique detection logic for each individual supported file type, but usually it's expected that the very first block comment is the license header. If you miss support for your favorite file type, please [open new issue][meta:new-issue].
+
+## 5. Quick Start Guide
+Let's demonstrate how to use Headroom in real world example: assume you have small source code repository with following structure and you'd like to setup Headroom for it:
+
 ```
 project/
   └── src/
@@ -58,21 +73,22 @@
           └── template1.html
 ```
 
-### 4.1. Adding License Header Templates
-Let's say our project is licensed under the [3-Clause BSD License][web:bsd-3] license, so we want to use appropriate license headers. Headroom already provides templates for this license which you can use without modifications, or as starting point for your customization. Now we need to generate template file for each source code file type we have. The template must be always named as `<FILE_TYPE>.mustache`, for reference see list of [supported file types](#422-supported-file-types) and [supported license types](#421-supported-license-types).
+### 5.1. Initializing Headroom
 
+#### 5.1.1. Automatic Initialization
+Easiest and fastest way how to initialize Headroom for your project is to use the _Init_ command, which generates all the boilerplate for you. The only drawback is that you can use it only in case that you use any supported open source license for which Headroom contains license header templates:
+
 ```shell
 cd project/
-mkdir templates/
-cd templates/
-
-headroom gen -l bsd3:css >./css.mustache
-headroom gen -l bsd3:html >./html.mustache
-headroom gen -l bsd3:scala >./scala.mustache
+headroom init -l bsd3 -s src/
 ```
 
-Now the project structure should be following:
+This command will automatically scan source code directories for supported file types and will generate:
+1. `.headroom.yaml` configuration file with correctly set path to template files, source codes and will contain dummy values for variables.
+1. `headroom-templates/` directory which contains template files for all known file types you use in your project and for open source license you choose.
 
+Now the project structure will be following:
+
 ```
 project/
   ├── src/
@@ -81,64 +97,56 @@
   │   │   └── Bar.scala
   │   └── html/
   │       └── template1.html
-  └── templates/
-      ├── css.mustache
-      ├── html.mustache
-      └── scala.mustache
+  ├── headroom-templates/
+  │   ├── html.mustache
+  │   └── scala.mustache
+  └── .headroom.yaml
 ```
 
-### 4.2. Adding Headroom Configuration
-Now we need to add configuration file where we specify path to source code files, template files and define values for variables in templates. The configuration file should be placed in project root directory and should be named `.headroom.yaml`, so Headroom can locate it:
+#### 5.1.2. Manual Initialization
+If you for some reason don't want to use the automatic initialization using the steps above, you can either create all the required files (_YAML_ configuration and template files) by hand, or you can use the _Generator_ command to do that in semi-automatic way:
 
-```
+```shell
 cd project/
 headroom gen -c >./.headroom.yaml
-```
 
-The project structure should now be following:
+mkdir headroom-templates/
+cd headroom-templates/
 
-```
-project/
-  ├── src/
-  │   ├── scala/
-  │   │   ├── Foo.scala
-  │   │   └── Bar.scala
-  │   └── html/
-  │       └── template1.html
-  ├── templates/
-  │   ├── css.mustache
-  │   ├── html.mustache
-  │   └── scala.mustache
-  └── .headroom.yaml
+headroom gen -l bsd3:css >./css.mustache
+headroom gen -l bsd3:html >./html.mustache
+headroom gen -l bsd3:scala >./scala.mustache
 ```
 
-Let's now edit configuration file to match our project:
+After these steps, make sure you set correctly the paths to template files and source code files in the _YAML_ configuration file.
 
-```yaml
-## This is the configuration file for Headroom.
-## See https://github.com/vaclavsvejcar/headroom for more details.
+### 5.2. YAML Configuration File
+Headroom's _YAML_ configuration file must be placed in root of your project and called `.headroom.yaml`. The configuration itself is pretty simple and consists of following keys:
 
-## Defines the behaviour how to handle license headers, possible options are:
-##   - add     = (default) adds license header to files with no existing header
-##   - drop    = drops existing license header from without replacement
-##   - replace = adds or replaces existing license header
+```yaml
 run-mode: add
-
-## Paths to source code files (either files or directories).
 source-paths:
-    - src
-
-## Paths to template files (either files or directories).
+  - .
 template-paths:
-    - templates
-
-## Variables (key-value) to replace in templates.
+  - headroom-templates
 variables:
-    author: John Smith
-    year: "2019"
+  author: John Smith
+  email: john.smith@example.com
+  project: My project
+  year: '2020'
 ```
 
-### 4.3. Running Headroom
+- `run-mode` - sets the default behaviour of _Run_ command (`headroom run`). Possible options are:
+  - `add` - Headroom will only add license headers to source code where these are missing, but won't touch files with existing headers
+  - `drop` - Headroom will drop existing license headers without any replacement
+  - `replace` - Headroom will add missing or replace existing license headers
+- `source-paths` - paths to source code files (either files or directories)
+- `template-paths` - paths to template files (either files or directories)
+- `variables` - variables (key-value) to replace in templates
+
+These settings can be then overriden by corresponding command line optionss. See more details about these settings in command line interface overview section below.
+
+### 5.3. Running Headroom
 Now we're ready to run Headroom:
 
 ```shell
@@ -148,12 +156,12 @@
 headroom run -d   # drops existing license headers from files
 ```
 
-## 5. Command Line Interface Overview
+## 6. Command Line Interface Overview
 Headroom provides various commands for different use cases. You can check commands overview by performing following command:
 
 ```
 $ headroom --help
-headroom v0.1.0.0 :: https://github.com/vaclavsvejcar/headroom
+headroom v0.1.3.0 :: https://github.com/vaclavsvejcar/headroom
 
 Usage: headroom COMMAND
   manage your source code license headers
@@ -164,9 +172,25 @@
 Available commands:
   run                      add or replace source code headers
   gen                      generate stub configuration and template files
+  init                     initialize current project for Headroom
 ```
 
-### 5.1. Run Command
+### 6.1. Init Command
+Init command is used to initialize Headroom for project by generating all the required files (_YAML_ config file and template files). You can display available options by running following command:
+
+```
+$ headroom init --help
+Usage: headroom init (-l|--license-type LICENSE_TYPE) (-s|--source-path PATH)
+  initialize current project for Headroom
+
+Available options:
+  -l,--license-type LICENSE_TYPE
+                           type of open source license
+  -s,--source-path PATH    path to source code file/directory
+  -h,--help                Show this help text
+```
+
+### 6.2. Run Command
 Run command is used to manipulate (add, replace or drop) license headers in source code files. You can display available options by running following command:
 
 ```
@@ -198,7 +222,7 @@
 | `template-paths`    | `-t`, `--template-path`   |
 
 
-### 5.2. Generator Command
+### 6.3. Generator Command
 Generator command is used to generate stubs for license header template and _YAML_ configuration file. You can display available options by running following command:
 
 ```
@@ -214,7 +238,7 @@
 
 When using the `-l,--license` option, you need to select the _license type_ and _file type_ from the list of supported ones listed bellow. For example to generate template for _Apache 2.0_ license and _Haskell_ file type, you need to use the `headroom gen -l apache2:haskell`.
 
-#### 5.2.1. Supported License Types
+#### 6.3.1. Supported License Types
 Below is the list of supported _open source_ license types. If you miss support for license you use, feel free to [open new issue][meta:new-issue].
 
 | License        | Used Name |
@@ -225,7 +249,7 @@
 | _GPLv3_        | `gpl3`    |
 | _MIT_          | `mit`     |
 
-#### 5.2.2. Supported File Types
+#### 6.3.2. Supported File Types
 Below is the list of supported source code file types. If you miss support for programming language you use, feel free to [open new issue][meta:new-issue].
 
 | Language     | Used Name | Supported Extensions |
@@ -238,7 +262,7 @@
 | _Scala_      | `scala`   | `.scala`             |
 
 
-[i24]: https://github.com/vaclavsvejcar/headroom/issues/24
+[i25]: https://github.com/vaclavsvejcar/headroom/issues/25
 [meta:new-issue]: https://github.com/vaclavsvejcar/headroom/issues/new
 [web:bsd-3]: https://opensource.org/licenses/BSD-3-Clause
 [web:cabal]: https://www.haskell.org/cabal/
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -22,9 +22,16 @@
 import           Headroom.Command.Gen.Env       ( GenMode(..)
                                                 , GenOptions(GenOptions)
                                                 )
+import           Headroom.Command.Init          ( commandInit )
+import           Headroom.Command.Init.Env      ( InitOptions(InitOptions) )
 import           Headroom.Command.Run           ( commandRun )
 import           Headroom.Command.Run.Env       ( RunOptions(RunOptions) )
-import           Headroom.Types                 ( HeadroomError(..) )
+import           Headroom.License               ( LicenseType
+                                                , parseLicenseType
+                                                )
+import           Headroom.Types                 ( HeadroomError(..)
+                                                , InitCommandError(..)
+                                                )
 import           Options.Applicative
 import           Prelude                        ( putStrLn )
 import           RIO
@@ -46,9 +53,17 @@
   c@(Gen _ _) -> do
     genMode <- parseGenMode c
     commandGen (GenOptions genMode)
+  Init licenseType sourcePaths -> do
+    licenseType' <- parseLicenseType' licenseType
+    commandInit (InitOptions sourcePaths licenseType')
 
 parseGenMode :: MonadThrow m => Command -> m GenMode
 parseGenMode = \case
   Gen True  Nothing        -> pure GenConfigFile
   Gen False (Just license) -> pure $ GenLicense license
   _                        -> throwM NoGenModeSelected
+
+parseLicenseType' :: MonadThrow m => Text -> m LicenseType
+parseLicenseType' input = case parseLicenseType input of
+  Just licenseType -> pure licenseType
+  Nothing          -> throwM $ InitCommandError $ InvalidLicenseType input
diff --git a/headroom.cabal b/headroom.cabal
--- a/headroom.cabal
+++ b/headroom.cabal
@@ -1,6 +1,6 @@
 cabal-version: 1.12
 name: headroom
-version: 0.1.2.0
+version: 0.1.3.0
 license: BSD3
 license-file: LICENSE
 copyright: Copyright (c) 2019-2020 Vaclav Svejcar
@@ -78,12 +78,15 @@
         Headroom.Command
         Headroom.Command.Gen
         Headroom.Command.Gen.Env
+        Headroom.Command.Init
+        Headroom.Command.Init.Env
         Headroom.Command.Run
         Headroom.Command.Run.Env
         Headroom.Command.Shared
         Headroom.Embedded
         Headroom.FileSystem
         Headroom.FileType
+        Headroom.Global
         Headroom.Header
         Headroom.Header.Impl
         Headroom.Header.Impl.CSS
@@ -100,6 +103,7 @@
         Headroom.Text
         Headroom.Types
         Headroom.Types.Utils
+        Headroom.UI.Progress
     hs-source-dirs: src
     other-modules:
         Paths_headroom
@@ -109,7 +113,7 @@
                  -Wpartial-fields -Wredundant-constraints
                  -Werror=incomplete-patterns
     build-depends:
-        aeson >=1.4.6.0 && <1.5,
+        aeson >=1.4.7.0 && <1.5,
         base >=4.7 && <5,
         either >=5.0.1.1 && <5.1,
         file-embed >=0.0.11.2 && <0.1,
@@ -118,12 +122,12 @@
         optparse-applicative >=0.15.1.0 && <0.16,
         pcre-heavy >=1.0.0.2 && <1.1,
         pcre-light >=0.4.1.0 && <0.5,
-        rio >=0.1.14.0 && <0.2,
+        rio >=0.1.14.1 && <0.2,
         template-haskell >=2.15.0.0 && <2.16,
         text >=1.2.4.0 && <1.3,
         time >=1.9.3 && <1.10,
         validation ==1.1.*,
-        yaml >=0.11.2.0 && <0.12
+        yaml >=0.11.3.0 && <0.12
 
 executable headroom
     main-is: Main.hs
@@ -139,7 +143,7 @@
         base >=4.7 && <5,
         headroom -any,
         optparse-applicative >=0.15.1.0 && <0.16,
-        rio >=0.1.14.0 && <0.2
+        rio >=0.1.14.1 && <0.2
 
 test-suite doctest
     type: exitcode-stdio-1.0
@@ -156,7 +160,7 @@
         base >=4.7 && <5,
         doctest >=0.16.2 && <0.17,
         optparse-applicative >=0.15.1.0 && <0.16,
-        rio >=0.1.14.0 && <0.2
+        rio >=0.1.14.1 && <0.2
 
 test-suite spec
     type: exitcode-stdio-1.0
@@ -164,6 +168,7 @@
     hs-source-dirs: test
     other-modules:
         Headroom.AppConfigSpec
+        Headroom.Command.InitSpec
         Headroom.FileSystemSpec
         Headroom.FileTypeSpec
         Headroom.Header.Impl.CSSSpec
@@ -179,6 +184,7 @@
         Headroom.TextSpec
         Headroom.Types.UtilsSpec
         Headroom.TypesSpec
+        Headroom.UI.ProgressSpec
         Test.Utils
         Paths_headroom
     default-language: Haskell2010
@@ -187,9 +193,9 @@
                  -Wpartial-fields -Wredundant-constraints
                  -Werror=incomplete-patterns
     build-depends:
-        aeson >=1.4.6.0 && <1.5,
+        aeson >=1.4.7.0 && <1.5,
         base >=4.7 && <5,
         headroom -any,
         hspec >=2.7.1 && <2.8,
         optparse-applicative >=0.15.1.0 && <0.16,
-        rio >=0.1.14.0 && <0.2
+        rio >=0.1.14.1 && <0.2
diff --git a/src/Headroom/AppConfig.hs b/src/Headroom/AppConfig.hs
--- a/src/Headroom/AppConfig.hs
+++ b/src/Headroom/AppConfig.hs
@@ -20,16 +20,23 @@
   , makePathsRelativeTo
   , parseAppConfig
   , parseVariables
+  , prettyPrintAppConfig
   , validateAppConfig
   )
 where
 
 import           Control.Lens
 import           Data.Aeson                     ( FromJSON(parseJSON)
+                                                , ToJSON(toJSON)
                                                 , genericParseJSON
+                                                , genericToJSON
                                                 )
 import           Data.Validation
 import qualified Data.Yaml                     as Y
+import           Data.Yaml.Pretty               ( defConfig
+                                                , encodePretty
+                                                , setConfCompare
+                                                )
 import           Headroom.Types                 ( AppConfigError(..)
                                                 , HeadroomError(..)
                                                 , RunMode(..)
@@ -58,6 +65,10 @@
 instance FromJSON AppConfig where
   parseJSON = genericParseJSON customOptions
 
+-- | Support for writing configuration to /YAML/.
+instance ToJSON AppConfig where
+  toJSON = genericToJSON customOptions
+
 instance Semigroup AppConfig where
   x <> y = AppConfig (acRunMode x)
                      (acSourcePaths x <> acSourcePaths y)
@@ -103,6 +114,12 @@
   parse input = case T.split (== '=') input of
     [key, value] -> pure (key, value)
     _            -> throwM $ InvalidVariable input
+
+-- | Writes the given 'AppConfig' to /YAML/ text, using the pretty printer.
+prettyPrintAppConfig :: AppConfig -- ^ application config to write to /YAML/
+                     -> Text      -- ^ pretty printed /YAML/ text
+prettyPrintAppConfig = decodeUtf8Lenient . encodePretty prettyConfig
+  where prettyConfig = setConfCompare compare defConfig
 
 -- | Validates whether given 'AppConfig' contains valid data.
 validateAppConfig :: MonadThrow m
diff --git a/src/Headroom/Command.hs b/src/Headroom/Command.hs
--- a/src/Headroom/Command.hs
+++ b/src/Headroom/Command.hs
@@ -27,6 +27,7 @@
 data Command
   = Run [FilePath] [FilePath] [Text] RunMode Bool -- ^ /Run/ command
   | Gen Bool (Maybe Text)                         -- ^ /Generator/ command
+  | Init Text [FilePath]                               -- ^ /Init/ command
     deriving (Show)
 
 -- | Parses command line arguments.
@@ -40,7 +41,7 @@
  where
   header' =
     "headroom v" <> buildVer <> " :: https://github.com/vaclavsvejcar/headroom"
-  commands   = subparser (runCommand <> genCommand)
+  commands   = subparser (runCommand <> genCommand <> initCommand)
   runCommand = command
     "run"
     (info (runOptions <**> helper)
@@ -51,7 +52,11 @@
     (info (genOptions <**> helper)
           (progDesc "generate stub configuration and template files")
     )
-
+  initCommand = command
+    "init"
+    (info (initOptions <**> helper)
+          (progDesc "initialize current project for Headroom")
+    )
 
 runOptions :: Parser Command
 runOptions =
@@ -99,5 +104,19 @@
           (strOption
             (long "license" <> short 'l' <> metavar "name:type" <> help
               "generate template for license and file type"
+            )
+          )
+
+initOptions :: Parser Command
+initOptions =
+  Init
+    <$> strOption
+          (long "license-type" <> short 'l' <> metavar "LICENSE_TYPE" <> help
+            "type of open source license"
+          )
+    <*> some
+          (strOption
+            (long "source-path" <> short 's' <> metavar "PATH" <> help
+              "path to source code file/directory"
             )
           )
diff --git a/src/Headroom/Command/Gen/Env.hs b/src/Headroom/Command/Gen/Env.hs
--- a/src/Headroom/Command/Gen/Env.hs
+++ b/src/Headroom/Command/Gen/Env.hs
@@ -11,21 +11,15 @@
 -}
 {-# LANGUAGE NoImplicitPrelude #-}
 module Headroom.Command.Gen.Env
-  ( GenMode(..)
+  ( Env(..)
+  , GenMode(..)
   , GenOptions(..)
-  , Env(..)
   )
 where
 
 import           RIO
 
 
--- | Options for the /Generator/ command.
-newtype GenOptions = GenOptions
-  { goGenMode :: GenMode -- ^ used /Generator/ command mode
-  }
-  deriving Show
-
 -- | /RIO/ Environment for the /Generator/ command.
 data Env = Env
   { envLogFunc    :: !LogFunc    -- ^ logging function
@@ -37,6 +31,12 @@
   = GenConfigFile   -- ^ generate /YAML/ config file stub
   | GenLicense Text -- ^ generate license header template
   deriving (Eq, Show)
+
+-- | Options for the /Generator/ command.
+newtype GenOptions = GenOptions
+  { goGenMode :: GenMode -- ^ used /Generator/ command mode
+  }
+  deriving Show
 
 instance HasLogFunc Env where
   logFuncL = lens envLogFunc (\x y -> x { envLogFunc = y })
diff --git a/src/Headroom/Command/Init.hs b/src/Headroom/Command/Init.hs
new file mode 100644
--- /dev/null
+++ b/src/Headroom/Command/Init.hs
@@ -0,0 +1,151 @@
+{-|
+Module      : Headroom.Command.Init
+Description : Logic for Init command
+Copyright   : (c) 2019-2020 Vaclav Svejcar
+License     : BSD-3
+Maintainer  : vaclav.svejcar@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+Logic for the @init@ command, used to generate initial configuration boilerplate
+for Headroom.
+-}
+{-# LANGUAGE LambdaCase        #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Headroom.Command.Init
+  ( commandInit
+  , doesAppConfigExist
+  , findSupportedFileTypes
+  )
+where
+
+import           Headroom.AppConfig             ( AppConfig(..)
+                                                , prettyPrintAppConfig
+                                                )
+import           Headroom.Command.Init.Env
+import           Headroom.Command.Shared        ( bootstrap )
+import           Headroom.Embedded              ( licenseTemplate )
+import           Headroom.FileSystem            ( fileExtension
+                                                , findFiles
+                                                )
+import           Headroom.FileType              ( FileType
+                                                , fileTypeByExt
+                                                )
+import           Headroom.Global                ( TemplateType )
+import           Headroom.License               ( License(..) )
+import           Headroom.Template              ( templateExtensions )
+import           Headroom.Types                 ( HeadroomError(..)
+                                                , InitCommandError(..)
+                                                )
+import           Headroom.UI.Progress           ( Progress(..)
+                                                , zipWithProgress
+                                                )
+import           RIO
+import qualified RIO.Char                      as C
+import           RIO.Directory                  ( createDirectory
+                                                , doesFileExist
+                                                , getCurrentDirectory
+                                                )
+import           RIO.FilePath                   ( (</>) )
+import qualified RIO.HashMap                   as HM
+import qualified RIO.List                      as L
+import qualified RIO.NonEmpty                  as NE
+import qualified RIO.Text                      as T
+
+
+env' :: InitOptions -> LogFunc -> IO Env
+env' opts logFunc = do
+  currentDir <- getCurrentDirectory
+  let paths = Paths { pCurrentDir   = currentDir
+                    , pConfigFile   = ".headroom.yaml"
+                    , pTemplatesDir = "headroom-templates"
+                    }
+  pure $ Env { envLogFunc = logFunc, envInitOptions = opts, envPaths = paths }
+
+-- | Handler for /Init/ command.
+commandInit :: InitOptions -- ^ /Init/ command options
+            -> IO ()       -- ^ execution result
+commandInit opts = bootstrap (env' opts) False $ doesAppConfigExist >>= \case
+  False -> do
+    fileTypes <- findSupportedFileTypes
+    makeTemplatesDir
+    createTemplates fileTypes
+    createConfigFile
+  True -> throwM $ InitCommandError AppConfigAlreadyExists
+
+-- | Recursively scans provided source paths for known file types for which
+-- templates can be generated.
+findSupportedFileTypes :: (HasInitOptions env, HasLogFunc env)
+                       => RIO env [FileType]
+findSupportedFileTypes = do
+  opts      <- view initOptionsL
+  fileTypes <- do
+    allFiles <- mapM (\path -> findFiles path (const True)) (ioSourcePaths opts)
+    let allFileTypes = fmap (fileExtension >=> fileTypeByExt) (concat allFiles)
+    pure $ L.nub . catMaybes $ allFileTypes
+  case fileTypes of
+    [] -> throwM $ InitCommandError NoSourcePaths
+    _  -> do
+      logInfo $ "Found supported file types: " <> displayShow fileTypes
+      pure fileTypes
+
+createTemplates :: (HasInitOptions env, HasLogFunc env, HasPaths env)
+                => [FileType]
+                -> RIO env ()
+createTemplates fileTypes = do
+  opts  <- view initOptionsL
+  paths <- view pathsL
+  let templatesDir = pCurrentDir paths </> pTemplatesDir paths
+  mapM_ (\(p, l) -> createTemplate templatesDir l p)
+        (zipWithProgress $ fmap (License (ioLicenseType opts)) fileTypes)
+
+createTemplate :: (HasLogFunc env)
+               => FilePath
+               -> License
+               -> Progress
+               -> RIO env ()
+createTemplate templatesDir license@(License _ fileType) progress = do
+  let extension = NE.head $ templateExtensions (Proxy :: Proxy TemplateType)
+      file = (fmap C.toLower . show $ fileType) <> "." <> T.unpack extension
+      filePath  = templatesDir </> file
+      template  = licenseTemplate license
+  logInfo $ mconcat
+    [display progress, " Creating template file in ", fromString filePath]
+  writeFileUtf8 filePath template
+
+createConfigFile :: (HasInitOptions env, HasLogFunc env, HasPaths env)
+                 => RIO env ()
+createConfigFile = do
+  opts  <- view initOptionsL
+  paths <- view pathsL
+  let filePath = pCurrentDir paths </> pConfigFile paths
+      content  = prettyPrintAppConfig $ appConfig opts paths
+  logInfo $ "Creating YAML config file in " <> fromString filePath
+  writeFileUtf8 filePath content
+ where
+  variables = HM.fromList
+    [ ("author" , "John Smith")
+    , ("email"  , "john.smith@example.com")
+    , ("project", "My project")
+    , ("year"   , "2020")
+    ]
+  appConfig opts paths = mempty { acSourcePaths   = ioSourcePaths opts
+                                , acTemplatePaths = [pTemplatesDir paths]
+                                , acVariables     = variables
+                                }
+
+-- | Checks whether application config file already exists.
+doesAppConfigExist :: (HasLogFunc env, HasPaths env) => RIO env Bool
+doesAppConfigExist = do
+  paths <- view pathsL
+  logInfo "Verifying that there's no existing Headroom configuration..."
+  doesFileExist $ pCurrentDir paths </> pConfigFile paths
+
+-- | Creates directory for template files.
+makeTemplatesDir :: (HasLogFunc env, HasPaths env) => RIO env ()
+makeTemplatesDir = do
+  paths <- view pathsL
+  let templatesDir = pCurrentDir paths </> pTemplatesDir paths
+  logInfo $ "Creating directory for templates in " <> fromString templatesDir
+  createDirectory templatesDir
diff --git a/src/Headroom/Command/Init/Env.hs b/src/Headroom/Command/Init/Env.hs
new file mode 100644
--- /dev/null
+++ b/src/Headroom/Command/Init/Env.hs
@@ -0,0 +1,55 @@
+{-|
+Module      : Headroom.Command.Init.Env
+Description : Environment for the Init command
+Copyright   : (c) 2019-2020 Vaclav Svejcar
+License     : BSD-3
+Maintainer  : vaclav.svejcar@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+Data types and instances for the /Init/ command environment.
+-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module Headroom.Command.Init.Env where
+
+import           Headroom.License               ( LicenseType )
+import           RIO
+
+
+-- | /RIO/ Environment for the /Init/ command.
+data Env = Env
+  { envLogFunc     :: !LogFunc
+  , envInitOptions :: !InitOptions
+  , envPaths       :: !Paths
+  }
+
+-- | Options for the /Init/ command.
+data InitOptions = InitOptions
+  { ioSourcePaths :: ![FilePath]
+  , ioLicenseType :: !LicenseType
+  }
+  deriving Show
+
+-- | Paths to various locations of file system.
+data Paths = Paths
+  { pCurrentDir   :: !FilePath
+  , pConfigFile   :: !FilePath
+  , pTemplatesDir :: !FilePath
+  }
+
+instance HasLogFunc Env where
+  logFuncL = lens envLogFunc (\x y -> x { envLogFunc = y })
+
+-- | Environment value with /Init/ command options.
+class HasInitOptions env where
+  initOptionsL :: Lens' env InitOptions
+
+-- | Environment value with 'Paths'.
+class HasPaths env where
+  pathsL :: Lens' env Paths
+
+instance HasInitOptions Env where
+  initOptionsL = lens envInitOptions (\x y -> x { envInitOptions = y })
+
+instance HasPaths Env where
+  pathsL = lens envPaths (\x y -> x { envPaths = y })
diff --git a/src/Headroom/Command/Run.hs b/src/Headroom/Command/Run.hs
--- a/src/Headroom/Command/Run.hs
+++ b/src/Headroom/Command/Run.hs
@@ -25,13 +25,15 @@
                                                 )
 import           Headroom.Command.Run.Env
 import           Headroom.Command.Shared        ( bootstrap )
-import           Headroom.FileSystem            ( findFilesByExts
+import           Headroom.FileSystem            ( fileExtension
+                                                , findFilesByExts
                                                 , findFilesByTypes
                                                 )
 import           Headroom.FileType              ( FileType
                                                 , fileTypeByExt
                                                 , fileTypeByName
                                                 )
+import           Headroom.Global                ( TemplateType )
 import           Headroom.Header                ( Header(..)
                                                 , addHeader
                                                 , containsHeader
@@ -41,14 +43,11 @@
 import           Headroom.Template              ( Template(..)
                                                 , loadTemplate
                                                 )
-import           Headroom.Template.Mustache     ( Mustache(..) )
-import           Headroom.Types                 ( Progress(..)
-                                                , RunMode(..)
-                                                )
+import           Headroom.Types                 ( RunMode(..) )
+import           Headroom.UI.Progress           ( Progress(..) )
 import           RIO                     hiding ( second )
 import           RIO.Directory
 import           RIO.FilePath                   ( takeBaseName
-                                                , takeExtension
                                                 , (</>)
                                                 )
 import qualified RIO.List                      as L
@@ -56,8 +55,6 @@
 import qualified RIO.Text                      as T
 
 
-type TemplateType = Mustache
-
 env' :: RunOptions -> LogFunc -> IO Env
 env' opts logFunc = do
   let startupEnv = StartupEnv { envLogFunc = logFunc, envRunOptions = opts }
@@ -71,10 +68,10 @@
   startTS <- liftIO getPOSIXTime
   logInfo "Loading source code header templates..."
   templates <- loadTemplates
-  logInfo $ "Done, found " <> displayShow (M.size templates) <> " template(s)"
+  logInfo $ "Done, found " <> display (M.size templates) <> " template(s)"
   logInfo "Searching for source code files..."
   sourceFiles <- findSourceFiles (M.keys templates)
-  let sourceFilesNum = displayShow . L.length $ sourceFiles
+  let sourceFilesNum = display . L.length $ sourceFiles
   logInfo $ mconcat
     ["Done, found ", sourceFilesNum, " sources code files(s) to process"]
   (total, skipped) <- processHeaders templates sourceFiles
@@ -82,11 +79,11 @@
   let (elapsedSeconds, _) = properFraction (endTS - startTS)
   logInfo $ mconcat
     [ "Done: modified "
-    , displayShow (total - skipped)
+    , display (total - skipped)
     , ", skipped "
-    , displayShow skipped
+    , display skipped
     , " files in "
-    , displayShow (elapsedSeconds :: Integer)
+    , display (elapsedSeconds :: Integer)
     , " second(s)."
     ]
 
@@ -132,7 +129,7 @@
     parsed
   pure $ M.fromList rendered
  where
-  extensions = templateExtensions (Proxy :: Proxy TemplateType)
+  extensions = toList $ templateExtensions (Proxy :: Proxy TemplateType)
   findPaths path = findFilesByExts path extensions
   filterTemplate (fileType, path) = (\ft -> Just (ft, path)) =<< fileType
 
@@ -163,11 +160,7 @@
  where
   withTemplate (fileType, path) =
     fmap (\t -> (Header fileType t, path)) (M.lookup fileType templates)
-  processPath path = fmap (, path) (fileTypeFor path)
-  fileTypeFor = fileTypeByExt . T.pack . fileExt
-  fileExt path = case takeExtension path of
-    '.' : xs -> xs
-    other    -> other
+  processPath path = fmap (, path) (fileExtension path >>= fileTypeByExt)
 
 processHeader :: (HasLogFunc env, HasRunOptions env)
               => Progress
@@ -184,7 +177,7 @@
   writeFileUtf8 path (action header fileContent)
   pure skipped
  where
-  log' msg = logInfo $ displayShow progress <> "  " <> msg
+  log' msg = logInfo $ display progress <> " " <> msg
   chooseAction runMode hasHeader = case runMode of
     Add     -> (hasHeader, addHeader, "Adding header to")
     Drop    -> (not hasHeader, dropHeader . hFileType, "Dropping header from")
diff --git a/src/Headroom/FileSystem.hs b/src/Headroom/FileSystem.hs
--- a/src/Headroom/FileSystem.hs
+++ b/src/Headroom/FileSystem.hs
@@ -11,7 +11,8 @@
 -}
 {-# LANGUAGE NoImplicitPrelude #-}
 module Headroom.FileSystem
-  ( findFiles
+  ( fileExtension
+  , findFiles
   , findFilesByExts
   , findFilesByTypes
   , listFiles
@@ -27,10 +28,20 @@
                                                 , getDirectoryContents
                                                 )
 import           RIO.FilePath                   ( isExtensionOf
+                                                , takeExtension
                                                 , (</>)
                                                 )
 import qualified RIO.Text                      as T
 
+
+-- | Returns file extension for given path (if file), or nothing otherwise.
+--
+-- >>> fileExtension "path/to/some/file.txt"
+-- Just "txt"
+fileExtension :: FilePath -> Maybe Text
+fileExtension path = case takeExtension path of
+  '.' : xs -> Just $ T.pack xs
+  _        -> Nothing
 
 -- | Recursively finds files on given path whose filename matches the predicate.
 findFiles :: MonadIO m
diff --git a/src/Headroom/Global.hs b/src/Headroom/Global.hs
new file mode 100644
--- /dev/null
+++ b/src/Headroom/Global.hs
@@ -0,0 +1,22 @@
+{-|
+Module      : Headroom.Global
+Description : Global data types and programmatic configuration
+Copyright   : (c) 2019-2020 Vaclav Svejcar
+License     : BSD-3
+Maintainer  : vaclav.svejcar@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+Contains data types and configuration that needs to be expressed as compile
+time code.
+-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module Headroom.Global
+  ( TemplateType
+  )
+where
+
+import           Headroom.Template.Mustache     ( Mustache )
+
+-- | Type of the template format.
+type TemplateType = Mustache
diff --git a/src/Headroom/License.hs b/src/Headroom/License.hs
--- a/src/Headroom/License.hs
+++ b/src/Headroom/License.hs
@@ -19,6 +19,7 @@
   ( License(..)
   , LicenseType(..)
   , parseLicense
+  , parseLicenseType
   )
 where
 
@@ -41,13 +42,13 @@
   | MIT     -- ^ /MIT/ license
   deriving (Bounded, Enum, Eq, Ord, Show)
 
+instance Read LicenseType where
+  readsPrec _ = readEnumCI
+
 -- | License (specified by 'LicenseType' and 'FileType')
 data License = License LicenseType FileType
   deriving (Show, Eq)
 
-instance Read LicenseType where
-  readsPrec _ = readEnumCI
-
 -- | Parses 'License' from the raw string representation, formatted as
 -- @licenseType:fileType@.
 --
@@ -61,4 +62,10 @@
     fileType    <- fileTypeByName rawFileType
     pure $ License licenseType fileType
   | otherwise = Nothing
-  where parseLicenseType = readMaybe . T.unpack
+
+-- | Parses 'LicenseType' from the raw string representation.
+--
+-- >>> parseLicenseType "bsd3"
+-- Just BSD3
+parseLicenseType :: Text -> Maybe LicenseType
+parseLicenseType = readMaybe . T.unpack
diff --git a/src/Headroom/Template.hs b/src/Headroom/Template.hs
--- a/src/Headroom/Template.hs
+++ b/src/Headroom/Template.hs
@@ -26,8 +26,8 @@
 -- | Type class representing generic license header template support.
 class Template t where
   -- | Returns list of supported file extensions for this template type.
-  templateExtensions :: proxy t -- ^ phantom parameter, not used
-                     -> [Text]  -- ^ list of supported file extensions
+  templateExtensions :: proxy t        -- ^ phantom parameter, not used
+                     -> NonEmpty Text  -- ^ list of supported file extensions
 
   -- | Parses template from given raw text.
   parseTemplate :: MonadThrow m
diff --git a/src/Headroom/Template/Mustache.hs b/src/Headroom/Template/Mustache.hs
--- a/src/Headroom/Template/Mustache.hs
+++ b/src/Headroom/Template/Mustache.hs
@@ -32,7 +32,7 @@
 
 -- | Support for /Mustache/ templates.
 instance Template Mustache where
-  templateExtensions _ = ["mustache"]
+  templateExtensions _ = "mustache" :| []
   parseTemplate  = parseTemplate'
   renderTemplate = renderTemplate'
 
diff --git a/src/Headroom/Types.hs b/src/Headroom/Types.hs
--- a/src/Headroom/Types.hs
+++ b/src/Headroom/Types.hs
@@ -16,19 +16,20 @@
   ( AppConfigError(..)
   , HeadroomError(..)
   , NewLine(..)
-  , Progress(..)
   , RunMode(..)
+  , InitCommandError(..)
   )
 where
 
 import           Data.Aeson                     ( FromJSON(parseJSON)
+                                                , ToJSON(toJSON)
                                                 , Value(String)
                                                 )
 import           RIO
 import qualified RIO.List                      as L
 import qualified RIO.Text                      as T
-import           Text.Printf                    ( printf )
 
+
 -- | Error occured during validation of application configuration.
 data AppConfigError
   = EmptySourcePaths       -- ^ no paths to source code files provided
@@ -41,11 +42,20 @@
   = InvalidAppConfig [AppConfigError] -- ^ invalid application configuration
   | InvalidLicense Text               -- ^ unknown license is selected in /Generator/
   | InvalidVariable Text              -- ^ invalid variable format (@key=value@)
-  | NoGenModeSelected                 -- ^ no mode for /Generator/ command is selected
   | MissingVariables Text [Text]      -- ^ not all variables were filled in template
+  | NoGenModeSelected                 -- ^ no mode for /Generator/ command is selected
   | ParseError Text                   -- ^ error parsing template file
+  | InitCommandError InitCommandError -- ^ error during execution of /Init/ command
   deriving (Show, Typeable)
 
+-- | Errors specific for the /Init/ command.
+data InitCommandError
+  = AppConfigAlreadyExists  -- ^ application configuration file already exists
+  | InvalidLicenseType Text -- ^ invalid license type specified
+  | NoSourcePaths           -- ^ no paths to source code files provided
+  | NoSupportedFileType     -- ^ no supported file types found on source paths
+  deriving (Show)
+
 -- | Represents newline separator.
 data NewLine
   = CR   -- ^ line ends with @\r@
@@ -53,11 +63,6 @@
   | LF   -- ^ line ends with @\n@
   deriving (Eq, Show)
 
--- | Progress indication. First argument is current progress, second the maximum
--- value.
-data Progress = Progress Int Int
-  deriving Eq
-
 -- | Mode of the /Run/ command, states how to license headers in source code
 -- files.
 data RunMode
@@ -75,27 +80,25 @@
 
 instance Exception HeadroomError where
   displayException = \case
-    (InvalidAppConfig errors) -> mconcat
+    InvalidAppConfig errors -> mconcat
       [ "Invalid configuration, following problems found:\n"
       , L.intercalate
         "\n"
         (fmap (\e -> "\t- " <> (T.unpack . displayAppConfigError $ e)) errors)
       ]
-    (InvalidLicense raw) -> "Cannot parse license type from: " <> T.unpack raw
-    (InvalidVariable raw) ->
+    InvalidLicense raw -> "Cannot parse license type from: " <> T.unpack raw
+    InvalidVariable raw ->
       "Cannot parse variable key=value from: " <> T.unpack raw
     NoGenModeSelected
       -> "Please select at least one option what to generate (see --help for details)"
-    (MissingVariables name variables) -> mconcat
+    MissingVariables name variables -> mconcat
       ["Missing variables for template '", T.unpack name, "': ", show variables]
-    (ParseError msg) -> "Error parsing template: " <> T.unpack msg
-
-instance Show Progress where
-  show (Progress current total) = mconcat ["[", currentS, " of ", totalS, "]"]
-   where
-    format   = "%" <> (show . L.length $ totalS) <> "d"
-    currentS = printf format current
-    totalS   = show total
+    ParseError       msg     -> "Error parsing template: " <> T.unpack msg
+    InitCommandError icError -> case icError of
+      AppConfigAlreadyExists -> "Config file '.headroom.yaml' already exists"
+      InvalidLicenseType raw -> "Invalid license type: " <> T.unpack raw
+      NoSourcePaths          -> "No path to source code files defined"
+      NoSupportedFileType    -> "No supported file type found"
 
 instance FromJSON RunMode where
   parseJSON (String s) = case T.toLower s of
@@ -104,3 +107,9 @@
     "replace" -> pure Replace
     _         -> error $ "Unknown run mode: " <> T.unpack s
   parseJSON other = error $ "Invalid value for run mode: " <> show other
+
+instance ToJSON RunMode where
+  toJSON = \case
+    Add     -> "add"
+    Drop    -> "drop"
+    Replace -> "replace"
diff --git a/src/Headroom/UI/Progress.hs b/src/Headroom/UI/Progress.hs
new file mode 100644
--- /dev/null
+++ b/src/Headroom/UI/Progress.hs
@@ -0,0 +1,46 @@
+{-|
+Module      : Headroom.UI.Progress
+Description : Console UI component for progress indication
+Copyright   : (c) 2019-2020 Vaclav Svejcar
+License     : BSD-3
+Maintainer  : vaclav.svejcar@gmail.com
+Stability   : experimental
+Portability : POSIX
+
+UI component that allows to display progress in console line interface.
+-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module Headroom.UI.Progress
+  ( Progress(..)
+  , zipWithProgress
+  )
+where
+
+import           RIO
+import qualified RIO.List                      as L
+import qualified RIO.Text                      as T
+import           Text.Printf                    ( printf )
+
+
+-- | Progress indication. First argument is current progress, second the maximum
+-- value.
+data Progress = Progress Int Int
+  deriving (Eq, Show)
+
+instance Display Progress where
+  textDisplay (Progress current total) = T.pack
+    $ mconcat ["[", currentS, " of ", totalS, "]"]
+   where
+    format   = "%" <> (show . L.length $ totalS) <> "d"
+    currentS = printf format current
+    totalS   = show total
+
+-- | Zips given list with the progress info.
+--
+-- >>> zipWithProgress ["a", "b"]
+-- [(Progress 1 2,"a"),(Progress 2 2,"b")]
+zipWithProgress :: [a] -> [(Progress, a)]
+zipWithProgress list = zip progresses list
+ where
+  listLength = L.length list
+  progresses = fmap (`Progress` listLength) [1 .. listLength]
diff --git a/test/Headroom/AppConfigSpec.hs b/test/Headroom/AppConfigSpec.hs
--- a/test/Headroom/AppConfigSpec.hs
+++ b/test/Headroom/AppConfigSpec.hs
@@ -49,6 +49,15 @@
         ["template1"]
         (HM.fromList [("key1", "value1"), ("key2", "value2")])
 
+  describe "prettyPrintAppConfig" $ do
+    it "can pretty print AppConfig" $ do
+      let result   = prettyPrintAppConfig appConfig2
+          expected = mconcat
+            [ "run-mode: add\nsource-paths:\n- source2\n"
+            , "template-paths:\n- template1\nvariables:\n  key2: value2\n"
+            ]
+      result `shouldBe` expected
+
   describe "validateAppConfig" $ do
     it "validates configuration version" $ do
       let check (Just (InvalidAppConfig [EmptySourcePaths])) = True
@@ -62,4 +71,3 @@
   describe "mconcat" $ do
     it "folds a list of AppConfig records" $ do
       mconcat [appConfig1, appConfig2] `shouldBe` expected'
-
diff --git a/test/Headroom/Command/InitSpec.hs b/test/Headroom/Command/InitSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Headroom/Command/InitSpec.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+module Headroom.Command.InitSpec
+  ( spec
+  )
+where
+
+import           Headroom.Command.Init
+import           Headroom.Command.Init.Env
+import           Headroom.FileType              ( FileType(HTML) )
+import           Headroom.License               ( LicenseType(..) )
+import           RIO
+import           RIO.FilePath                   ( (</>) )
+import qualified RIO.List                      as L
+import           Test.Hspec
+
+
+spec :: Spec
+spec = do
+  describe "doesAppConfigExist" $ do
+    it "checks that '.headroom.yaml' exists in selected directory" $ do
+      result <- runRIO env doesAppConfigExist
+      result `shouldBe` True
+
+  describe "findSupportedFileTypes" $ do
+    it "recursively finds all known file types present in given path" $ do
+      result <- runRIO env findSupportedFileTypes
+      let expected = [HTML]
+      L.sort result `shouldBe` L.sort expected
+
+env :: Env
+env = Env { envLogFunc = logFunc, envInitOptions = opts, envPaths = paths }
+ where
+  logFunc = mkLogFunc (\_ _ _ _ -> pure ())
+  opts    = InitOptions { ioSourcePaths = ["test-data" </> "test-traverse"]
+                        , ioLicenseType = BSD3
+                        }
+  paths = Paths { pCurrentDir   = "."
+                , pConfigFile   = "test-data" </> "configs" </> "full.yaml"
+                , pTemplatesDir = "headroom-templates"
+                }
diff --git a/test/Headroom/FileSystemSpec.hs b/test/Headroom/FileSystemSpec.hs
--- a/test/Headroom/FileSystemSpec.hs
+++ b/test/Headroom/FileSystemSpec.hs
@@ -14,6 +14,12 @@
 
 spec :: Spec
 spec = do
+  describe "fileExtension" $ do
+    it "returns file extension for valid file path" $ do
+      fileExtension "/some/path/to/file.txt" `shouldBe` Just "txt"
+    it "returns nothing for invalid file path" $ do
+      fileExtension "/some/nonsense/path" `shouldBe` Nothing
+
   describe "findFiles" $ do
     it "recursively finds files filtered by given predicate" $ do
       files <- findFiles "test-data/test-traverse/" ("b.html" `L.isSuffixOf`)
diff --git a/test/Headroom/LicenseSpec.hs b/test/Headroom/LicenseSpec.hs
--- a/test/Headroom/LicenseSpec.hs
+++ b/test/Headroom/LicenseSpec.hs
@@ -16,3 +16,8 @@
     it "parses license from raw input" $ do
       parseLicense "bsd3:haskell" `shouldBe` Just (License BSD3 Haskell)
       parseLicense "foo" `shouldBe` Nothing
+
+  describe "parseLicenseType" $ do
+    it "parses license type from raw input" $ do
+      parseLicenseType "bsd3" `shouldBe` Just BSD3
+      parseLicenseType "foo" `shouldBe` Nothing
diff --git a/test/Headroom/TypesSpec.hs b/test/Headroom/TypesSpec.hs
--- a/test/Headroom/TypesSpec.hs
+++ b/test/Headroom/TypesSpec.hs
@@ -13,11 +13,6 @@
 
 spec :: Spec
 spec = do
-  describe "show" $ do
-    it "shows correct output for Progress data type" $ do
-      show (Progress 1 1) `shouldBe` "[1 of 1]"
-      show (Progress 10 250) `shouldBe` "[ 10 of 250]"
-
   describe "parseJSON" $ do
     it "should parse RunMode value" $ do
       eitherDecode "\"add\"" `shouldBe` Right Add
diff --git a/test/Headroom/UI/ProgressSpec.hs b/test/Headroom/UI/ProgressSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Headroom/UI/ProgressSpec.hs
@@ -0,0 +1,24 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Headroom.UI.ProgressSpec
+  ( spec
+  )
+where
+
+import           Headroom.UI.Progress
+import           RIO
+import           Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "zipWithProgress" $ do
+    it "zips progress for given collection" $ do
+      let col      = ["a", "b"] :: [Text]
+          actual   = zipWithProgress col
+          expected = [(Progress 1 2, "a"), (Progress 2 2, "b")]
+      actual `shouldBe` expected
+
+  describe "show" $ do
+    it "displays correct output for Progress data type" $ do
+      textDisplay (Progress 1 1) `shouldBe` "[1 of 1]"
+      textDisplay (Progress 10 250) `shouldBe` "[ 10 of 250]"
