diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,42 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [PVP Versioning][1]. The changelog is also
+available [on GitHub][2].
+
+
+## [Unreleased]
+
+## [0.0.0.0] — 2022-08-09 🌇
+
+Initial release prepared by [@chshersh](https://github.com/chshersh).
+
+### Added
+
+- [#34](https://github.com/chshersh/iris/issues/34):
+  Add the `--no-input` CLI option for disabling interactivity
+  (by [@charrsky](https://github.com/charrsky))
+- [#36](https://github.com/chshersh/iris/issues/36):
+  Support Windows and macOS
+  (by [@charrsky](https://github.com/charrsky))
+- [#37](https://github.com/chshersh/iris/issues/37):
+  Support GHC 9.0.2
+  (by [@charrsky](https://github.com/charrsky))
+- [#38](https://github.com/chshersh/iris/issues/38):
+  Support GHC 9.2.3 and GHC 9.2.4
+  (by [@charrsky](https://github.com/charrsky), [@chshersh](https://github.com/chshersh))
+- [#42](https://github.com/chshersh/iris/issues/42),
+  [#52](https://github.com/chshersh/iris/issues/52):
+  Add `stack` support and instructions to build with `stack`
+  (by [@charrsky](https://github.com/charrsky), [@chshersh](https://github.com/chshersh))
+- [#43](https://github.com/chshersh/iris/issues/43):
+  Add `MonadUnliftIO` instance for the `CliApp` monad
+  (by [@charrsky](https://github.com/charrsky))
+
+[1]: https://pvp.haskell.org
+[2]: https://github.com/chshersh/iris/releases
+
+[Unreleased]: https://github.com/chshersh/iris/compare/v0.0.0.0...HEAD
+[0.0.0.0]: https://github.com/chshersh/iris/releases/tag/v0.0.0.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,180 @@
+# iris
+
+[![GitHub CI](https://github.com/chshersh/iris/workflows/CI/badge.svg)](https://github.com/chshersh/iris/actions)
+[![Hackage](https://img.shields.io/hackage/v/iris.svg?logo=haskell)](https://hackage.haskell.org/package/iris)
+[![MPL-2.0 license](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](LICENSE)
+
+**Iris** is a Haskell framework for building CLI applications that follow
+[Command Line Interface Guidelines](https://clig.dev/).
+
+> 🌈 Iris (/ˈaɪrɪs/) is a Greek goddess associated with communication, messages,
+> the rainbow, and new endeavors.
+
+<picture>
+  <source media="(prefers-color-scheme: dark)"  srcset="https://raw.githubusercontent.com/chshersh/iris/main/images/iris-dark.png">
+  <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/chshersh/iris/main/images/iris-light.png">
+  <img alt="Iris changing her workflow and hair colour depending on the time of day." src="https://raw.githubusercontent.com/chshersh/iris/main/images/iris-dark-always.png">
+</picture>
+
+> ℹ️ **DISCLAIMER #1:** Currently, Iris is in experimental phase and
+> mostly for early adopters. It may lack documentation or have
+> significant breaking changes. We appreciate anyone's help in
+> improving the documentation! At the same time, the maintainers will
+> strive to provide helpful migration guides.
+
+> ℹ️ **DISCLAIMER #2:** Iris is developed and maintained in free time
+> by volunteers. The development may continue for decades or may stop
+> tomorrow. You can use
+> [GitHub Sponsorship](https://github.com/sponsors/chshersh) to support
+> the development of this project.
+
+## Goals
+
+Iris development is guided by the following principles:
+
+1. **Support [Command Line Interface Guidelines](https://clig.dev/).**
+   Features or changes that violate these guidelines are not accepted
+   in the project.
+2. **Beginner-friendliess.** Haskell beginners should be able to build
+   CLI applications with Iris. Hence, the implementation of Iris API
+   that uses less fancy Haskell features are preferred. When the
+   complexity is justified, the cost of introducing this extra
+   complexity should be mitigated by having better documentation.
+3. **Reasonable batteries-included.** Iris is not trying to be
+   minimalistic as possible, it strives to provide out-of-the-box
+   solutions for most common problems. But at the same time, we don't
+   want to impose unnecessary heavy dependencies.
+4. **Excellent documentation.** Iris documentation should be as
+   helpful as possible in using the framework.
+
+   > **NOTE:** Currently, Iris may lack documentation but there's an
+   > ongoing effort to improve the situation.
+
+🧱 Iris focuses solely on CLI applications. If you're interested in
+building TUI app with Haskell, check out
+[brick](https://hackage.haskell.org/package/brick).
+
+## Features
+
+CLI apps built with Iris offer the following features for end users:
+
+* Automatic detection of colouring support in the terminal
+* Ability to check required external tools if you need e.g. `curl` or
+  `git`
+* Support for standard CLI options out-of-the-box:
+    * `--help`
+    * `--version`
+    * `--numeric-version`: helpful for detecting required tools versions
+    * `--no-input`: for disabling all interactive features
+* Utilities to open files in a browser
+
+## How to use?
+
+`iris` is compatible with the following GHC
+versions - [supported versions](https://matrix.hackage.haskell.org/#/package/iris)
+
+In order to start using `iris` in your project, you
+will need to set it up with these steps:
+
+1. Add the dependency on `iris` in your project's
+   `.cabal` file. For this, you should modify the `build-depends`
+   section according to the below section:
+
+   ```haskell
+   build-depends:
+     , base ^>= LATEST_SUPPORTED_BASE
+     , iris ^>= LATEST_VERSION
+   ```
+
+2. To use this package, refer to the below example.
+
+   ```haskell
+   {-# LANGUAGE GeneralizedNewtypeDeriving #-}
+
+   module Main (main) where
+
+   import Control.Monad.IO.Class (MonadIO (..))
+
+   import qualified Iris
+
+
+   newtype App a = App
+       { unApp :: Iris.CliApp () () a
+       } deriving newtype
+           ( Functor
+           , Applicative
+           , Monad
+           , MonadIO
+           )
+
+   appSettings :: Iris.CliEnvSettings () ()
+   appSettings = Iris.defaultCliEnvSettings
+       { Iris.cliEnvSettingsHeaderDesc = "Iris usage example"
+       , Iris.cliEnvSettingsProgDesc = "A simple 'Hello, world!' utility"
+       }
+
+   app :: App ()
+   app = liftIO $ putStrLn "Hello, world!"
+
+   main :: IO ()
+   main = Iris.runCliApp appSettings $ unApp app
+   ```
+
+## For contributors
+
+Check [CONTRIBUTING.md](https://github.com/chshersh/iris/blob/main/CONTRIBUTING.md)
+for contributing guidelines.
+
+## Development
+
+To build the project and run the tests locally, you can use either
+`cabal` or `stack`.
+
+> See the [First time](#first-time) section if you don't have Haskell
+> development environment locally.
+
+### Cabal
+
+Build the project:
+
+```shell
+cabal build all
+```
+
+Run all unit tests:
+
+```shell
+cabal test --enable-tests --test-show-details=direct
+```
+
+### Stack
+
+Build the project:
+
+```shell
+stack build --test --no-run-tests
+```
+
+Run all unit tests:
+
+```shell
+stack test
+```
+
+### First time
+
+If this is your first time dealing with Haskell tooling, we recommend
+using [GHCup](https://www.haskell.org/ghcup/).
+
+During the installation, GHCup will suggest you installing all the
+necessary tools. If you have GHCup installed but miss some of the
+tooling for some reason, type the following commands in the terminal:
+
+```shell
+ghcup install ghc 8.10.7
+ghcup set     ghc 8.10.7
+ghcup install cabal 3.6.2.0
+```
+
+> If you are using Linux or macOS, you may find `ghcup tui` command a
+> more attractive option.
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,45 @@
+module Main (main) where
+
+import Control.Monad.IO.Class (MonadIO (..))
+import Control.Monad.Reader (MonadReader)
+
+import qualified Colourista
+import qualified Iris
+import qualified Paths_iris as Autogen
+
+
+newtype App a = App
+    { unApp :: Iris.CliApp () () a
+    } deriving newtype
+        ( Functor
+        , Applicative
+        , Monad
+        , MonadIO
+        , MonadReader (Iris.CliEnv () ())
+        )
+
+appSettings :: Iris.CliEnvSettings () ()
+appSettings = Iris.defaultCliEnvSettings
+    { Iris.cliEnvSettingsHeaderDesc = "Iris usage example"
+    , Iris.cliEnvSettingsProgDesc = "A simple grep utility"
+    , Iris.cliEnvSettingsVersionSettings =
+        Just (Iris.defaultVersionSettings Autogen.version)
+            { Iris.versionSettingsMkDesc = \v -> "Iris Example v" <> v
+            }
+    , Iris.cliEnvSettingsRequiredTools = ["curl", "ghc"]
+    }
+
+app :: App ()
+app = do
+    liftIO $ putStrLn "Hello from an Iris app!"
+
+    Iris.putStdoutColouredLn
+        (Colourista.formatWith [Colourista.yellow, Colourista.bold])
+        "I'm yellow"
+
+    Iris.putStderrColouredLn
+        (Colourista.formatWith [Colourista.blueBg])
+        "I'm white on blue"
+
+main :: IO ()
+main = Iris.runCliApp appSettings $ unApp app
diff --git a/iris.cabal b/iris.cabal
new file mode 100644
--- /dev/null
+++ b/iris.cabal
@@ -0,0 +1,133 @@
+cabal-version:       3.0
+name:                iris
+version:             0.0.0.0
+synopsis:            Haskell CLI framework
+description:
+    Haskell CLI framework.
+    See [README.md](https://github.com/chshersh/iris#iris) for more details.
+homepage:            https://github.com/chshersh/iris
+bug-reports:         https://github.com/chshersh/iris/issues
+license:             MPL-2.0
+license-file:        LICENSE
+author:              Dmitrii Kovanikov
+maintainer:          Dmitrii Kovanikov <kovanikov@gmail.com>
+copyright:           2022 Dmitrii Kovanikov
+category:            CLI,Framework
+build-type:          Simple
+extra-doc-files:     README.md
+                     CHANGELOG.md
+tested-with:         GHC == 9.2.4
+                     GHC == 9.0.2
+                     GHC == 8.10.7
+
+source-repository head
+  type:                git
+  location:            https://github.com/chshersh/iris.git
+
+common common-options
+  build-depends:       base >= 4.14 && < 4.17
+
+  ghc-options:         -Wall
+                       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+                       -Wnoncanonical-monad-instances
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
+  if impl(ghc >= 8.8)
+    ghc-options:       -Wmissing-deriving-strategies
+                       -fwrite-ide-info
+                       -hiedir=.hie
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
+  if impl(ghc >= 9.0)
+    ghc-options:       -Winvalid-haddock
+  if impl(ghc >= 9.2)
+    ghc-options:       -Wredundant-bang-patterns
+                       -Woperator-whitespace
+
+  default-language:    Haskell2010
+  default-extensions:  ConstraintKinds
+                       DeriveAnyClass
+                       DeriveGeneric
+                       DerivingStrategies
+                       GeneralizedNewtypeDeriving
+                       InstanceSigs
+                       KindSignatures
+                       LambdaCase
+                       OverloadedStrings
+                       RecordWildCards
+                       ScopedTypeVariables
+                       StandaloneDeriving
+                       StrictData
+                       TupleSections
+                       TypeApplications
+                       ViewPatterns
+
+library
+  import:              common-options
+  hs-source-dirs:      src
+  exposed-modules:
+    Iris
+      Iris.App
+      Iris.Browse
+      Iris.Cli
+        Iris.Cli.Browse
+        Iris.Cli.Interactive
+        Iris.Cli.Version
+      Iris.Colour
+        Iris.Colour.Formatting
+        Iris.Colour.Mode
+      Iris.Env
+      Iris.Tool
+
+  build-depends:
+    , ansi-terminal        ^>= 0.11
+    , directory            ^>= 1.3
+    , bytestring            >= 0.10 && < 0.12
+    , mtl                   >= 2.2  && < 2.4
+    , optparse-applicative ^>= 0.17
+    , process              ^>= 1.6
+    , text                  >= 1.2 && < 2.1
+    , unliftio-core        ^>= 0.2
+
+executable iris-example
+  import:              common-options
+  hs-source-dirs:      app
+
+  main-is:             Main.hs
+  autogen-modules:     Paths_iris
+  other-modules:       Paths_iris
+
+  build-depends:
+     , iris
+     , colourista ^>= 0.1
+     , mtl
+
+  ghc-options:         -threaded
+                       -rtsopts
+                       -with-rtsopts=-N
+
+test-suite iris-test
+  import:              common-options
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+
+  other-modules:
+    Test.Iris
+      Test.Iris.Tool
+
+  build-depends:
+    , iris
+    , hspec >= 2.9.7 && < 2.11
+    , text
+
+  ghc-options:         -threaded
+                       -rtsopts
+                       -with-rtsopts=-N
diff --git a/src/Iris.hs b/src/Iris.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris.hs
@@ -0,0 +1,77 @@
+{- |
+Module                  : Iris
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+__Iris__ is a Haskell CLI framework. It contains batteries for bulding
+CLI applications in Haskell by following best-practices.
+
+The library is designed for __qualified__ imports. To use it, import
+like this:
+
+@
+__import qualified__ "Iris"
+@
+
+To create an CLI application with __Iris__, you need to do the
+following steps:
+
+1. Create settings for your applications by constructing a value of
+   type 'CliEnvSettings'.
+2. Define a monad for your application with the help of 'CliApp' by
+   using either __type__ or __newtype__.
+
+That's all! Now, you can write your CLI app by having access to all
+capabilities provided by __Iris__ 🎉
+
+@since 0.0.0.0
+-}
+
+module Iris
+    ( -- $app
+      module Iris.App
+      -- $browse
+    , module Iris.Browse
+      -- $cli
+    , module Iris.Cli
+      -- $colour
+    , module Iris.Colour
+      -- $env
+    , module Iris.Env
+      -- $tool
+    , module Iris.Tool
+    ) where
+
+import Iris.App
+import Iris.Browse
+import Iris.Cli
+import Iris.Colour
+import Iris.Env
+import Iris.Tool
+
+{- $app
+CLI Application monad.
+-}
+
+{- $browse
+Functions to open local files in a browser.
+-}
+
+{- $cli
+CLI parsing utilities.
+-}
+
+{- $colour
+Functions to detect terminal support for colouring and print coloured output.
+-}
+
+{- $env
+Global environment for a CLI application and CLI app settings.
+-}
+
+{- $tool
+Capabilities to check required tools on the application start.
+-}
diff --git a/src/Iris/App.hs b/src/Iris/App.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/App.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DerivingVia #-}
+
+{- |
+Module                  : Iris.Env
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Haskell CLI framework
+
+@since 0.0.0.0
+-}
+
+module Iris.App
+    ( CliApp (..)
+    , runCliApp
+    , runCliAppManually
+    ) where
+
+import Control.Monad.IO.Class (MonadIO)
+import Control.Monad.Reader (MonadReader, ReaderT (..))
+import Control.Monad.IO.Unlift (MonadUnliftIO)
+
+import Iris.Env (CliEnv, CliEnvSettings, mkCliEnv)
+
+
+{- | Main monad for your CLI application.
+
+@since 0.0.0.0
+-}
+newtype CliApp cmd appEnv a = CliApp
+    { unCliApp :: CliEnv cmd appEnv -> IO a
+    } deriving
+        ( Functor
+        -- ^ @since 0.0.0.0
+        , Applicative
+        -- ^ @since 0.0.0.0
+        , Monad
+        -- ^ @since 0.0.0.0
+        , MonadIO
+        -- ^ @since 0.0.0.0
+        , MonadReader (CliEnv cmd appEnv)
+        -- ^ @since 0.0.0.0
+        , MonadUnliftIO
+        -- ^ @since 0.0.0.0
+        ) via ReaderT (CliEnv cmd appEnv) IO
+
+{- | Run application with settings.
+
+@since 0.0.0.0
+-}
+runCliApp :: CliEnvSettings cmd appEnv -> CliApp cmd appEnv a -> IO a
+runCliApp settings cliApp = do
+    cliEnv <- mkCliEnv settings
+    runCliAppManually cliEnv cliApp
+
+{- | Run application by constructing 'CliEnv' settings manually.
+
+@since 0.0.0.0
+-}
+runCliAppManually :: CliEnv cmd appEnv -> CliApp cmd appEnv a -> IO a
+runCliAppManually cliEnv (CliApp run) = run cliEnv
diff --git a/src/Iris/Browse.hs b/src/Iris/Browse.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Browse.hs
@@ -0,0 +1,159 @@
+{- |
+Module                  : Iris.Browse
+Copyright               : (c) 2020 Kowainik
+                          (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Implements a function that opens a given file in a browser.
+
+@since 0.0.0.0
+-}
+
+module Iris.Browse
+    ( openInBrowser
+    , BrowseException (..)
+    ) where
+
+import Control.Exception (Exception, throwIO)
+import System.Directory (findExecutable)
+import System.Environment (lookupEnv)
+import System.Info (os)
+import System.Process (callCommand, showCommandForUser)
+
+
+{- | Exception thrown by 'openInBrowser'.
+
+@since 0.0.0.0
+-}
+newtype BrowseException
+    -- | Can't find a browser application. Stores the current OS inside.
+    --
+    -- @since 0.0.0.0
+    = BrowserNotFoundException String
+    deriving stock
+        ( Show  -- ^ @since 0.0.0.0
+        )
+
+    deriving newtype
+        ( Eq  -- ^ @since 0.0.0.0
+        )
+
+    deriving anyclass
+        ( Exception  -- ^ @since 0.0.0.0
+        )
+
+
+{- | Open a given file in a browser. The function has the following algorithm:
+
+* Check the @BROWSER@ environment variable
+* If it's not set, try to guess browser depending on OS
+* If unsuccsessful, print a message
+
+__Throws:__ 'BrowseException' if can't find a browser.
+
+@since 0.0.0.0
+-}
+openInBrowser :: FilePath -> IO ()
+openInBrowser file = lookupEnv "BROWSER" >>= \case
+    Just browser -> runCommand browser [file]
+    Nothing -> case os of
+        "darwin"  -> runCommand "open" [file]
+        "mingw32" -> runCommand "cmd"  ["/c", "start", file]
+        curOs     -> do
+            browserExe <- findFirstExecutable
+                [ "xdg-open"
+                , "cygstart"
+                , "x-www-browser"
+                , "firefox"
+                , "opera"
+                , "mozilla"
+                , "netscape"
+                ]
+            case browserExe of
+                Just browser -> runCommand browser [file]
+                Nothing      -> throwIO $ BrowserNotFoundException curOs
+
+-- | Execute a command with arguments.
+runCommand :: FilePath -> [String] -> IO ()
+runCommand cmd args = do
+    let cmdStr = showCommandForUser cmd args
+    putStrLn $ "⚙  " ++ cmdStr
+    callCommand cmdStr
+
+findFirstExecutable :: [FilePath] -> IO (Maybe FilePath)
+findFirstExecutable = \case
+    [] -> pure Nothing
+    exe:exes -> findExecutable exe >>= \case
+        Nothing   -> findFirstExecutable exes
+        Just path -> pure $ Just path
+
+{-
+------------------------
+-- Original source code:
+------------------------
+
+{- |
+Copyright: (c) 2020 Kowainik
+SPDX-License-Identifier: MPL-2.0
+Maintainer: Kowainik <xrom.xkov@gmail.com>
+
+Contains implementation of a function that opens a given file in a
+browser.
+-}
+
+module Stan.Browse
+    ( openBrowser
+    ) where
+
+import Colourista (errorMessage, infoMessage)
+import System.Directory (findExecutable)
+import System.Info (os)
+import System.Process (callCommand, showCommandForUser)
+
+
+{- | Open a given file in a browser. The function has the following algorithm:
+
+* Check the @BROWSER@ environment variable
+* If it's not set, try to guess browser depending on OS
+* If unsuccsessful, print a message
+-}
+openBrowser :: FilePath -> IO ()
+openBrowser file = lookupEnv "BROWSER" >>= \case
+    Just browser -> runCommand browser [file]
+    Nothing -> case os of
+        "darwin"  -> runCommand "open" [file]
+        "mingw32" -> runCommand "cmd"  ["/c", "start", file]
+        curOs    -> do
+            browserExe <- findFirstExecutable
+                [ "xdg-open"
+                , "cygstart"
+                , "x-www-browser"
+                , "firefox"
+                , "opera"
+                , "mozilla"
+                , "netscape"
+                ]
+            case browserExe of
+                Just browser -> runCommand browser [file]
+                Nothing -> do
+                    errorMessage $ "Cannot guess browser for the OS: " <> toText curOs
+                    infoMessage "Please set the $BROWSER environment variable to a web launcher"
+                    exitFailure
+
+-- | Execute a command with arguments.
+runCommand :: FilePath -> [String] -> IO ()
+runCommand cmd args = do
+    let cmdStr = showCommandForUser cmd args
+    putStrLn $ "⚙  " ++ cmdStr
+    callCommand cmdStr
+
+findFirstExecutable :: [FilePath] -> IO (Maybe FilePath)
+findFirstExecutable = \case
+    [] -> pure Nothing
+    exe:exes -> findExecutable exe >>= \case
+        Nothing   -> findFirstExecutable exes
+        Just path -> pure $ Just path
+-}
diff --git a/src/Iris/Cli.hs b/src/Iris/Cli.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Cli.hs
@@ -0,0 +1,30 @@
+{- |
+Module                  : Iris.Cli
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+CLI options parsing.
+
+@since 0.0.0.0
+-}
+
+module Iris.Cli
+    ( -- $browse
+      module Iris.Cli.Browse
+      -- $version
+    , module Iris.Cli.Version
+    ) where
+
+import Iris.Cli.Browse
+import Iris.Cli.Version
+
+{- $browse
+CLI parsers for @--browse@ flags.
+-}
+
+{- $version
+CLI parsers for @--version@ and @--numeric-version@ flags.
+-}
diff --git a/src/Iris/Cli/Browse.hs b/src/Iris/Cli/Browse.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Cli/Browse.hs
@@ -0,0 +1,50 @@
+{- |
+Module                  : Iris.Cli.Browse
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+CLI options parsing for @--browse@ and @--browse=<FILE_PATH>@.
+
+@since 0.0.0.0
+-}
+
+module Iris.Cli.Browse
+    ( browseP
+    , browseFileP
+    ) where
+
+import qualified Options.Applicative as Opt
+
+{- | A CLI option parser yields a boolean value if a file needs to be open in
+a browser.
+
+Use 'Iris.Browse.openInBrowser' to open the file of your choice in a
+browser.
+
+@since 0.0.0.0
+-}
+browseP
+    :: String  -- ^ Flag description
+    -> Opt.Parser Bool
+browseP description = Opt.switch $ mconcat
+    [ Opt.long "browse"
+    , Opt.help description
+    ]
+
+{- | A CLI option parser for a 'FilePath' that needs to be open with a browser.
+
+Use 'Iris.Browse.openInBrowser' to open the passed file in a browser.
+
+@since 0.0.0.0
+-}
+browseFileP
+    :: String  -- ^ Flag description
+    -> Opt.Parser FilePath
+browseFileP description = Opt.option Opt.str $ mconcat
+    [ Opt.long "browse"
+    , Opt.metavar "FILE_PATH"
+    , Opt.help description
+    ]
diff --git a/src/Iris/Cli/Interactive.hs b/src/Iris/Cli/Interactive.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Cli/Interactive.hs
@@ -0,0 +1,48 @@
+{- |
+Module                  : Iris.Cli.Interactive
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Interative mode datatype and CLI parser.
+
+@since 0.0.0.0
+-}
+
+module Iris.Cli.Interactive
+    ( InteractiveMode (..)
+    , interactiveModeP
+    ) where
+
+import Options.Applicative ( (<|>) )
+import qualified Options.Applicative as Opt
+
+{- Datatype for specifying if the terminal is interactive.
+    
+@since 0.0.0.0
+-}
+data InteractiveMode
+    -- | @since 0.0.0.0
+    = Interactive 
+    -- | @since 0.0.0.0
+    | NonInteractive 
+    deriving stock 
+        ( Show       -- ^ @since 0.0.0.0
+        , Eq         -- ^ @since 0.0.0.0
+        )
+
+
+{- | A CLI option parser for switching to non-interactive mode if the flag is passed.
+
+@since 0.0.0.0
+-}
+interactiveModeP :: Opt.Parser InteractiveMode
+interactiveModeP = nonInteractiveP <|> pure Interactive
+  where
+    nonInteractiveP :: Opt.Parser InteractiveMode
+    nonInteractiveP = Opt.flag' NonInteractive $ mconcat
+        [ Opt.long "no-input"
+        , Opt.help "Enter the terminal in non-interactive mode"
+        ]
diff --git a/src/Iris/Cli/Version.hs b/src/Iris/Cli/Version.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Cli/Version.hs
@@ -0,0 +1,82 @@
+{- |
+Module                  : Iris.Cli.Version
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+CLI options parsing for @--version@ and @--numeric-version@
+
+__Enabled with config__
+
+@since 0.0.0.0
+-}
+
+module Iris.Cli.Version
+    ( -- * Settings
+      VersionSettings (..)
+    , defaultVersionSettings
+
+      -- * CLI parser
+    , fullVersionP
+
+      -- * Internal helpers
+    , mkVersionParser
+    ) where
+
+import Data.Version (Version, showVersion)
+
+import qualified Options.Applicative as Opt
+
+
+{- |
+
+@since 0.0.0.0
+-}
+data VersionSettings = VersionSettings
+    { -- | @since 0.0.0.0
+      versionSettingsVersion :: Version
+
+      -- | @since 0.0.0.0
+    , versionSettingsMkDesc  :: String -> String
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+defaultVersionSettings :: Version -> VersionSettings
+defaultVersionSettings version = VersionSettings
+    { versionSettingsVersion = version
+    , versionSettingsMkDesc  = id
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+mkVersionParser :: Maybe VersionSettings -> Opt.Parser (a -> a)
+mkVersionParser = maybe (pure id) fullVersionP
+
+{- |
+
+@since 0.0.0.0
+-}
+fullVersionP :: VersionSettings -> Opt.Parser (a -> a)
+fullVersionP VersionSettings{..} = versionP <*> numericVersionP
+  where
+    versionStr :: String
+    versionStr = showVersion versionSettingsVersion
+
+    versionP :: Opt.Parser (a -> a)
+    versionP = Opt.infoOption (versionSettingsMkDesc versionStr) $ mconcat
+       [ Opt.long "version"
+       , Opt.help "Show application version"
+       ]
+
+    numericVersionP :: Opt.Parser (a -> a)
+    numericVersionP = Opt.infoOption versionStr $ mconcat
+       [ Opt.long "numeric-version"
+       , Opt.help "Show only numeric application version"
+       ]
diff --git a/src/Iris/Colour.hs b/src/Iris/Colour.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Colour.hs
@@ -0,0 +1,30 @@
+{- |
+Module                  : Iris.Colour
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Functions to handle colouring of the output to terminal.
+
+@since 0.0.0.0
+-}
+
+module Iris.Colour
+    ( -- $mode
+      module Iris.Colour.Mode
+      -- $formatting
+    , module Iris.Colour.Formatting
+    ) where
+
+import Iris.Colour.Formatting
+import Iris.Colour.Mode
+
+{- $mode
+Colouring mode in application.
+-}
+
+{- $formatting
+Formatting of Terminal output
+-}
diff --git a/src/Iris/Colour/Formatting.hs b/src/Iris/Colour/Formatting.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Colour/Formatting.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+{- |
+Module                  : Iris.Colour.Formatting
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Helper functions to print with colouring.
+
+@since 0.0.0.0
+-}
+
+module Iris.Colour.Formatting
+    ( putStdoutColouredLn
+    , putStderrColouredLn
+    ) where
+
+import Control.Monad.IO.Class (MonadIO (..))
+import Control.Monad.Reader (MonadReader)
+import Data.ByteString (ByteString)
+import System.IO (stderr)
+
+import Iris.Colour.Mode (ColourMode (..))
+import Iris.Env (CliEnv (..), asksCliEnv)
+
+import qualified Data.ByteString.Char8 as BS8
+
+
+{- | Print 'ByteString' to 'System.IO.stdout' by providing a custom
+formatting function.
+
+This works especially well with the @colourista@ package:
+
+@
+'putStdoutColouredLn'
+    (Colourista.formatWith [Colourista.bold, Colourista.green])
+    "my message"
+@
+
+@since 0.0.0.0
+-}
+putStdoutColouredLn
+    :: ( MonadReader (CliEnv cmd appEnv) m
+       , MonadIO m
+       )
+    => (ByteString -> ByteString)
+    -> ByteString
+    -> m ()
+putStdoutColouredLn formatWithColour str = do
+    colourMode <- asksCliEnv cliEnvStdoutColourMode
+    liftIO $ BS8.putStrLn $ case colourMode of
+        DisableColour -> str
+        EnableColour  -> formatWithColour str
+
+{- | Print 'ByteString' to 'System.IO.stderr' by providing a custom
+formatting function.
+
+This works especially well with the @colourista@ package:
+
+@
+'putStderrColouredLn'
+    (Colourista.formatWith [Colourista.bold, Colourista.green])
+    "my message"
+@
+
+@since 0.0.0.0
+-}
+putStderrColouredLn
+    :: ( MonadReader (CliEnv cmd appEnv) m
+       , MonadIO m
+       )
+    => (ByteString -> ByteString)
+    -> ByteString
+    -> m ()
+putStderrColouredLn formatWithColour str = do
+    colourMode <- asksCliEnv cliEnvStderrColourMode
+    liftIO $ BS8.hPutStrLn stderr $ case colourMode of
+        DisableColour -> str
+        EnableColour  -> formatWithColour str
diff --git a/src/Iris/Colour/Mode.hs b/src/Iris/Colour/Mode.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Colour/Mode.hs
@@ -0,0 +1,176 @@
+{- |
+Module                  : Iris.Colour.Mode
+Copyright               : (c) 2020 Kowainik
+                          (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+The 'ColourMode' data type that allows disabling and enabling of
+colouring.
+
+@since 0.0.0.0
+-}
+
+module Iris.Colour.Mode
+    ( ColourMode (..)
+    , handleColourMode
+    ) where
+
+import System.Console.ANSI (hSupportsANSIColor)
+import System.IO (Handle)
+
+
+{- | Data type that tells whether the colouring is enabled or
+disabled. Its value is detected automatically on application start and
+stored in 'Iris.Env.CliEnv'.
+
+@since 0.0.0.0
+-}
+data ColourMode
+    -- | @since 0.0.0.0
+    = DisableColour
+
+    -- | @since 0.0.0.0
+    | EnableColour
+    deriving stock
+        ( Show     -- ^ @since 0.0.0.0
+        , Eq       -- ^ @since 0.0.0.0
+        , Ord      -- ^ @since 0.0.0.0
+        , Enum     -- ^ @since 0.0.0.0
+        , Bounded  -- ^ @since 0.0.0.0
+        )
+
+{- | Returns 'ColourMode' of a 'Handle'. You can use this function on
+output 'Handle's to find out whether they support colouring or
+not.
+
+Use a function like this to check whether you can print with colour
+to terminal:
+
+@
+'handleColourMode' 'System.IO.stdout'
+@
+
+@since 0.0.0.0
+-}
+handleColourMode :: Handle -> IO ColourMode
+handleColourMode handle = do
+    supportsANSI <- hSupportsANSIColor handle
+    pure $ if supportsANSI then EnableColour else DisableColour
+
+{-
+------------------------
+-- Original source code:
+------------------------
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE ImplicitParams        #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+{- |
+Copyright: (c) 2020 Kowainik
+SPDX-License-Identifier: MPL-2.0
+Maintainer: Kowainik <xrom.xkov@gmail.com>
+
+The 'ColourMode' data type that allows disabling and enabling of
+colouring. Implemented using the [Implicit Parameters](https://downloads.haskell.org/ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters)
+GHC feature.
+
+By default, all formatting and printing functions in @colourista@
+print with colour. However, you control this behaviour by adding the
+@HasColourMode@ constraint to your functions and setting the value of
+the implicit @?colourMode@ variable.
+
+@since 0.2.0.0
+-}
+
+module Colourista.Mode
+    ( ColourMode (..)
+    , HasColourMode
+    , withColourMode
+    , handleColourMode
+    ) where
+
+import System.IO (Handle)
+import System.Console.ANSI (hSupportsANSIWithoutEmulation)
+import Data.String (IsString)
+
+import GHC.Classes (IP (..))
+
+
+{- | Data type that tells whether the colouring is enabled or
+disabled. It's used with the @-XImplicitParams@ GHC extension.
+
+@since 0.2.0.0
+-}
+data ColourMode
+    = DisableColour
+    | EnableColour
+    deriving stock (Show, Eq, Enum, Bounded)
+
+{- | Magic instance to set the value of the implicit variable
+@?colourMode@ to 'EnableColour' by default. Equivalent to the
+following code:
+
+@
+?colourMode = 'EnableColour'
+@
+
+However, you still can override @?colourMode@ with any possible value.
+
+@since 0.2.0.0
+-}
+instance IP "colourMode" ColourMode where
+    ip = EnableColour
+
+{- | Constraint that stores 'ColourMode' as an implicit parameter.
+
+@since 0.2.0.0
+-}
+type HasColourMode = (?colourMode :: ColourMode)
+
+{- | Helper function for writing custom formatter. The function takes
+'ColourMode' from the implicit parameter context and either returns a
+given string or an empty string.
+
+@since 0.2.0.0
+-}
+withColourMode :: (HasColourMode, IsString str) => str -> str
+withColourMode str = case ?colourMode of
+    EnableColour  -> str
+    DisableColour -> ""
+{-# INLINE withColourMode #-}
+
+{- | Returns 'ColourMode' of a 'Handle'. You can use this function on
+output 'Handle's to find out whether they support colouring or
+now. Use this function like this to check whether you can print with
+colour to terminal:
+
+@
+'handleColourMode' 'System.IO.stdout'
+@
+
+Typical usage can look like this:
+
+@
+main :: IO ()
+main = do
+    colourMode <- 'handleColourMode' 'System.IO.stdout'
+    let ?colourMode = fromMaybe 'DisableColour'
+    'Colourista.IO.successMessage' "Success!"
+@
+
+@since 0.2.0.0
+-}
+handleColourMode :: Handle -> IO (Maybe ColourMode)
+handleColourMode handle = do
+    supportsANSI <- hSupportsANSIWithoutEmulation handle
+    pure $ fmap
+        (\supportsColour -> if supportsColour then EnableColour else DisableColour)
+        supportsANSI
+-}
diff --git a/src/Iris/Env.hs b/src/Iris/Env.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Env.hs
@@ -0,0 +1,225 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ApplicativeDo #-}
+
+{- |
+Module                  : Iris.Env
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Environment of a CLI app.
+
+@since 0.0.0.0
+-}
+
+
+module Iris.Env
+    ( -- * Settings for the CLI app
+      CliEnvSettings (..)
+    , defaultCliEnvSettings
+
+      -- * CLI application environment
+      -- ** Constructing
+    , CliEnv (..)
+    , CliEnvException (..)
+    , CliEnvError (..)
+    , mkCliEnv
+
+      -- ** Querying
+    , asksCliEnv
+    , asksAppEnv
+    ) where
+
+import Control.Exception (Exception, throwIO)
+import Control.Monad.Reader (MonadReader, asks)
+import Data.Foldable (for_)
+import Data.Kind (Type)
+import System.IO (stderr, stdout)
+
+import Iris.Cli.Version (VersionSettings, mkVersionParser)
+import Iris.Cli.Interactive (InteractiveMode, interactiveModeP)
+import Iris.Colour.Mode (ColourMode, handleColourMode)
+import Iris.Tool (Tool, ToolCheckResult (..), checkTool)
+
+import qualified Options.Applicative as Opt
+
+
+{- |
+
+@since 0.0.0.0
+-}
+data CliEnvSettings (cmd :: Type) (appEnv :: Type) = CliEnvSettings
+    {  -- | @since 0.0.0.0
+      cliEnvSettingsCmdParser       :: Opt.Parser cmd
+
+      -- | @since 0.0.0.0
+    , cliEnvSettingsAppEnv          :: appEnv
+
+      -- | @since 0.0.0.0
+    , cliEnvSettingsHeaderDesc      :: String
+
+      -- | @since 0.0.0.0
+    , cliEnvSettingsProgDesc        :: String
+
+      -- | @since 0.0.0.0
+    , cliEnvSettingsVersionSettings :: Maybe VersionSettings
+
+      -- | @since 0.0.0.0
+    , cliEnvSettingsRequiredTools   :: [Tool cmd]
+    }
+
+
+{- |
+
+@since 0.0.0.0
+-}
+defaultCliEnvSettings :: CliEnvSettings () ()
+defaultCliEnvSettings = CliEnvSettings
+    { cliEnvSettingsCmdParser       = pure ()
+    , cliEnvSettingsAppEnv          = ()
+    , cliEnvSettingsHeaderDesc      = "Simple CLI program"
+    , cliEnvSettingsProgDesc        = "CLI tool build with iris - a Haskell CLI framework"
+    , cliEnvSettingsVersionSettings = Nothing
+    , cliEnvSettingsRequiredTools   = []
+    }
+
+
+{- | CLI application environment. It contains default settings for
+every CLI app and parameter
+
+Has the following type parameters:
+
+* @cmd@ — application commands
+* @appEnv@ — application-specific environment; use @()@ if you don't
+  have custom app environment
+
+@since 0.0.0.0
+-}
+data CliEnv (cmd :: Type) (appEnv :: Type) = CliEnv
+    { -- | @since 0.0.0.0
+      cliEnvCmd              :: cmd
+
+      -- | @since 0.0.0.0
+    , cliEnvStdoutColourMode :: ColourMode
+
+      -- | @since 0.0.0.0
+    , cliEnvStderrColourMode :: ColourMode
+
+      -- | @since 0.0.0.0
+    , cliEnvAppEnv           :: appEnv
+
+      -- | @since 0.0.0.0
+    , cliEnvInteractiveMode  :: InteractiveMode
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+newtype CliEnvError
+    -- | @since 0.0.0.0
+    = CliEnvToolError ToolCheckResult
+    deriving stock
+        ( Show  -- ^ @since 0.0.0.0
+        )
+
+    deriving newtype
+        ( Eq  -- ^ @since 0.0.0.0
+        )
+{- |
+
+@since 0.0.0.0
+-}
+newtype CliEnvException = CliEnvException
+    { unCliEnvException :: CliEnvError
+    }
+    deriving stock
+        ( Show  -- ^ @since 0.0.0.0
+        )
+
+    deriving newtype
+        ( Eq  -- ^ @since 0.0.0.0
+        )
+
+    deriving anyclass
+        ( Exception  -- ^ @since 0.0.0.0
+        )
+
+{- | 
+
+Wrapper around @cmd@ with additional predefined fields
+-}
+
+data Cmd (cmd :: Type) = Cmd
+    { cmdInteractiveMode :: InteractiveMode
+    , cmdCmd :: cmd
+    }
+
+{- |
+
+__Throws:__ 'CliEnvException'
+
+@since 0.0.0.0
+-}
+mkCliEnv
+    :: forall cmd appEnv
+    .  CliEnvSettings cmd appEnv
+    -> IO (CliEnv cmd appEnv)
+mkCliEnv CliEnvSettings{..} = do
+    Cmd{..} <- Opt.execParser cmdParserInfo
+    stdoutColourMode <- handleColourMode stdout
+    stderrColourMode <- handleColourMode stderr
+
+    for_ cliEnvSettingsRequiredTools $ \tool ->
+        checkTool cmdCmd tool >>= \case
+            ToolOk  -> pure ()
+            toolErr -> throwIO $ CliEnvException $ CliEnvToolError toolErr
+
+    pure CliEnv
+        { cliEnvCmd              = cmdCmd
+        , cliEnvStdoutColourMode = stdoutColourMode
+        , cliEnvStderrColourMode = stderrColourMode
+        , cliEnvAppEnv           = cliEnvSettingsAppEnv
+        , cliEnvInteractiveMode  = cmdInteractiveMode
+        }
+  where
+    cmdParserInfo :: Opt.ParserInfo (Cmd cmd)
+    cmdParserInfo = Opt.info
+        ( Opt.helper
+        <*> mkVersionParser cliEnvSettingsVersionSettings
+        <*> cmdP
+        )
+        $ mconcat
+            [ Opt.fullDesc
+            , Opt.header cliEnvSettingsHeaderDesc
+            , Opt.progDesc cliEnvSettingsProgDesc
+            ]
+    cmdP :: Opt.Parser (Cmd cmd)
+    cmdP = do
+      cmdInteractiveMode <- interactiveModeP
+      cmdCmd <- cliEnvSettingsCmdParser
+
+      pure Cmd{..}
+
+{- | Get a field from the global environment 'CliEnv'.
+
+@since 0.0.0.0
+-}
+asksCliEnv
+    :: MonadReader (CliEnv cmd appEnv) m
+    => (CliEnv cmd appEnv -> field)
+    -> m field
+asksCliEnv = asks
+
+{- | Get a field from custom application-specific environment
+@appEnv@.
+
+@since 0.0.0.0
+-}
+asksAppEnv
+    :: MonadReader (CliEnv cmd appEnv) m
+    => (appEnv -> field)
+    -> m field
+asksAppEnv getField = asksCliEnv (getField . cliEnvAppEnv)
diff --git a/src/Iris/Tool.hs b/src/Iris/Tool.hs
new file mode 100644
--- /dev/null
+++ b/src/Iris/Tool.hs
@@ -0,0 +1,122 @@
+{- |
+Module                  : Iris.Tool
+Copyright               : (c) 2022 Dmitrii Kovanikov
+SPDX-License-Identifier : MPL-2.0
+Maintainer              : Dmitrii Kovanikov <kovanikov@gmail.com>
+Stability               : Experimental
+Portability             : Portable
+
+Utilities to check required tools and their minimal version for a CLI app.
+
+@since 0.0.0.0
+-}
+
+module Iris.Tool
+    ( -- * Types describing executable requirements
+      Tool (..)
+    , ToolSelector (..)
+    , defaultToolSelector
+
+      -- * Tool requirements check
+    , ToolCheckResult (..)
+    , checkTool
+    ) where
+
+import Data.String (IsString (..))
+import Data.Text (Text)
+import System.Directory (findExecutable)
+import System.Process (readProcess)
+
+import qualified Data.Text as Text
+
+
+{- |
+
+@since 0.0.0.0
+-}
+data Tool cmd = Tool
+    { -- | @since 0.0.0.0
+      toolName     :: Text
+
+      -- | @since 0.0.0.0
+    , toolSelector :: Maybe (ToolSelector cmd)
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+instance IsString (Tool cmd) where
+    fromString :: String -> Tool cmd
+    fromString s = Tool
+        { toolName     = fromString s
+        , toolSelector = Nothing
+        }
+
+{- |
+
+@since 0.0.0.0
+-}
+data ToolSelector cmd = ToolSelector
+    { -- | @since 0.0.0.0
+      toolSelectorFunction   :: cmd -> Text -> Bool
+
+      -- | @since 0.0.0.0
+    , toolSelectorVersionArg :: Maybe Text
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+defaultToolSelector :: ToolSelector cmd
+defaultToolSelector = ToolSelector
+    { toolSelectorFunction   = \_cmd _version -> True
+    , toolSelectorVersionArg = Nothing
+    }
+
+{- |
+
+@since 0.0.0.0
+-}
+data ToolCheckResult
+    {- |
+
+    @since 0.0.0.0
+    -}
+    = ToolNotFound Text
+
+    {- |
+
+    @since 0.0.0.0
+    -}
+    | ToolWrongVersion Text
+
+    {- |
+
+    @since 0.0.0.0
+    -}
+    | ToolOk
+    deriving stock
+        ( Show  -- ^ @since 0.0.0.0
+        , Eq    -- ^ @since 0.0.0.0
+        )
+
+{- |
+
+@since 0.0.0.0
+-}
+checkTool :: cmd -> Tool cmd -> IO ToolCheckResult
+checkTool cmd Tool{..} = findExecutable (Text.unpack toolName) >>= \case
+    Nothing  -> pure $ ToolNotFound toolName
+    Just exe -> case toolSelector of
+        Nothing               -> pure ToolOk
+        Just ToolSelector{..} -> case toolSelectorVersionArg of
+            Nothing         -> pure ToolOk
+            Just versionArg -> do
+                toolVersionOutput <- readProcess exe [Text.unpack versionArg] ""
+                let version = Text.strip $ Text.pack toolVersionOutput
+
+                if toolSelectorFunction cmd version
+                then pure ToolOk
+                else pure $ ToolWrongVersion version
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,9 @@
+module Main (main) where
+
+import Test.Hspec (hspec)
+
+import Test.Iris (irisSpec)
+
+
+main :: IO ()
+main = hspec irisSpec
diff --git a/test/Test/Iris.hs b/test/Test/Iris.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Iris.hs
@@ -0,0 +1,9 @@
+module Test.Iris (irisSpec) where
+
+import Test.Hspec (Spec, describe)
+
+import Test.Iris.Tool (toolSpec)
+
+irisSpec :: Spec
+irisSpec = describe "Iris" $ do
+    toolSpec
diff --git a/test/Test/Iris/Tool.hs b/test/Test/Iris/Tool.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/Iris/Tool.hs
@@ -0,0 +1,45 @@
+module Test.Iris.Tool (toolSpec) where
+
+import Data.Text (Text)
+import Data.Version (Version, makeVersion, parseVersion)
+import Test.Hspec (Spec, describe, it, shouldReturn, shouldSatisfy)
+import Text.ParserCombinators.ReadP (readP_to_S)
+
+import Iris.Tool (Tool (..), ToolCheckResult (..), ToolSelector (..), checkTool,
+                  defaultToolSelector)
+
+import qualified Data.Text as Text
+
+
+toolSpec :: Spec
+toolSpec = describe "Tool" $ do
+    it "should find 'curl'" $ do
+        checkTool () "curl" `shouldReturn` ToolOk
+
+    it "shouldn't find 'xxx_unknown_executable'" $ do
+        checkTool () "xxx_unknown_executable"
+            `shouldReturn` ToolNotFound "xxx_unknown_executable"
+
+    it "shouldn't find 'ghc' version 100" $ do
+        let tool :: Tool ()
+            tool = "ghc"
+                { toolSelector = Just defaultToolSelector
+                    { toolSelectorFunction = \_cmd version -> case getVersion version of
+                          Nothing -> False
+                          Just v  -> v >= makeVersion [100]
+                    , toolSelectorVersionArg = Just "--numeric-version"
+                    }
+                }
+
+        let isToolWrongVersion :: ToolCheckResult -> Bool
+            isToolWrongVersion (ToolWrongVersion _) = True
+            isToolWrongVersion _other               = False
+
+        checkTool () tool >>= (`shouldSatisfy` isToolWrongVersion)
+
+getVersion :: Text -> Maybe Version
+getVersion = extractVersion . readP_to_S parseVersion . Text.unpack
+  where
+    extractVersion :: [(Version, String)] -> Maybe Version
+    extractVersion [(version, "")] = Just version
+    extractVersion _               = Nothing
