diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# TaskLite
+
+CLI task manager built with [Haskell] and [SQLite].
+
+<img
+  src="screenshots/help-short.svg"
+  alt="Screenshot of TaskLite's help output"
+  width="600"
+/>
+
+Check out [tasklite.org] for extensive documentation.
+
+[tasklite.org]: https://tasklite.org
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+import Distribution.Simple
+
+
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,55 @@
+{-|
+This is the main module which provides the CLI
+-}
+module Main where
+
+import Protolude (
+  Bool (True),
+  Either (..),
+  IO,
+  die,
+  writeFile,
+  ($),
+ )
+
+import Data.Text qualified as T
+import Data.Yaml (decodeFileEither, prettyPrintParseException)
+import GHC.IO.Encoding (setLocaleEncoding, utf8)
+import System.Directory (
+  XdgDirectory (..),
+  createDirectoryIfMissing,
+  getXdgDirectory,
+ )
+import System.FilePath ((</>))
+
+import Cli (exampleConfig, printOutput)
+
+
+main :: IO ()
+main = do
+  -- Necessary for Docker image
+  setLocaleEncoding utf8
+
+  let appName = "tasklite"
+
+  configDirectory <- getXdgDirectory XdgConfig appName
+  createDirectoryIfMissing True configDirectory
+
+  let configPath = configDirectory </> "config.yaml"
+
+  configResult <- decodeFileEither configPath
+
+  case configResult of
+    Left error -> do
+      if "file not found"
+        `T.isInfixOf` T.pack (prettyPrintParseException error)
+        then do
+          writeFile configPath exampleConfig
+          configResult2 <- decodeFileEither configPath
+
+          case configResult2 of
+            Left error2 -> die $ T.pack $ prettyPrintParseException error2
+            Right configUser -> printOutput appName configUser
+        else die $ T.pack $ prettyPrintParseException error
+    Right configUser ->
+      printOutput appName configUser
diff --git a/example-config.yaml b/example-config.yaml
new file mode 100644
--- /dev/null
+++ b/example-config.yaml
@@ -0,0 +1,35 @@
+tableName: tasks
+idWidth: 4
+idStyle: green
+priorityStyle: magenta
+dateStyle: dull black
+bodyStyle: white
+bodyClosedStyle: black
+closedStyle: dull black
+dueStyle: yellow
+overdueStyle: red
+tagStyle: blue
+utcFormat: YYYY-MM-DD H:MI:S
+#| FIXME: Blocked by https://github.com/vincenthz/hs-hourglass/issue
+# utcFormatShort: YYYY-DDD H:MI
+utcFormatShort: YYYY-MM-DD H:MI
+
+#| Optional, uses the XDG directory per default
+#| https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+# dataDir: /custom/path/tasklite
+
+dbName: main.db
+dateWidth: 10
+bodyWidth: 10
+prioWidth: 4
+headCount: 20
+maxWidth: 120
+progressBarWidth: 24
+
+# hooks:
+#   #| Is per default the "hooks" directory in the `dataDir`
+#   # directory: /custom/path/hooks
+#   launch:
+#     pre:
+#       - interpreter: python3
+#         body: print('Python test')
diff --git a/tasklite.cabal b/tasklite.cabal
new file mode 100644
--- /dev/null
+++ b/tasklite.cabal
@@ -0,0 +1,90 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.36.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           tasklite
+version:        0.3.0.0
+synopsis:       CLI task / todo list manager with SQLite backend
+description:    TaskLite is a CLI task / todo list manager with a SQLite backend.
+                It is designed to be simple and easy to use,
+                while still providing a powerful interface for managing tasks.
+                It's heavily inspired by Taskwarrior and stems from my personal frustration
+                with some of its design decisions.
+                Check out https://tasklite.org/differences_taskwarrior
+                for a full comparison.
+category:       CLI, Task, Todo
+homepage:       https://github.com/ad-si/TaskLite#readme
+bug-reports:    https://github.com/ad-si/TaskLite/issues
+author:         Adrian Sieber
+maintainer:     mail@adriansieber.com
+copyright:      Adrian Sieber
+license:        AGPL-3.0-or-later
+build-type:     Simple
+extra-source-files:
+    README.md
+data-files:
+    example-config.yaml
+
+source-repository head
+  type: git
+  location: https://github.com/ad-si/TaskLite
+
+executable tasklite
+  main-is: Main.hs
+  other-modules:
+      Paths_tasklite
+  autogen-modules:
+      Paths_tasklite
+  hs-source-dirs:
+      app
+  default-extensions:
+      DeriveAnyClass
+      LambdaCase
+      MultiWayIf
+      NoImplicitPrelude
+      OverloadedRecordDot
+      OverloadedStrings
+      RecordWildCards
+      TypeFamilies
+      UndecidableInstances
+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wno-orphans -Wredundant-constraints -Wunused-packages
+  build-depends:
+      base >=4.18 && <5
+    , directory
+    , filepath
+    , protolude >=0.3
+    , tasklite-core
+    , text
+    , yaml
+  default-language: GHC2021
+
+test-suite tasklite-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      CliSpec
+      Paths_tasklite
+  autogen-modules:
+      Paths_tasklite
+  hs-source-dirs:
+      test
+  default-extensions:
+      DeriveAnyClass
+      LambdaCase
+      MultiWayIf
+      NoImplicitPrelude
+      OverloadedRecordDot
+      OverloadedStrings
+      RecordWildCards
+      TypeFamilies
+      UndecidableInstances
+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wno-orphans -Wredundant-constraints -Wunused-packages
+  build-depends:
+      base >=4.18 && <5
+    , hspec
+    , optparse-applicative
+    , protolude >=0.3
+    , tasklite-core
+  default-language: GHC2021
diff --git a/test/CliSpec.hs b/test/CliSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/CliSpec.hs
@@ -0,0 +1,35 @@
+module CliSpec where
+
+import Protolude (($), (&))
+import Protolude qualified as P
+
+import Options.Applicative (
+  ParseError (ShowHelpText),
+  ParserFailure,
+  ParserHelp,
+  defaultPrefs,
+  parserFailure,
+  renderFailure,
+ )
+import Test.Hspec (Spec, describe, it, shouldContain)
+
+import Cli (commandParserInfo)
+import Config (defaultConfig)
+
+
+spec :: Spec
+spec = do
+  describe "CLI" $ do
+    it "should include header, body, and footer in help output" $ do
+      let
+        failure :: ParserFailure ParserHelp =
+          parserFailure
+            defaultPrefs
+            (commandParserInfo defaultConfig)
+            (ShowHelpText P.Nothing)
+            []
+        helpText =
+          renderFailure failure "xxx" & P.fst
+
+      helpText `shouldContain` "Usage: xxx"
+      helpText `shouldContain` "developed by"
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,9 @@
+import Protolude (IO)
+import Test.Hspec (hspec)
+
+import CliSpec qualified
+
+
+main :: IO ()
+main = do
+  hspec CliSpec.spec
