From dc13ef5de9094eb2cecfb08a7e106d34d3276c0d Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Tue, 9 Feb 2021 09:26:49 +0100
Subject: [PATCH] LUKA: Drop some code for the luka client.

- Add the cli header lib for that client
- Drop in a clang-format file
- Basic luka.cpp
---
 .clang-format                           |  148 +++
 README.md                               |    5 +-
 inc/cli/.github/FUNDING.yml             |   12 +
 inc/cli/.github/workflows/ccpp.yml      |   28 +
 inc/cli/.gitignore                      |   69 ++
 inc/cli/LICENSE                         |   23 +
 inc/cli/boostasiocliasyncsession.h      |   40 +
 inc/cli/boostasioremotecli.h            |   39 +
 inc/cli/boostasioscheduler.h            |   38 +
 inc/cli/cli.h                           | 1092 +++++++++++++++++++++++
 inc/cli/clifilesession.h                |   83 ++
 inc/cli/clilocalsession.h               |   65 ++
 inc/cli/colorprofile.h                  |   76 ++
 inc/cli/detail/boostasiolib.h           |   49 +
 inc/cli/detail/commonprefix.h           |   70 ++
 inc/cli/detail/fromstring.h             |  281 ++++++
 inc/cli/detail/genericasioremotecli.h   |  576 ++++++++++++
 inc/cli/detail/genericasioscheduler.h   |   87 ++
 inc/cli/detail/genericcliasyncsession.h |  100 +++
 inc/cli/detail/history.h                |  164 ++++
 inc/cli/detail/inputdevice.h            |   72 ++
 inc/cli/detail/inputhandler.h           |  133 +++
 inc/cli/detail/keyboard.h               |   58 ++
 inc/cli/detail/linuxkeyboard.h          |  143 +++
 inc/cli/detail/newboostasiolib.h        |   83 ++
 inc/cli/detail/newstandaloneasiolib.h   |   82 ++
 inc/cli/detail/oldboostasiolib.h        |   77 ++
 inc/cli/detail/oldstandaloneasiolib.h   |   80 ++
 inc/cli/detail/rang.h                   |  298 +++++++
 inc/cli/detail/server.h                 |  159 ++++
 inc/cli/detail/split.h                  |  237 +++++
 inc/cli/detail/standaloneasiolib.h      |   49 +
 inc/cli/detail/terminal.h               |  213 +++++
 inc/cli/detail/winkeyboard.h            |  132 +++
 inc/cli/filehistorystorage.h            |   85 ++
 inc/cli/historystorage.h                |   54 ++
 inc/cli/loopscheduler.h                 |  103 +++
 inc/cli/scheduler.h                     |   55 ++
 inc/cli/standaloneasiocliasyncsession.h |   40 +
 inc/cli/standaloneasioremotecli.h       |   40 +
 inc/cli/standaloneasioscheduler.h       |   38 +
 inc/cli/volatilehistorystorage.h        |   68 ++
 src/Makefile.am                         |    6 +-
 src/Makefile.in                         |   27 +-
 src/main/luka.cpp                       |   33 +
 utils/licenses/CLI.LICENSE              |   28 +
 46 files changed, 5432 insertions(+), 6 deletions(-)
 create mode 100644 .clang-format
 create mode 100644 inc/cli/.github/FUNDING.yml
 create mode 100644 inc/cli/.github/workflows/ccpp.yml
 create mode 100644 inc/cli/.gitignore
 create mode 100644 inc/cli/LICENSE
 create mode 100644 inc/cli/boostasiocliasyncsession.h
 create mode 100644 inc/cli/boostasioremotecli.h
 create mode 100644 inc/cli/boostasioscheduler.h
 create mode 100644 inc/cli/cli.h
 create mode 100644 inc/cli/clifilesession.h
 create mode 100644 inc/cli/clilocalsession.h
 create mode 100644 inc/cli/colorprofile.h
 create mode 100644 inc/cli/detail/boostasiolib.h
 create mode 100644 inc/cli/detail/commonprefix.h
 create mode 100644 inc/cli/detail/fromstring.h
 create mode 100644 inc/cli/detail/genericasioremotecli.h
 create mode 100644 inc/cli/detail/genericasioscheduler.h
 create mode 100644 inc/cli/detail/genericcliasyncsession.h
 create mode 100644 inc/cli/detail/history.h
 create mode 100644 inc/cli/detail/inputdevice.h
 create mode 100644 inc/cli/detail/inputhandler.h
 create mode 100644 inc/cli/detail/keyboard.h
 create mode 100644 inc/cli/detail/linuxkeyboard.h
 create mode 100644 inc/cli/detail/newboostasiolib.h
 create mode 100644 inc/cli/detail/newstandaloneasiolib.h
 create mode 100644 inc/cli/detail/oldboostasiolib.h
 create mode 100644 inc/cli/detail/oldstandaloneasiolib.h
 create mode 100644 inc/cli/detail/rang.h
 create mode 100644 inc/cli/detail/server.h
 create mode 100644 inc/cli/detail/split.h
 create mode 100644 inc/cli/detail/standaloneasiolib.h
 create mode 100644 inc/cli/detail/terminal.h
 create mode 100644 inc/cli/detail/winkeyboard.h
 create mode 100644 inc/cli/filehistorystorage.h
 create mode 100644 inc/cli/historystorage.h
 create mode 100644 inc/cli/loopscheduler.h
 create mode 100644 inc/cli/scheduler.h
 create mode 100644 inc/cli/standaloneasiocliasyncsession.h
 create mode 100644 inc/cli/standaloneasioremotecli.h
 create mode 100644 inc/cli/standaloneasioscheduler.h
 create mode 100644 inc/cli/volatilehistorystorage.h
 create mode 100644 src/main/luka.cpp
 create mode 100644 utils/licenses/CLI.LICENSE

diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..f6164387
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,148 @@
+---
+Language:                       Cpp
+AccessModifierOffset:           -2
+AlignAfterOpenBracket:          Align
+AlignConsecutiveMacros:         false
+AlignConsecutiveAssignments:    false
+AlignConsecutiveBitFields:      false
+AlignConsecutiveDeclarations:   false
+AlignEscapedNewlines:           Right
+AlignOperands:                  Align
+AlignTrailingComments:          true
+AllowAllArgumentsOnNextLine:    true
+AllowAllConstructorInitializersOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortEnumsOnASingleLine:           true
+AllowShortBlocksOnASingleLine:          Never
+AllowShortCaseLabelsOnASingleLine:      false
+AllowShortFunctionsOnASingleLine:       All
+AllowShortLambdasOnASingleLine:         All
+AllowShortIfStatementsOnASingleLine:    Never
+AllowShortLoopsOnASingleLine:           false
+AlwaysBreakAfterDefinitionReturnType:   None
+AlwaysBreakAfterReturnType:             None
+AlwaysBreakBeforeMultilineStrings:      false
+AlwaysBreakTemplateDeclarations:        MultiLine
+BinPackArguments:   true
+BinPackParameters:  true
+BraceWrapping:
+  AfterCaseLabel:               false
+  AfterClass:                   false
+  AfterControlStatement:        Never
+  AfterEnum:                    false
+  AfterFunction:                false
+  AfterNamespace:               false
+  AfterObjCDeclaration:         false
+  AfterStruct:                  false
+  AfterUnion:                   false
+  AfterExternBlock:             false
+  BeforeCatch:                  false
+  BeforeElse:                   false
+  BeforeLambdaBody:             false
+  BeforeWhile:                  false
+  IndentBraces:                 false
+  SplitEmptyFunction:           true
+  SplitEmptyRecord:             true
+  SplitEmptyNamespace:          true
+BreakBeforeBinaryOperators:     None
+BreakBeforeBraces:              Attach
+BreakBeforeInheritanceComma:    false
+BreakInheritanceList:           BeforeColon
+BreakBeforeTernaryOperators:    true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers:   BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals:            true
+ColumnLimit:                    100
+CommentPragmas:                 '^ IWYU pragma:'
+CompactNamespaces:              trye
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth:        4
+Cpp11BracedListStyle:           true
+DeriveLineEnding:               true
+DerivePointerAlignment:         false
+DisableFormat:                  false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
+    Priority:        2
+    SortPriority:    0
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentCaseLabels:   false
+IndentCaseBlocks:   false
+IndentGotoLabels:   true
+IndentPPDirectives: None
+IndentExternBlock:  AfterExternBlock
+IndentWidth:        2
+IndentWrappedFunctionNames: false
+InsertTrailingCommas:    None
+JavaScriptQuotes:       Leave
+JavaScriptWrapImports:  true
+KeepEmptyLinesAtTheStartOfBlocks:       true
+MacroBlockBegin:                        ''
+MacroBlockEnd:                          ''
+MaxEmptyLinesToKeep:                    1
+NamespaceIndentation:                   None
+ObjCBinPackProtocolList:                Auto
+ObjCBlockIndentWidth:                   2
+ObjCBreakBeforeNestedBlockParam:        true
+ObjCSpaceAfterProperty:                 false
+ObjCSpaceBeforeProtocolList:            true
+PenaltyBreakAssignment:                 2
+PenaltyBreakBeforeFirstCallParameter:   19
+PenaltyBreakComment:                    300
+PenaltyBreakFirstLessLess:              120
+PenaltyBreakString:                     1000
+PenaltyBreakTemplateDeclaration:        10
+PenaltyExcessCharacter:                 1000000
+PenaltyReturnTypeOnItsOwnLine:          60
+PointerAlignment:                       Right
+ReflowComments:                         true
+SortIncludes:                           false
+SortUsingDeclarations:                  true
+SpaceAfterCStyleCast:                   false
+SpaceAfterLogicalNot:                   false
+SpaceAfterTemplateKeyword:              true
+SpaceBeforeAssignmentOperators:         true
+SpaceBeforeCpp11BracedList:             false
+SpaceBeforeCtorInitializerColon:        true
+SpaceBeforeInheritanceColon:            true
+SpaceBeforeParens:                      ControlStatements
+SpaceBeforeRangeBasedForLoopColon:      true
+SpaceInEmptyBlock:                      false
+SpaceInEmptyParentheses:                false
+SpacesBeforeTrailingComments:           1
+SpacesInAngles:                         false
+SpacesInConditionalStatement:           false
+SpacesInContainerLiterals:              true
+SpacesInCStyleCastParentheses:          false
+SpacesInParentheses:                    false
+SpacesInSquareBrackets:                 false
+SpaceBeforeSquareBrackets:              false
+Standard:                               Latest
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+...
+
diff --git a/README.md b/README.md
index f4cd73a4..9529448e 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,10 @@ Note that because of the use of `curl` and `get`, building behind a proxy may be
 
 If you are developping for lektor, you will need:
 
-- the [astyle](http://astyle.sourceforge.net/) command line utility, for a beautifull and uniform coding style.
+- the [astyle](http://astyle.sourceforge.net/) command line utility, for a beautifull and uniform
+  coding style for C code.
+- the [clang-format](https://releases.llvm.org/download.html) command line utility, for a
+  beautifull and uniform C++ code style.
 
 > If you are having problems build lektor (AC macros not defined), try copy the
 > `.m4` files from `/usr/share/aclocal` to `config/m4`.
diff --git a/inc/cli/.github/FUNDING.yml b/inc/cli/.github/FUNDING.yml
new file mode 100644
index 00000000..ffc06a19
--- /dev/null
+++ b/inc/cli/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: daniele77
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/inc/cli/.github/workflows/ccpp.yml b/inc/cli/.github/workflows/ccpp.yml
new file mode 100644
index 00000000..5d151e62
--- /dev/null
+++ b/inc/cli/.github/workflows/ccpp.yml
@@ -0,0 +1,28 @@
+name: C/C++ CI of Cli
+
+on: [push,pull_request]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    
+    steps:
+    - uses: actions/checkout@v2
+    - name: setup dependencies
+      run: |
+        sudo apt-get -y update
+        sudo apt-get -y install libboost-all-dev libasio-dev
+    - name: run cmake
+      run: |
+        mkdir build
+        cd build
+        cmake .. -DCLI_BuildTests=ON -DCLI_BuildExamples=ON -DCLI_UseBoostAsio=ON
+    - name: make
+      run: |
+        cd /home/runner/work/cli/cli/build
+        make all
+    - name: run tests
+      run: |
+        cd /home/runner/work/cli/cli/build/test
+        ./test_suite
diff --git a/inc/cli/.gitignore b/inc/cli/.gitignore
new file mode 100644
index 00000000..2da4bda0
--- /dev/null
+++ b/inc/cli/.gitignore
@@ -0,0 +1,69 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Cmake Files
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+build
+cmake-build-*
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+Debug
+Release
+test/test_suite
+asyncsession
+complete
+filesession
+pluginmanager
+simplelocalsession
+
+# Project files
+.cproject
+.project
+.vscode/
+.vs
+*.opensdf
+*.vcxproj.filters
+*.sdf
+*.user
+*.db
+.svn
+
+# Doxygen
+doc/doxy/html
+
+# Test files
+cli_test_history
\ No newline at end of file
diff --git a/inc/cli/LICENSE b/inc/cli/LICENSE
new file mode 100644
index 00000000..127a5bc3
--- /dev/null
+++ b/inc/cli/LICENSE
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/inc/cli/boostasiocliasyncsession.h b/inc/cli/boostasiocliasyncsession.h
new file mode 100644
index 00000000..e2dc607e
--- /dev/null
+++ b/inc/cli/boostasiocliasyncsession.h
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_BOOSTASIOCLIASYNCSESSION_H_
+#define CLI_BOOSTASIOCLIASYNCSESSION_H_
+
+#include "detail/genericcliasyncsession.h"
+#include "detail/boostasiolib.h"
+
+
+namespace cli { using BoostAsioCliAsyncSession = detail::GenericCliAsyncSession<detail::BoostAsioLib>; }
+
+#endif // CLI_BOOSTASIOCLIASYNCSESSION_H_
+
diff --git a/inc/cli/boostasioremotecli.h b/inc/cli/boostasioremotecli.h
new file mode 100644
index 00000000..f9d5acc6
--- /dev/null
+++ b/inc/cli/boostasioremotecli.h
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_BOOSTASIOREMOTECLI_H_
+#define CLI_BOOSTASIOREMOTECLI_H_
+
+#include "detail/genericasioremotecli.h"
+#include "detail/boostasiolib.h"
+
+namespace cli { using BoostAsioCliTelnetServer = detail::CliGenericTelnetServer<detail::BoostAsioLib>; }
+
+#endif // CLI_BOOSTASIOREMOTECLI_H_
+
diff --git a/inc/cli/boostasioscheduler.h b/inc/cli/boostasioscheduler.h
new file mode 100644
index 00000000..f4a872a5
--- /dev/null
+++ b/inc/cli/boostasioscheduler.h
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_BOOSTASIOSCHEDULER_H_
+#define CLI_BOOSTASIOSCHEDULER_H_
+
+#include "detail/genericasioscheduler.h"
+#include "detail/boostasiolib.h"
+
+namespace cli { using BoostAsioScheduler = detail::GenericAsioScheduler<detail::BoostAsioLib>; }
+
+#endif // CLI_BOOSTASIOSCHEDULER_H_
diff --git a/inc/cli/cli.h b/inc/cli/cli.h
new file mode 100644
index 00000000..49d78fc5
--- /dev/null
+++ b/inc/cli/cli.h
@@ -0,0 +1,1092 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_H_
+#define CLI_H_
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <memory>
+#include <functional>
+#include <algorithm>
+#include <cctype> // std::isspace
+#include <type_traits>
+#include "colorprofile.h"
+#include "detail/history.h"
+#include "detail/split.h"
+#include "detail/fromstring.h"
+#include "historystorage.h"
+#include "volatilehistorystorage.h"
+
+// #define CLI_DEPRECATED_API
+
+namespace cli
+{
+
+    // ********************************************************************
+
+    template < typename T > struct TypeDesc { static const char* Name() { return ""; } };
+    template <> struct TypeDesc< char > { static const char* Name() { return "<char>"; } };
+    template <> struct TypeDesc< unsigned char > { static const char* Name() { return "<unsigned char>"; } };
+    template <> struct TypeDesc< signed char > { static const char* Name() { return "<signed char>"; } };
+    template <> struct TypeDesc< short > { static const char* Name() { return "<short>"; } };
+    template <> struct TypeDesc< unsigned short > { static const char* Name() { return "<unsigned short>"; } };
+    template <> struct TypeDesc< int > { static const char* Name() { return "<int>"; } };
+    template <> struct TypeDesc< unsigned int > { static const char* Name() { return "<unsigned int>"; } };
+    template <> struct TypeDesc< long > { static const char* Name() { return "<long>"; } };
+    template <> struct TypeDesc< unsigned long > { static const char* Name() { return "<unsigned long>"; } };
+    template <> struct TypeDesc< long long > { static const char* Name() { return "<long long>"; } };
+    template <> struct TypeDesc< unsigned long long > { static const char* Name() { return "<unsigned long long>"; } };
+    template <> struct TypeDesc< float > { static const char* Name() { return "<float>"; } };
+    template <> struct TypeDesc< double > { static const char* Name() { return "<double>"; } };
+    template <> struct TypeDesc< long double > { static const char* Name() { return "<long double>"; } };
+    template <> struct TypeDesc< bool > { static const char* Name() { return "<bool>"; } };
+    template <> struct TypeDesc< std::string > { static const char* Name() { return "<string>"; } };
+
+    // ********************************************************************
+
+    // forward declarations
+    class Menu;
+    class CliSession;
+
+
+    class Cli
+    {
+
+        // inner class to provide a global output stream
+        class OutStream
+        {
+        public:
+            template <typename T>
+            OutStream& operator << (const T& msg)
+            {
+                for (auto out: ostreams)
+                    *out << msg;
+                return *this;
+            }
+
+            // this is the type of std::cout
+            typedef std::basic_ostream<char, std::char_traits<char> > CoutType;
+            // this is the function signature of std::endl
+            typedef CoutType& (*StandardEndLine)(CoutType&);
+
+            // takes << std::endl
+            OutStream& operator << (StandardEndLine manip)
+            {
+                for (auto out: ostreams)
+                    manip(*out);
+                return *this;
+            }
+
+        private:
+            friend class Cli;
+
+            void Register(std::ostream& o)
+            {
+                ostreams.push_back(&o);
+            }
+            void UnRegister(std::ostream& o)
+            {
+                ostreams.erase(std::remove(ostreams.begin(), ostreams.end(), &o), ostreams.end());
+            }
+
+            std::vector<std::ostream*> ostreams;
+        };
+        // end inner class
+
+    public:
+        Cli(
+            std::unique_ptr<Menu>&& _rootMenu,
+            std::function< void(std::ostream&)> _exitAction = {},
+            std::unique_ptr<HistoryStorage>&& historyStorage = std::make_unique<VolatileHistoryStorage>()
+        ) :
+            globalHistoryStorage(std::move(historyStorage)),
+            rootMenu(std::move(_rootMenu)),
+            exitAction(_exitAction)
+        {
+        }
+
+        Cli(std::unique_ptr<Menu> _rootMenu, std::unique_ptr<HistoryStorage> historyStorage) :
+            Cli(std::move(_rootMenu), {}, std::move(historyStorage))
+        {
+        }
+
+        // disable value semantics
+        Cli(const Cli&) = delete;
+        Cli& operator = (const Cli&) = delete;
+
+        Menu* RootMenu() { return rootMenu.get(); }
+        void ExitAction( const std::function< void(std::ostream&)>& action ) { exitAction = action; }
+        void ExitAction( std::ostream& out )
+        {
+            if ( exitAction )
+                exitAction( out );
+        }
+        void StdExceptionHandler(const std::function< void(std::ostream&, const std::string& cmd, const std::exception&) >& handler)
+        {
+            exceptionHandler = handler;
+        }
+        void StdExceptionHandler(std::ostream& out, const std::string& cmd, const std::exception& e)
+        {
+            if (exceptionHandler)
+                exceptionHandler(out, cmd, e);
+            else
+                out << e.what() << '\n';
+        }
+
+        static void Register(std::ostream& o) { cout().Register(o); }
+        static void UnRegister(std::ostream& o) { cout().UnRegister(o); }
+
+        static OutStream& cout()
+        {
+            static OutStream s;
+            return s;
+        }
+
+        void StoreCommands(const std::vector<std::string>& cmds)
+        {
+            globalHistoryStorage->Store(cmds);
+        }
+
+        std::vector<std::string> GetCommands() const
+        {
+            return globalHistoryStorage->Commands();
+        }
+
+    private:
+        std::unique_ptr<HistoryStorage> globalHistoryStorage;
+        std::unique_ptr<Menu> rootMenu; // just to keep it alive
+        std::function<void(std::ostream&)> exitAction;
+        std::function<void(std::ostream&, const std::string& cmd, const std::exception& )> exceptionHandler;
+    };
+
+    // ********************************************************************
+
+    class Command
+    {
+    public:
+        explicit Command(const std::string& _name) : name(_name), enabled(true) {}
+        virtual ~Command() = default;
+        virtual void Enable() { enabled = true; }
+        virtual void Disable() { enabled = false; }
+        virtual bool Exec(const std::vector<std::string>& cmdLine, CliSession& session) = 0;
+        virtual void Help(std::ostream& out) const = 0;
+        // Returns the collection of completions relatives to this command.
+        // For simple commands, provides a base implementation that use the name of the command
+        // for aggregate commands (i.e., Menu), the function is redefined to give the menu command
+        // and the subcommand recursively
+        virtual std::vector<std::string> GetCompletionRecursive(const std::string& line) const
+        {
+            if (!enabled) return {};
+            if (name.rfind(line, 0) == 0) return {name}; // name starts_with line
+            else return {};
+        }
+    protected:
+        const std::string& Name() const { return name; }
+        bool IsEnabled() const { return enabled; }
+    private:
+        const std::string name;
+        bool enabled;
+    };
+
+    // ********************************************************************
+
+    // free utility function to get completions from a list of commands and the current line
+    inline std::vector<std::string> GetCompletions(
+        const std::shared_ptr<std::vector<std::shared_ptr<Command>>>& cmds,
+        const std::string& currentLine)
+    {
+        std::vector<std::string> result;
+        std::for_each(cmds->begin(), cmds->end(),
+            [&currentLine,&result](const auto& cmd)
+            {
+                auto c = cmd->GetCompletionRecursive(currentLine);
+                result.insert(
+                    result.end(),
+                    std::make_move_iterator(c.begin()),
+                    std::make_move_iterator(c.end())
+                );
+            }
+        );
+        return result;
+    }
+
+    // ********************************************************************
+
+    class CliSession
+    {
+    public:
+        CliSession(Cli& _cli, std::ostream& _out, std::size_t historySize = 100);
+        virtual ~CliSession() { cli.UnRegister(out); }
+
+        // disable value semantics
+        CliSession(const CliSession&) = delete;
+        CliSession& operator = (const CliSession&) = delete;
+
+        void Feed( const std::string& cmd );
+
+        void Prompt();
+
+        void Current(Menu* menu) { current = menu; }
+
+        std::ostream& OutStream() { return out; }
+
+        void Help() const;
+
+        void Exit()
+        {
+            exitAction(out);
+            cli.ExitAction(out);
+
+            auto cmds = history.GetCommands();
+            cli.StoreCommands(cmds);
+        }
+
+        void ExitAction(const std::function<void(std::ostream&)>& action)
+        {
+            exitAction = action;
+        }
+
+        void ShowHistory() const { history.Show(out); }
+
+        std::string PreviousCmd(const std::string& line)
+        {
+            return history.Previous(line);
+        }
+
+        std::string NextCmd()
+        {
+            return history.Next();
+        }
+
+        std::vector<std::string> GetCompletions(std::string currentLine) const;
+
+    private:
+
+        Cli& cli;
+        Menu* current;
+        std::unique_ptr<Menu> globalScopeMenu;
+        std::ostream& out;
+        std::function< void(std::ostream&)> exitAction = []( std::ostream& ){};
+        detail::History history;
+    };
+
+    // ********************************************************************
+
+    class CmdHandler
+    {
+    public:
+        using CmdVec = std::vector<std::shared_ptr<Command>>;
+        CmdHandler() : descriptor(std::make_shared<Descriptor>()) {}
+        CmdHandler(std::weak_ptr<Command> c, std::weak_ptr<CmdVec> v) :
+            descriptor(std::make_shared<Descriptor>(c, v))
+        {}
+        void Enable() { if (descriptor) descriptor->Enable(); }
+        void Disable() { if (descriptor) descriptor->Disable(); }
+        void Remove() { if (descriptor) descriptor->Remove(); }
+    private:
+        struct Descriptor
+        {
+            Descriptor() {}
+            Descriptor(const std::weak_ptr<Command>& c, const std::weak_ptr<CmdVec>& v) :
+                cmd(c), cmds(v)
+            {}
+            void Enable()
+            {
+                if (auto c = cmd.lock())
+                    c->Enable();
+            }
+            void Disable()
+            {
+                if(auto c = cmd.lock())
+                    c->Disable();
+            }
+            void Remove()
+            {
+                auto scmd = cmd.lock();
+                auto scmds = cmds.lock();
+                if (scmd && scmds)
+                {
+                    auto i = std::find_if(
+                        scmds->begin(),
+                        scmds->end(),
+                        [&](const auto& c){ return c.get() == scmd.get(); }
+                    );
+                    if (i != scmds->end())
+                        scmds->erase(i);
+                }
+            }
+            std::weak_ptr<Command> cmd;
+            std::weak_ptr<CmdVec> cmds;
+        };
+        std::shared_ptr<Descriptor> descriptor;
+    };
+
+    // ********************************************************************
+
+    class Menu : public Command
+    {
+    public:
+        // disable value semantics
+        Menu(const Menu&) = delete;
+        Menu& operator = (const Menu&) = delete;
+
+        Menu() : Command({}), parent(nullptr), description(), cmds(std::make_shared<Cmds>()) {}
+
+        Menu(const std::string& _name, const std::string& desc = "(menu)") :
+            Command(_name), parent(nullptr), description(desc), cmds(std::make_shared<Cmds>())
+        {}
+
+        template <typename F>
+        CmdHandler Insert(const std::string& cmdName, F f, const std::string& help = "", const std::vector<std::string>& parDesc={})
+        {
+            // dispatch to private Insert methods
+            return Insert(cmdName, help, parDesc, f, &F::operator());
+        }
+
+        template <typename F>
+        CmdHandler Insert(const std::string& cmdName, const std::vector<std::string>& parDesc, F f, const std::string& help = "")
+        {
+            // dispatch to private Insert methods
+            return Insert(cmdName, help, parDesc, f, &F::operator());
+        }
+
+#ifdef CLI_DEPRECATED_API
+        template <typename F>
+        [[deprecated("Use the method Insert instead")]]
+        void Add(const std::string& cmdName, F f, const std::string& help = "")
+        {
+            // dispatch to private Add methods
+            Add(cmdName, help, f, &F::operator());
+        }
+
+        [[deprecated("Use the method Insert instead")]]
+        void Add(std::unique_ptr<Command>&& cmd)
+        {
+            std::shared_ptr<Command> s(std::move(cmd));
+            cmds->push_back(s);
+        }
+
+        [[deprecated("Use the method Insert instead")]]
+        void Add(std::unique_ptr<Menu>&& menu)
+        {
+            std::shared_ptr<Menu> s(std::move(menu));
+            s->parent = this;
+            cmds->push_back(s);
+        }
+#endif // CLI_DEPRECATED_API
+
+        CmdHandler Insert(std::unique_ptr<Command>&& cmd)
+        {
+            std::shared_ptr<Command> scmd(std::move(cmd));
+            CmdHandler c(scmd, cmds);
+            cmds->push_back(scmd);
+            return c;
+        }
+
+        CmdHandler Insert(std::unique_ptr<Menu>&& menu)
+        {
+            std::shared_ptr<Menu> smenu(std::move(menu));
+            CmdHandler c(smenu, cmds);
+            smenu->parent = this;
+            cmds->push_back(smenu);
+            return c;
+        }
+
+        bool Exec(const std::vector<std::string>& cmdLine, CliSession& session) override
+        {
+            if (!IsEnabled())
+                return false;
+            if (cmdLine[0] == Name())
+            {
+                if (cmdLine.size() == 1)
+                {
+                    session.Current(this);
+                    return true;
+                }
+                else
+                {
+                    // check also for subcommands
+                    std::vector<std::string > subCmdLine(cmdLine.begin()+1, cmdLine.end());
+                    for (auto& cmd: *cmds)
+                        if (cmd->Exec( subCmdLine, session )) return true;
+                }
+            }
+            return false;
+        }
+
+        bool ScanCmds(const std::vector<std::string>& cmdLine, CliSession& session)
+        {
+            if (!IsEnabled()) return false;
+            for (auto& cmd: *cmds)
+                if (cmd->Exec(cmdLine, session)) return true;
+            if (parent && parent->Exec(cmdLine, session)) return true;
+            return false;
+        }
+
+        std::string Prompt() const
+        {
+            return Name();
+        }
+
+        void MainHelp(std::ostream& out)
+        {
+            if (!IsEnabled()) return;
+            for (const auto& cmd: *cmds)
+                cmd->Help(out);
+            if (parent) parent->Help(out);
+        }
+
+        void Help(std::ostream& out) const override
+        {
+            if (!IsEnabled()) return;
+            out << " - " << Name() << "\n\t" << description << "\n";
+        }
+
+        // returns:
+        // - the completions of this menu command
+        // - the recursive completions of subcommands
+        // - the recursive completions of parent menu
+        std::vector<std::string> GetCompletions(const std::string& currentLine) const
+        {
+            auto result = cli::GetCompletions(cmds, currentLine);
+            if (parent)
+            {
+                auto c = parent->GetCompletionRecursive(currentLine);
+                result.insert(result.end(), std::make_move_iterator(c.begin()), std::make_move_iterator(c.end()));
+            }
+            return result;
+        }
+
+        // returns:
+        // - the completion of this menu command
+        // - the recursive completions of the subcommands
+        virtual std::vector<std::string> GetCompletionRecursive(const std::string& line) const override
+        {
+            if (line.rfind(Name(), 0) == 0) // line starts_with Name()
+            {
+                auto rest = line;
+                rest.erase(0, Name().size());
+                // trim_left(rest);
+                rest.erase(rest.begin(), std::find_if(rest.begin(), rest.end(), [](int ch) { return !std::isspace(ch); }));
+                std::vector<std::string> result;
+                for (const auto& cmd: *cmds)
+                {
+                    auto cs = cmd->GetCompletionRecursive(rest);
+                    for (const auto& c: cs)
+                        result.push_back(Name() + ' ' + c); // concat submenu with command
+                }
+                return result;
+            }
+            return Command::GetCompletionRecursive(line);
+        }
+
+    private:
+
+#ifdef CLI_DEPRECATED_API
+        template <typename F, typename R>
+        void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(std::ostream& out) const);
+
+        template <typename F, typename R, typename A1>
+        void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, std::ostream& out) const);
+
+        template <typename F, typename R, typename A1, typename A2>
+        void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, std::ostream& out) const);
+
+        template <typename F, typename R, typename A1, typename A2, typename A3>
+        void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, A3, std::ostream& out) const);
+
+        template <typename F, typename R, typename A1, typename A2, typename A3, typename A4>
+        void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, A3, A4, std::ostream& out) const);
+#endif // CLI_DEPRECATED_API
+
+        template <typename F, typename R, typename ... Args>
+        CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, Args...) const);
+
+        template <typename F, typename R>
+        CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, const std::vector<std::string>&) const);
+
+        template <typename F, typename R>
+        CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, std::vector<std::string>) const);
+
+        Menu* parent;
+        const std::string description;
+        // using shared_ptr instead of unique_ptr to get a weak_ptr
+        // for the CmdHandler::Descriptor
+        using Cmds = std::vector<std::shared_ptr<Command>>;
+        std::shared_ptr<Cmds> cmds;
+    };
+
+    // ********************************************************************
+
+#ifdef CLI_DEPRECATED_API
+
+    class FuncCmd : public Command
+    {
+    public:
+        // disable value semantics
+        FuncCmd( const FuncCmd& ) = delete;
+        FuncCmd& operator = ( const FuncCmd& ) = delete;
+
+        FuncCmd(
+            const std::string& _name,
+            std::function< void( std::ostream& )> _function,
+            const std::string& desc = ""
+        ) : Command( _name ), function( _function ), description( desc )
+        {
+        }
+        bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override
+        {
+            if ( cmdLine.size() != 1 ) return false;
+            if ( cmdLine[ 0 ] == Name() )
+            {
+                function( session.OutStream() );
+                return true;
+            }
+
+            return false;
+        }
+        void Help( std::ostream& out ) const override
+        {
+            out << " - " << Name() << "\n\t" << description << "\n";
+        }
+    private:
+        const std::function< void( std::ostream& ) > function;
+        const std::string description;
+    };
+
+    template < typename T >
+    class FuncCmd1 : public Command
+    {
+    public:
+        // disable value semantics
+        FuncCmd1( const FuncCmd1& ) = delete;
+        FuncCmd1& operator = ( const FuncCmd1& ) = delete;
+
+        FuncCmd1(
+            const std::string& _name,
+            std::function< void( T, std::ostream& ) > _function,
+            const std::string& desc = ""
+            ) : Command( _name ), function( _function ), description( desc )
+        {
+        }
+        bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override
+        {
+            if ( cmdLine.size() != 2 ) return false;
+            if ( Name() == cmdLine[ 0 ] )
+            {
+                try
+                {
+                    T arg = detail::from_string<T>( cmdLine[ 1 ] );
+                    function( arg, session.OutStream() );
+                }
+                catch (std::bad_cast&)
+                {
+                    return false;
+                }
+                return true;
+            }
+
+            return false;
+        }
+        void Help( std::ostream& out ) const override
+        {
+            out << " - " << Name()
+                << " " << TypeDesc< T >::Name()
+                << "\n\t" << description << "\n";
+        }
+    private:
+        const std::function< void( T, std::ostream& )> function;
+        const std::string description;
+    };
+
+    template < typename T1, typename T2 >
+    class FuncCmd2 : public Command
+    {
+    public:
+        // disable value semantics
+        FuncCmd2( const FuncCmd2& ) = delete;
+        FuncCmd2& operator = ( const FuncCmd2& ) = delete;
+
+        FuncCmd2(
+            const std::string& _name,
+            std::function< void( T1, T2, std::ostream& ) > _function,
+            const std::string& desc = "2 parameter command"
+            ) : Command( _name ), function( _function ), description( desc )
+        {
+        }
+        bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override
+        {
+            if ( cmdLine.size() != 3 ) return false;
+            if ( Name() == cmdLine[ 0 ] )
+            {
+                try
+                {
+                    T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] );
+                    T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] );
+                    function( arg1, arg2, session.OutStream() );
+                }
+                catch (std::bad_cast&)
+                {
+                    return false;
+                }
+                return true;
+            }
+
+            return false;
+        }
+        void Help( std::ostream& out ) const override
+        {
+            out << " - " << Name()
+                << " " << TypeDesc< T1 >::Name()
+                << " " << TypeDesc< T2 >::Name()
+                << "\n\t" << description << "\n";
+        }
+    private:
+        const std::function< void( T1, T2, std::ostream& )> function;
+        const std::string description;
+    };
+
+    template < typename T1, typename T2, typename T3 >
+    class FuncCmd3 : public Command
+    {
+    public:
+        // disable value semantics
+        FuncCmd3( const FuncCmd3& ) = delete;
+        FuncCmd3& operator = ( const FuncCmd3& ) = delete;
+
+        FuncCmd3(
+            const std::string& _name,
+            std::function< void( T1, T2, T3, std::ostream& ) > _function,
+            const std::string& desc = "3 parameters command"
+            ) : Command( _name ), function( _function ), description( desc )
+        {
+        }
+        bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override
+        {
+            if ( cmdLine.size() != 4 ) return false;
+            if ( Name() == cmdLine[ 0 ] )
+            {
+                try
+                {
+                    T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] );
+                    T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] );
+                    T3 arg3 = detail::from_string<T3>( cmdLine[ 3 ] );
+                    function( arg1, arg2, arg3, session.OutStream() );
+                }
+                catch (std::bad_cast&)
+                {
+                    return false;
+                }
+                return true;
+            }
+
+            return false;
+        }
+        void Help( std::ostream& out ) const override
+        {
+            out << " - " << Name()
+                << " " << TypeDesc< T1 >::Name()
+                << " " << TypeDesc< T2 >::Name()
+                << " " << TypeDesc< T3 >::Name()
+                << "\n\t" << description << "\n";
+        }
+    private:
+        const std::function< void( T1, T2, T3, std::ostream& )> function;
+        const std::string description;
+    };
+
+    template < typename T1, typename T2, typename T3, typename T4 >
+    class FuncCmd4 : public Command
+    {
+    public:
+        // disable value semantics
+        FuncCmd4( const FuncCmd4& ) = delete;
+        FuncCmd4& operator = ( const FuncCmd4& ) = delete;
+
+        FuncCmd4(
+            const std::string& _name,
+            std::function< void( T1, T2, T3, T4, std::ostream& ) > _function,
+            const std::string& desc = "4 parameters command"
+            ) : Command( _name ), function( _function ), description( desc )
+        {
+        }
+        bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override
+        {
+            if ( cmdLine.size() != 5 ) return false;
+            if ( Name() == cmdLine[ 0 ] )
+            {
+                try
+                {
+                    T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] );
+                    T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] );
+                    T3 arg3 = detail::from_string<T3>( cmdLine[ 3 ] );
+                    T4 arg4 = detail::from_string<T4>( cmdLine[ 4 ] );
+                    function( arg1, arg2, arg3, arg4, session.OutStream() );
+                }
+                catch (std::bad_cast&)
+                {
+                    return false;
+                }
+                return true;
+            }
+
+            return false;
+        }
+        void Help( std::ostream& out ) const override
+        {
+            out << " - " << Name()
+                << " " << TypeDesc< T1 >::Name()
+                << " " << TypeDesc< T2 >::Name()
+                << " " << TypeDesc< T3 >::Name()
+                << " " << TypeDesc< T4 >::Name()
+                << "\n\t" << description << "\n";
+        }
+    private:
+        const std::function< void( T1, T2, T3, T4, std::ostream& )> function;
+        const std::string description;
+    };
+
+#endif // CLI_DEPRECATED_API
+
+    // *******************************************
+
+    template <typename F, typename ... Args>
+    struct Select;
+
+    template <typename F, typename P, typename ... Args>
+    struct Select<F, P, Args...>
+    {
+        template <typename InputIt>
+        static void Exec(const F& f, InputIt first, InputIt last)
+        {
+            assert( first != last );
+            assert( std::distance(first, last) == 1+sizeof...(Args) );
+            const P p = detail::from_string<typename std::decay<P>::type>(*first);
+            auto g = [&](auto ... pars){ f(p, pars...); };
+            Select<decltype(g), Args...>::Exec(g, std::next(first), last);
+        }
+    };
+
+    template <typename F>
+    struct Select<F>
+    {
+        template <typename InputIt>
+        static void Exec(const F& f, InputIt first, InputIt last)
+        {
+            // silence the unused warning in release mode when assert is disabled
+            static_cast<void>(first);
+            static_cast<void>(last);
+
+            assert(first == last);
+            
+            f();
+        }
+    };
+
+    template <typename ... Args>
+    struct PrintDesc;
+
+    template <typename P, typename ... Args>
+    struct PrintDesc<P, Args...>
+    {
+        static void Dump(std::ostream& out)
+        {
+            out << " " << TypeDesc< typename std::decay<P>::type >::Name();
+            PrintDesc<Args...>::Dump(out);
+        }
+    };
+
+    template <>
+    struct PrintDesc<>
+    {
+        static void Dump(std::ostream& /*out*/) {}
+    };
+
+    // *******************************************
+
+    template <typename F, typename ... Args>
+    class VariadicFunctionCommand : public Command
+    {
+    public:
+        // disable value semantics
+        VariadicFunctionCommand(const VariadicFunctionCommand&) = delete;
+        VariadicFunctionCommand& operator = (const VariadicFunctionCommand&) = delete;
+
+        VariadicFunctionCommand(
+            const std::string& _name,
+            F fun,
+            const std::string& desc,
+            const std::vector<std::string>& parDesc
+        )
+            : Command(_name), func(std::move(fun)), description(desc), parameterDesc(parDesc)
+        {
+        }
+
+        bool Exec(const std::vector< std::string >& cmdLine, CliSession& session) override
+        {
+            if (!IsEnabled()) return false;
+            const std::size_t paramSize = sizeof...(Args);
+            if (cmdLine.size() != paramSize+1) return false;
+            if (Name() == cmdLine[0])
+            {
+                try
+                {
+                    auto g = [&](auto ... pars){ func( session.OutStream(), pars... ); };
+                    Select<decltype(g), Args...>::Exec(g, std::next(cmdLine.begin()), cmdLine.end());
+                }
+                catch (std::bad_cast&)
+                {
+                    return false;
+                }
+                return true;
+            }
+            return false;
+        }
+
+        void Help(std::ostream& out) const override
+        {
+            if (!IsEnabled()) return;
+            out << " - " << Name();
+            if (parameterDesc.empty())
+                PrintDesc<Args...>::Dump(out);
+            for (auto& s: parameterDesc)
+                out << " <" << s << '>';
+            out << "\n\t" << description << "\n";
+        }
+
+    private:
+
+        const F func;
+        const std::string description;
+        const std::vector<std::string> parameterDesc;
+    };
+
+
+    template <typename F>
+    class FreeformCommand : public Command
+    {
+    public:
+        // disable value semantics
+        FreeformCommand(const FreeformCommand&) = delete;
+        FreeformCommand& operator = (const FreeformCommand&) = delete;
+
+        FreeformCommand(
+            const std::string& _name,
+            F fun,
+            const std::string& desc = "unknown command",
+            const std::vector<std::string>& parDesc = {}
+        )
+            : Command(_name), func(std::move(fun)), description(desc), parameterDesc(parDesc)
+        {
+        }
+
+        bool Exec(const std::vector< std::string >& cmdLine, CliSession& session) override
+        {
+            if (!IsEnabled()) return false;
+            assert(!cmdLine.empty());
+            if (Name() == cmdLine[0])
+            {
+                func(session.OutStream(), std::vector<std::string>(std::next(cmdLine.begin()), cmdLine.end()));
+                return true;
+            }
+            return false;
+        }
+        void Help(std::ostream& out) const override
+        {
+            if (!IsEnabled()) return;
+            out << " - " << Name();
+            for (auto& s: parameterDesc)
+                out << " <" << s << '>';
+            out << "\n\t" << description << "\n";
+        }
+
+    private:
+
+        const F func;
+        const std::string description;
+        const std::vector<std::string> parameterDesc;
+    };
+
+
+    // ********************************************************************
+
+    // CliSession implementation
+
+    inline CliSession::CliSession(Cli& _cli, std::ostream& _out, std::size_t historySize) :
+            cli(_cli),
+            current(cli.RootMenu()),
+            globalScopeMenu(std::make_unique< Menu >()),
+            out(_out),
+            history(historySize)
+        {
+            history.LoadCommands(cli.GetCommands());
+
+            cli.Register(out);
+            globalScopeMenu->Insert(
+                "help",
+                [this](std::ostream&){ Help(); },
+                "This help message"
+            );
+            globalScopeMenu->Insert(
+                "exit",
+                [this](std::ostream&){ Exit(); },
+                "Quit the session"
+            );
+#ifdef CLI_HISTORY_CMD
+            globalScopeMenu->Insert(
+                "history",
+                [this](std::ostream&){ ShowHistory(); },
+                "Show the history"
+            );
+#endif
+        }
+
+    inline void CliSession::Feed(const std::string& cmd)
+    {
+        std::vector<std::string> strs;
+        detail::split(strs, cmd);
+        if (strs.empty()) return; // just hit enter
+
+        history.NewCommand(cmd); // add anyway to history
+
+        try
+        {
+
+            // global cmds check
+            bool found = globalScopeMenu->ScanCmds(strs, *this);
+
+            // root menu recursive cmds check
+            if (!found) found = current->ScanCmds(std::move(strs), *this); // last use of strs
+
+            if (!found) // error msg if not found
+                out << "wrong command: " << cmd << '\n';
+        }
+        catch(const std::exception& e)
+        {
+            cli.StdExceptionHandler(out, cmd, e);
+        }
+        catch(...)
+        {
+            out << "Cli. Unknown exception caught handling command line \""
+                << cmd
+                << "\"\n";
+        }
+
+        return;
+    }
+
+    inline void CliSession::Prompt()
+    {
+        out << beforePrompt
+            << current->Prompt()
+            << afterPrompt
+            << "> "
+            << std::flush;
+    }
+
+    inline void CliSession::Help() const
+    {
+        out << "Commands available:\n";
+        globalScopeMenu->MainHelp(out);
+        current -> MainHelp( out );
+    }
+
+    inline std::vector<std::string> CliSession::GetCompletions(std::string currentLine) const
+    {
+        // trim_left(currentLine);
+        currentLine.erase(currentLine.begin(), std::find_if(currentLine.begin(), currentLine.end(), [](int ch) { return !std::isspace(ch); }));
+        auto v1 = globalScopeMenu->GetCompletions(currentLine);
+        auto v3 = current->GetCompletions(currentLine);
+        v1.insert(v1.end(), std::make_move_iterator(v3.begin()), std::make_move_iterator(v3.end()));
+
+        // removes duplicates (std::unique requires a sorted container)
+        std::sort(v1.begin(), v1.end());
+        auto ip = std::unique(v1.begin(), v1.end());
+        v1.resize(static_cast<std::size_t>(std::distance(v1.begin(), ip)));
+
+        return v1;
+    }
+
+    // Menu implementation
+
+#ifdef CLI_DEPRECATED_API
+    template < typename F, typename R >
+    void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(std::ostream& out) const )
+    {
+        cmds->push_back(std::make_shared<FuncCmd>(name, f, help));
+    }
+
+    template < typename F, typename R, typename A1 >
+    void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, std::ostream& out) const )
+    {
+        cmds->push_back(std::make_shared<FuncCmd1<A1>>(name, f, help));
+    }
+
+    template < typename F, typename R, typename A1, typename A2 >
+    void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, std::ostream& out) const )
+    {
+        cmds->push_back(std::make_shared<FuncCmd2<A1, A2>>(name, f, help));
+    }
+
+    template < typename F, typename R, typename A1, typename A2, typename A3 >
+    void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, A3, std::ostream& out) const )
+    {
+        cmds->push_back(std::make_shared<FuncCmd3<A1, A2, A3>>(name, f, help));
+    }
+
+    template < typename F, typename R, typename A1, typename A2, typename A3, typename A4 >
+    void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, A3, A4, std::ostream& out) const )
+    {
+        cmds->push_back(std::make_shared<FuncCmd4<A1, A2, A3, A4>>(name, f, help));
+    }
+#endif // CLI_DEPRECATED_API
+
+    template <typename F, typename R, typename ... Args>
+    CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, Args...) const )
+    {
+        return Insert(std::make_unique<VariadicFunctionCommand<F, Args ...>>(cmdName, f, help, parDesc));
+    }
+
+    template <typename F, typename R>
+    CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, const std::vector<std::string>& args) const )
+    {
+        return Insert(std::make_unique<FreeformCommand<F>>(cmdName, f, help, parDesc));
+    }
+
+    template <typename F, typename R>
+    CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, std::vector<std::string> args) const )
+    {
+        return Insert(std::make_unique<FreeformCommand<F>>(cmdName, f, help, parDesc));
+    }
+
+} // namespace
+
+#endif
diff --git a/inc/cli/clifilesession.h b/inc/cli/clifilesession.h
new file mode 100644
index 00000000..7896c681
--- /dev/null
+++ b/inc/cli/clifilesession.h
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_FILESESSION_H_
+#define CLI_FILESESSION_H_
+
+#include <string>
+#include <iostream>
+#include <stdexcept> // std::invalid_argument
+#include "cli.h" // CliSession
+
+namespace cli
+{
+
+class CliFileSession : public CliSession
+{
+public:
+    /// @throw std::invalid_argument if @c _in or @c out are invalid streams
+    CliFileSession(Cli& _cli, std::istream& _in=std::cin, std::ostream& _out=std::cout) :
+        CliSession(_cli, _out, 1),
+        exit(false),
+        in(_in)
+    {
+        if (!_in.good()) throw std::invalid_argument("istream invalid");
+        if (!_out.good()) throw std::invalid_argument("ostream invalid");
+        ExitAction(
+            [this](std::ostream&)
+            {
+                exit = true;
+            }
+        );
+    }
+    void Start()
+    {
+        while(!exit)
+        {
+            Prompt();
+            std::string line;
+            if (!in.good())
+                Exit();
+            std::getline(in, line);
+            if (in.eof())
+                Exit();
+            else
+                Feed(line);
+        }
+    }
+
+private:
+    bool exit;
+    std::istream& in;
+};
+
+} // namespace
+
+#endif // CLI_FILESESSION_H_
+
diff --git a/inc/cli/clilocalsession.h b/inc/cli/clilocalsession.h
new file mode 100644
index 00000000..feec7f99
--- /dev/null
+++ b/inc/cli/clilocalsession.h
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_LOCALSESSION_H_
+#define CLI_LOCALSESSION_H_
+
+#include "detail/keyboard.h"
+#include "detail/inputhandler.h"
+#include "cli.h" // CliSession
+
+namespace cli
+{
+
+class Scheduler; // forward declaration
+
+
+class CliLocalTerminalSession : public CliSession
+{
+public:
+
+    CliLocalTerminalSession(Cli& _cli, Scheduler& scheduler, std::ostream& _out, std::size_t historySize = 100) :
+        CliSession(_cli, _out, historySize),
+        kb(scheduler),
+        ih(*this, kb)
+    {
+        Prompt();
+    }
+
+private:
+    detail::Keyboard kb;
+    detail::InputHandler ih;
+};
+
+using CliLocalSession = CliLocalTerminalSession;
+
+} // namespace cli
+
+#endif // CLI_LOCALSESSION_H_
+
diff --git a/inc/cli/colorprofile.h b/inc/cli/colorprofile.h
new file mode 100644
index 00000000..0d9714bd
--- /dev/null
+++ b/inc/cli/colorprofile.h
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_COLORPROFILE_H_
+#define CLI_COLORPROFILE_H_
+
+#include "detail/rang.h"
+
+namespace cli
+{
+
+inline bool& Color() { static bool color; return color; }
+
+inline void SetColor() { Color() = true; }
+inline void SetNoColor() { Color() = false; }
+
+enum BeforePrompt { beforePrompt };
+enum AfterPrompt { afterPrompt };
+enum BeforeInput { beforeInput };
+enum AfterInput { afterInput };
+
+inline std::ostream& operator<<(std::ostream& os, BeforePrompt)
+{
+    if ( Color() ) { os << rang::control::forceColor << rang::fg::green << rang::style::bold; }
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, AfterPrompt)
+{
+    os << rang::style::reset;
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, BeforeInput)
+{
+    if ( Color() ) { os << rang::control::forceColor << rang::fgB::gray; }
+    return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os, AfterInput)
+{
+    os << rang::style::reset;
+    return os;
+}
+
+} // namespace cli
+
+#endif // CLI_COLORPROFILE_H_
+
+
diff --git a/inc/cli/detail/boostasiolib.h b/inc/cli/detail/boostasiolib.h
new file mode 100644
index 00000000..a82c53cd
--- /dev/null
+++ b/inc/cli/detail/boostasiolib.h
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_BOOSTASIOLIB_H_
+#define CLI_DETAIL_BOOSTASIOLIB_H_
+
+/**
+ * This header file provides the class `cli::BoostAsioLib`, using the right
+ * implementation according to the version of boost libraries included.
+ */
+
+#include <boost/version.hpp>
+
+#if BOOST_VERSION < 106600
+    #include "oldboostasiolib.h"
+    namespace cli { namespace detail { using BoostAsioLib = OldBoostAsioLib; } }
+#else
+    #include "newboostasiolib.h"
+    namespace cli { namespace detail { using BoostAsioLib = NewBoostAsioLib; } }
+#endif
+
+#endif // CLI_DETAIL_BOOSTASIOLIB_H_
+
diff --git a/inc/cli/detail/commonprefix.h b/inc/cli/detail/commonprefix.h
new file mode 100644
index 00000000..626e089c
--- /dev/null
+++ b/inc/cli/detail/commonprefix.h
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_COMMONPREFIX_H_
+#define CLI_DETAIL_COMMONPREFIX_H_
+
+#include <cassert>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+namespace cli
+{
+namespace detail
+{
+
+inline std::string CommonPrefix(const std::vector<std::string>& v)
+{
+    assert(!v.empty());
+    std::string prefix;
+
+    // find the shorter string
+    auto smin = std::min_element(v.begin(), v.end(),
+                [] (const std::string& s1, const std::string& s2)
+                {
+                    return s1.size() < s2.size();
+                });
+
+    for (std::size_t i = 0; i < smin->size(); ++i)
+    {
+        // check if i-th element is equal in each input string
+        const char c = (*smin)[i];
+        for (auto& x: v)
+            if (x[i] != c) return prefix;
+        prefix += c;
+    }
+
+    return prefix;
+}
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_COMMONPREFIX_H_
diff --git a/inc/cli/detail/fromstring.h b/inc/cli/detail/fromstring.h
new file mode 100644
index 00000000..822c7de0
--- /dev/null
+++ b/inc/cli/detail/fromstring.h
@@ -0,0 +1,281 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_FROMSTRING_H_
+#define CLI_DETAIL_FROMSTRING_H_
+
+// #define CLI_FROMSTRING_USE_BOOST
+
+#ifdef CLI_FROMSTRING_USE_BOOST
+
+#include <boost/lexical_cast.hpp>
+
+namespace cli
+{
+namespace detail
+{
+
+template <typename T>
+inline
+T from_string(const std::string& s)
+{
+    return boost::lexical_cast<T>(s);
+}
+
+} // namespace detail
+} // namespace cli
+
+#else
+
+#include <exception>
+#include <string>
+#include <sstream>
+
+namespace cli
+{
+
+    namespace detail
+    {
+        class bad_conversion : public std::bad_cast
+        {
+            public:
+                virtual const char* what() const noexcept {
+                    return "bad from_string conversion: "
+                        "source string value could not be interpreted as target";
+                }
+        };
+
+template <typename T>
+inline T from_string(const std::string& s);
+
+template <>
+inline std::string from_string(const std::string& s)
+{
+    return s;
+}
+
+template <>
+inline std::nullptr_t from_string(const std::string& /*s*/)
+{
+    return nullptr;
+}
+
+namespace detail
+{
+
+template <typename T>
+inline T unsigned_digits_from_string(const std::string& s)
+{
+    if (s.empty())
+        throw bad_conversion();
+    T result = 0;
+    for (char c: s)
+    {
+        if (!std::isdigit(c))
+            throw bad_conversion();
+        const T digit = static_cast<T>( c - '0' );
+        const T tmp = (result * 10) + digit;
+        if (result != ((tmp-digit)/10) || (tmp < result))
+            throw bad_conversion();
+        result = tmp;
+    }
+    return result;
+}
+
+template <typename T>
+inline T unsigned_from_string(std::string s)
+{
+    if (s.empty())
+        throw bad_conversion();
+    if (s[0] == '+')
+    {
+        s = s.substr(1);
+    }
+    return unsigned_digits_from_string<T>(s);
+}
+
+template <typename T>
+inline T signed_from_string(std::string s)
+{
+    if (s.empty())
+        throw bad_conversion();
+    using U = std::make_unsigned_t<T>;
+    if (s[0] == '-')
+    {
+        s = s.substr(1);
+        const U val = unsigned_digits_from_string<U>(s);
+        if ( val > static_cast<U>( - std::numeric_limits<T>::min() ) )
+            throw bad_conversion();
+        return (- static_cast<T>(val));
+    }
+    else if (s[0] == '+')
+    {
+        s = s.substr(1);
+    }
+    const U val = unsigned_digits_from_string<U>(s);
+    if (val > static_cast<U>( std::numeric_limits<T>::max() ))
+        throw bad_conversion();
+    return static_cast<T>(val);
+}
+
+} // detail
+
+// signed
+
+template <> inline signed char 
+from_string(const std::string& s) { return detail::signed_from_string<signed char>(s); }
+
+template <> inline short int 
+from_string(const std::string& s) { return detail::signed_from_string<short int>(s); }
+
+template <> inline int
+from_string(const std::string& s) { return detail::signed_from_string<int>(s); }
+
+template <> inline long int
+from_string(const std::string& s) { return detail::signed_from_string<long int>(s); }
+
+template <> inline long long int
+from_string(const std::string& s) { return detail::signed_from_string<long long int>(s); }
+
+// unsigned
+
+template <> inline unsigned char
+from_string(const std::string& s) { return detail::unsigned_from_string<unsigned char>(s); }
+
+template <> inline unsigned short int
+from_string(const std::string& s) { return detail::unsigned_from_string<unsigned short int>(s); }
+
+template <> inline unsigned int
+from_string(const std::string& s) { return detail::unsigned_from_string<unsigned int>(s); }
+
+template <> inline unsigned long int
+from_string(const std::string& s) { return detail::unsigned_from_string<unsigned long int>(s); }
+
+template <> inline unsigned long long int
+from_string(const std::string& s) { return detail::unsigned_from_string<unsigned long long int>(s); }
+
+// bool
+
+template <>
+inline bool from_string(const std::string& s)
+{
+    if (s == "true") return true;
+    else if (s == "false") return false;
+    const auto value = detail::signed_from_string<long long int>(s);
+    if (value == 1) return true;
+    else if (value == 0) return false;
+    throw bad_conversion();            
+}
+
+// chars
+
+template <>
+inline char from_string(const std::string& s)
+{
+    if (s.size() != 1) throw bad_conversion();
+    return s[0];            
+}
+
+// floating points
+
+template <>
+inline float from_string(const std::string& s)
+{
+    if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) )
+        throw bad_conversion();
+    std::string::size_type sz;
+    float result = {};
+    try {
+        result = std::stof(s, &sz);
+    } catch (const std::exception&) {
+        throw bad_conversion();
+    }
+    if (sz != s.size())
+        throw bad_conversion();
+    return result;
+}
+
+template <>
+inline double from_string(const std::string& s)
+{
+    if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) )
+        throw bad_conversion();
+    std::string::size_type sz;
+    double result = {};
+    try {
+        result = std::stod(s, &sz);
+    } catch (const std::exception&) {
+        throw bad_conversion();
+    }
+    if (sz != s.size())
+        throw bad_conversion();
+    return result;
+}
+
+template <>
+inline long double from_string(const std::string& s)
+{
+    if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) )
+        throw bad_conversion();
+    std::string::size_type sz;
+    long double result = {};
+    try {
+        result = std::stold(s, &sz);
+    } catch (const std::exception&) {
+        throw bad_conversion();
+    }
+    if (sz != s.size())
+        throw bad_conversion();
+    return result;
+}
+
+// fallback: operator <<
+
+template <typename T>
+inline T from_string(const std::string& s)
+{
+    std::stringstream interpreter;
+    T result;
+
+    if(!(interpreter << s) ||
+        !(interpreter >> result) ||
+        !(interpreter >> std::ws).eof())
+        throw bad_conversion();
+
+    return result;
+}
+
+    } // detail
+
+} // cli
+
+
+#endif // CLI_FROMSTRING_USE_BOOST
+
+#endif // CLI_DETAIL_FROMSTRING_H_
diff --git a/inc/cli/detail/genericasioremotecli.h b/inc/cli/detail/genericasioremotecli.h
new file mode 100644
index 00000000..603bb44a
--- /dev/null
+++ b/inc/cli/detail/genericasioremotecli.h
@@ -0,0 +1,576 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_GENERICASIOREMOTECLI_H_
+#define CLI_DETAIL_GENERICASIOREMOTECLI_H_
+
+#include <memory>
+#include "../cli.h"
+#include "inputhandler.h"
+#include "server.h"
+#include "inputdevice.h"
+
+namespace cli
+{
+namespace detail
+{
+
+// *******************************************************************************
+
+class TelnetSession : public Session
+{
+public:
+    TelnetSession(asiolib::ip::tcp::socket _socket) :
+        Session(std::move(_socket))
+    {}
+
+protected:
+
+    virtual std::string Encode(const std::string& _data) const override
+    {
+        std::string result;
+        for (char c: _data)
+        {
+            if (c == '\n') result += '\r';
+            result += c;
+        }
+        return result;
+    }
+
+    virtual void OnConnect() override
+    {
+        // to specify hexadecimal value as chars we use
+        // the syntax \xVVV
+        // and the std::string ctor that takes the size,
+        // so that it's not null-terminated
+        //std::string msg{ "\x0FF\x0FD\x027", 3 };
+        //waitAck = true;
+        //std::string iacDoSuppressGoAhead{ "\x0FF\x0FD\x003", 3 };
+        //this -> OutStream() << iacDoSuppressGoAhead << std::flush;
+
+        // https://www.ibm.com/support/knowledgecenter/SSLTBW_1.13.0/com.ibm.zos.r13.hald001/telcmds.htm
+
+        std::string iacDoLineMode{ "\x0FF\x0FD\x022", 3 };
+        this -> OutStream() << iacDoLineMode << std::flush;
+
+        std::string iacSbLineMode0IacSe{ "\x0FF\x0FA\x022\x001\x000\x0FF\x0F0", 7 };
+        this -> OutStream() << iacSbLineMode0IacSe << std::flush;
+
+        std::string iacWillEcho{ "\x0FF\x0FB\x001", 3 };
+        this -> OutStream() << iacWillEcho << std::flush;
+/*
+        constexpr char IAC = '\x0FF'; // 255
+        constexpr char DO = '\x0FD'; // 253
+        constexpr char VT100 = '\x030'; // 48
+
+        this -> OutStream() << IAC << DO << VT100 << std::flush;
+*/
+        //cliSession.Prompt();
+    }
+    virtual void OnDisconnect() override {}
+    virtual void OnError() override {}
+#if 0
+    virtual void OnDataReceived(const std::string& data) override
+    {
+        if (waitAck)
+        {
+            if ( data[0] == '\x0FF' )
+            {
+                // TODO
+                for (size_t i = 0; i < data.size(); ++i)
+                    std::cout << static_cast<int>( data[i] & 0xFF ) << ' ';
+                std::cout << std::endl;
+
+                for (size_t i = 0; i < data.size(); ++i)
+                    std::cout << "0x" << std::hex << static_cast<int>( data[i] & 0xFF ) << std::dec << ' ';
+                std::cout << std::endl;
+
+                for (size_t i = 0; i < data.size(); ++i)
+                    switch (static_cast<int>( data[i] & 0xFF ))
+                    {
+                        case 0xFF: std::cout << "IAC "; break;
+                        case 0xFE: std::cout << "DONT "; break;
+                        case 0xFD: std::cout << "DO "; break;
+                        case 0xFC: std::cout << "WONT "; break;
+                        case 0xFB: std::cout << "WILL "; break;
+                        case 0xFA: std::cout << "SB "; break;
+                        case 0xF9: std::cout << "GoAhead "; break;
+                        case 0xF8: std::cout << "EraseLine "; break;
+                        case 0xF7: std::cout << "EraseCharacter "; break;
+                        case 0xF6: std::cout << "AreYouThere "; break;
+                        case 0xF5: std::cout << "AbortOutput "; break;
+                        case 0xF4: std::cout << "InterruptProcess "; break;
+                        case 0xF3: std::cout << "Break "; break;
+                        case 0xF2: std::cout << "DataMark "; break;
+                        case 0xF1: std::cout << "NOP "; break;
+                        case 0xF0: std::cout << "SE "; break;
+                        default: std::cout << (static_cast<int>( data[i] & 0xFF )) << ' ';
+                    }
+                std::cout << std::endl;
+
+            }
+            waitAck = false;
+/*
+            std::string iacWillSuppressGoAhead{ "\x0FF\x0FB\x003", 3 };
+            if ( data == iacWillSuppressGoAhead )
+            {
+                waitAck = false;
+                cliSession.Prompt();
+            }
+
+            else
+                Disconnect();
+*/
+        }
+        else
+        {
+            for (size_t i = 0; i < data.size(); ++i)
+                Feed(data[i]);
+/*
+            auto str = data;
+            // trim trailing spaces
+            std::size_t endpos = str.find_last_not_of(" \t\r\n");
+            if( std::string::npos != endpos ) str = str.substr( 0, endpos+1 );
+
+            if ( cliSession.Feed( str ) ) cliSession.Prompt();
+            else Disconnect();
+*/
+        }
+    }
+#else
+
+    enum
+    {
+        SE = '\x0F0',                  // End of subnegotiation parameters.
+        NOP = '\x0F1',                 // No operation.
+        DataMark = '\x0F2',            // The data stream portion of a Synch.
+                                       // This should always be accompanied
+                                       // by a TCP Urgent notification.
+        Break = '\x0F3',               // NVT character BRK.
+        InterruptProcess = '\x0F4',    // The function IP.
+        AbortOutput = '\x0F5',         // The function AO.
+        AreYouThere = '\x0F6',         // The function AYT.
+        EraseCharacter = '\x0F7',      // The function EC.
+        EraseLine = '\x0F8',           // The function EL.
+        GoAhead = '\x0F9',             // The GA signal.
+        SB = '\x0FA',                  // Indicates that what follows is
+                                       // subnegotiation of the indicated
+                                       // option.
+        WILL = '\x0FB',                // Indicates the desire to begin
+                                       // performing, or confirmation that
+                                       // you are now performing, the
+                                       // indicated option.
+        WONT = '\x0FC',                // Indicates the refusal to perform,
+                                       // or continue performing, the
+                                       // indicated option.
+        DO = '\x0FD',                  // Indicates the request that the
+                                       // other party perform, or
+                                       // confirmation that you are expecting
+                                       // the other party to perform, the
+                                       // indicated option.
+        DONT = '\x0FE',                // Indicates the demand that the
+                                       // other party stop performing,
+                                       // or confirmation that you are no
+                                       // longer expecting the other party
+                                       // to perform, the indicated option.
+        IAC = '\x0FF'                  // Data Byte 255.
+    };
+
+
+    virtual void OnDataReceived(const std::string& _data) override
+    {
+        for (char c: _data)
+            Consume(c);
+    }
+
+private:
+
+    void Consume(char c)
+    {
+        if (escape)
+        {
+            if (c == IAC)
+            {
+                Data(c);
+                escape = false;
+            }
+            else
+            {
+                Command(c);
+                escape = false;
+            }
+        }
+        else
+        {
+            if (c == IAC)
+                escape = true;
+            else
+                Data(c);
+        }
+    }
+
+    void Data(char c)
+    {
+        switch(state)
+        {
+            case State::data:
+                Output(c);
+                break;
+            case State::sub:
+                Sub(c);
+                break;
+            case State::wait_will:
+                Will(c);
+                state = State::data;
+                break;
+            case State::wait_wont:
+                Wont(c);
+                state = State::data;
+                break;
+            case State::wait_do:
+                Do(c);
+                state = State::data;
+                break;
+            case State::wait_dont:
+                Dont(c);
+                state = State::data;
+                break;
+        }
+    }
+
+    void Command(char c)
+    {
+/*
+        switch (static_cast<int>( c & 0xFF ))
+        {
+            case 0xFF: std::cout << "IAC" << std::endl; break;
+            case 0xFE: std::cout << "DONT" << std::endl; break;
+            case 0xFD: std::cout << "DO" << std::endl; break;
+            case 0xFC: std::cout << "WONT" << std::endl; break;
+            case 0xFB: std::cout << "WILL" << std::endl; break;
+            case 0xFA: std::cout << "SB" << std::endl; break;
+            case 0xF9: std::cout << "GoAhead" << std::endl; break;
+            case 0xF8: std::cout << "EraseLine" << std::endl; break;
+            case 0xF7: std::cout << "EraseCharacter" << std::endl; break;
+            case 0xF6: std::cout << "AreYouThere" << std::endl; break;
+            case 0xF5: std::cout << "AbortOutput" << std::endl; break;
+            case 0xF4: std::cout << "InterruptProcess" << std::endl; break;
+            case 0xF3: std::cout << "Break" << std::endl; break;
+            case 0xF2: std::cout << "DataMark" << std::endl; break;
+            case 0xF1: std::cout << "NOP" << std::endl; break;
+            case 0xF0: std::cout << "SE" << std::endl; break;
+            default: std::cout << (static_cast<int>( c & 0xFF )) << std::endl;;
+        }
+*/
+        switch(c)
+        {
+            case SE:
+                if (state == State::sub)
+                    state = State::data;
+                else
+                    std::cout << "ERROR: received SE when not in sub state" << std::endl;
+                break;
+            case DataMark: // ?
+            case Break: // ?
+            case InterruptProcess:
+            case AbortOutput:
+            case AreYouThere:
+            case EraseCharacter:
+            case EraseLine:
+            case GoAhead:
+            case NOP:
+                state = State::data;
+                break;
+            case SB:
+                if (state != State::sub)
+                    state = State::sub;
+                else
+                    std::cout << "ERROR: received SB when already in sub state" << std::endl;
+                break;
+            case WILL:
+                state = State::wait_will;
+                break;
+            case WONT:
+                state = State::wait_wont;
+                break;
+            case DO:
+                state = State::wait_do;
+                break;
+            case DONT:
+                state = State::wait_dont;
+                break;
+            case IAC:
+                assert(false); // can't be here
+                state = State::data;
+                break;
+        }
+    }
+
+    void Will(char c) 
+    { 
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "will " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+    void Wont(char c)
+    { 
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "wont " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+    void Do(char c)
+    { 
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "do " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+    void Dont(char c) 
+    {
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "dont " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+    void Sub(char c) 
+    { 
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "sub: " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+protected:
+    virtual void Output(char c)
+    {
+        #ifdef CLI_TELNET_TRACE
+        std::cout << "data: " << static_cast<int>(c) << std::endl;
+        #else
+        (void)c;
+        #endif
+    }
+private:
+    enum class State { data, sub, wait_will, wait_wont, wait_do, wait_dont };
+    State state = State::data;
+    bool escape = false;
+
+#endif
+
+private:
+    void Feed(char c)
+    {
+        if (std::isprint(c)) std::cout << c << std::endl;
+        else std::cout << "0x" << std::hex << static_cast<int>(c) << std::dec << std::endl;
+/*
+        switch ( c )
+        {
+        case 0: break;
+        case '\n':
+        case '\r':
+        {
+            // trim trailing spaces
+            std::size_t endpos = buffer.find_last_not_of(" \t\r\n");
+            if( std::string::npos != endpos ) buffer = buffer.substr( 0, endpos+1 );
+            if ( cliSession.Feed( buffer ) ) cliSession.Prompt();
+            else Disconnect();
+
+            buffer.clear();
+            break;
+        }
+        default:
+            Echo( c );
+            buffer += c;
+        }
+*/
+    }
+/*
+    void Echo( char c )
+    {
+        this -> OutStream() << c << std::flush;
+    }
+*/
+    std::string buffer;
+    //bool waitAck = false;
+};
+
+template <typename ASIOLIB>
+class TelnetServer : public Server<ASIOLIB>
+{
+public:
+    TelnetServer(typename ASIOLIB::ContextType& ios, unsigned short port) :
+        Server<ASIOLIB>(ios, port)
+    {}
+    virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket _socket) override
+    {
+        return std::make_shared<TelnetSession>(std::move(_socket));
+    }
+};
+
+//////////////
+
+class CliTelnetSession : public InputDevice, public TelnetSession, public CliSession
+{
+public:
+
+    CliTelnetSession(Scheduler& _scheduler, asiolib::ip::tcp::socket _socket, Cli& _cli, std::function< void(std::ostream&)> _exitAction, std::size_t historySize ) :
+        InputDevice(_scheduler),
+        TelnetSession(std::move(_socket)),
+        CliSession(_cli, TelnetSession::OutStream(), historySize),
+        poll(*this, *this)
+    {
+        ExitAction([this, _exitAction](std::ostream& _out){ _exitAction(_out), Disconnect(); } );
+    }
+protected:
+
+    virtual void OnConnect() override
+    {
+        TelnetSession::OnConnect();
+        Prompt();
+    }
+
+    void Output(char c) override
+    {
+        switch(step)
+        {
+            case Step::_1:
+                switch( c )
+                {
+                    case EOF:
+                    case 4:  // EOT
+                        Notify(std::make_pair(KeyType::eof,' ')); break;
+                    case 8: // Backspace
+                    case 127:  // Backspace or Delete
+                        Notify(std::make_pair(KeyType::backspace, ' ')); break;
+                    //case 10: Notify(std::make_pair(KeyType::ret,' ')); break;
+                    case 27: step = Step::_2; break;  // symbol
+                    case 13: step = Step::wait_0; break;  // wait for 0 (ENTER key)
+                    default: // ascii
+                    {
+                        const char ch = static_cast<char>(c);
+                        Notify(std::make_pair(KeyType::ascii,ch));
+                    }
+                }
+                break;
+
+            case Step::_2: // got 27 last time
+                if ( c == 91 )
+                {
+                    step = Step::_3;
+                    break;  // arrow keys
+                }
+                else
+                {
+                    step = Step::_1;
+                    Notify(std::make_pair(KeyType::ignored,' '));
+                    break; // unknown
+                }
+                break;
+
+            case Step::_3: // got 27 and 91
+                switch( c )
+                {
+                    case 65: step = Step::_1; Notify(std::make_pair(KeyType::up,' ')); break;
+                    case 66: step = Step::_1; Notify(std::make_pair(KeyType::down,' ')); break;
+                    case 68: step = Step::_1; Notify(std::make_pair(KeyType::left,' ')); break;
+                    case 67: step = Step::_1; Notify(std::make_pair(KeyType::right,' ')); break;
+                    case 70: step = Step::_1; Notify(std::make_pair(KeyType::end,' ')); break;
+                    case 72: step = Step::_1; Notify(std::make_pair(KeyType::home,' ')); break;
+                    default: step = Step::_4; break;  // not arrow keys
+                }
+                break;
+
+            case Step::_4:
+                if ( c == 126 ) Notify(std::make_pair(KeyType::canc,' '));
+                else Notify(std::make_pair(KeyType::ignored,' '));
+
+                step = Step::_1;
+
+                break;
+
+            case Step::wait_0:
+                if ( c == 0 /* linux */ || c == 10 /* win */ ) Notify(std::make_pair(KeyType::ret,' '));
+                else Notify(std::make_pair(KeyType::ignored,' '));
+
+                step = Step::_1;
+
+                break;
+
+        }
+    }
+
+private:
+
+    enum class Step { _1, _2, _3, _4, wait_0 };
+    Step step = Step::_1;
+    InputHandler poll;
+};
+
+template <typename ASIOLIB>
+class CliGenericTelnetServer : public Server<ASIOLIB>
+{
+public:
+    CliGenericTelnetServer(GenericAsioScheduler<ASIOLIB>& _scheduler, unsigned short port, Cli& _cli, std::size_t _historySize=100 ) :
+        Server<ASIOLIB>(_scheduler.AsioContext(), port),
+        scheduler(_scheduler),
+        cli(_cli),
+        historySize(_historySize)
+    {}
+    CliGenericTelnetServer(GenericAsioScheduler<ASIOLIB>& _scheduler, std::string address, unsigned short port, Cli& _cli, std::size_t _historySize=100 ) :
+        Server<ASIOLIB>(_scheduler.AsioContext(), address, port),
+        scheduler(_scheduler),
+        cli(_cli),
+        historySize(_historySize)
+    {}
+    void ExitAction( std::function< void(std::ostream&)> action )
+    {
+        exitAction = action;
+    }
+    virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket _socket) override
+    {
+        return std::make_shared<CliTelnetSession>(scheduler, std::move(_socket), cli, exitAction, historySize);
+    }
+private:
+    Scheduler& scheduler;
+    Cli& cli;
+    std::function< void(std::ostream&)> exitAction;
+    std::size_t historySize;
+};
+
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_GENERICASIOREMOTECLI_H_
+
diff --git a/inc/cli/detail/genericasioscheduler.h b/inc/cli/detail/genericasioscheduler.h
new file mode 100644
index 00000000..8b579612
--- /dev/null
+++ b/inc/cli/detail/genericasioscheduler.h
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_GENERICASIOSCHEDULER_H_
+#define CLI_DETAIL_GENERICASIOSCHEDULER_H_
+
+#include "../scheduler.h"
+
+namespace cli
+{
+namespace detail
+{
+
+template <typename ASIOLIB>
+class GenericAsioScheduler : public Scheduler
+{
+public:
+
+    using ContextType = typename ASIOLIB::ContextType;
+
+    GenericAsioScheduler() : owned{true}, context{new ContextType()}, executor{*context} {}
+
+    GenericAsioScheduler(ContextType& _context) : context{&_context}, executor{*context} {}
+
+    ~GenericAsioScheduler() { if (owned) delete context; }
+
+    // non copyable
+    GenericAsioScheduler(const GenericAsioScheduler&) = delete;
+    GenericAsioScheduler& operator=(const GenericAsioScheduler&) = delete;
+
+    void Stop() { context->stop(); }
+
+    void Run()
+    {
+        auto work = ASIOLIB::MakeWorkGuard(*context);
+        context->run();
+    }
+
+    void ExecOne() { context->run_one(); }
+
+    void Post(const std::function<void()>& f) override
+    {
+        executor.Post(f);
+    }
+
+    ContextType& AsioContext() { return *context; }
+
+private:
+
+    using ExecutorType = typename ASIOLIB::Executor;
+
+    bool owned = false;
+    ContextType* context;
+    ExecutorType executor;
+};
+
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_GENERICASIOSCHEDULER_H_
diff --git a/inc/cli/detail/genericcliasyncsession.h b/inc/cli/detail/genericcliasyncsession.h
new file mode 100644
index 00000000..3da81273
--- /dev/null
+++ b/inc/cli/detail/genericcliasyncsession.h
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_GENERICCLIASYNCSESSION_H_
+#define CLI_DETAIL_GENERICCLIASYNCSESSION_H_
+
+#include <string>
+#include "../cli.h" // CliSession
+#include "genericasioscheduler.h"
+
+namespace cli
+{
+namespace detail
+{
+
+template <typename ASIOLIB>
+class GenericCliAsyncSession : public CliSession
+{
+public:
+    GenericCliAsyncSession(GenericAsioScheduler<ASIOLIB>& _scheduler, Cli& _cli) :
+        CliSession(_cli, std::cout, 1),
+        input(_scheduler.AsioContext(), ::dup(STDIN_FILENO))
+    {
+        Read();
+    }
+    ~GenericCliAsyncSession()
+    {
+        input.close();
+    }
+
+private:
+
+    void Read()
+    {
+        Prompt();
+        // Read a line of input entered by the user.
+        asiolib::async_read_until(
+            input,
+            inputBuffer,
+            '\n',
+            std::bind( &GenericCliAsyncSession::NewLine, this,
+                       std::placeholders::_1,
+                       std::placeholders::_2 )
+        );
+    }
+
+    void NewLine(const asiolibec::error_code& error, std::size_t length )
+    {
+        if ( !error || error == asiolib::error::not_found )
+        {
+            auto bufs = inputBuffer.data();
+            auto size = static_cast<long>(length);
+            if ( !error ) --size; // remove \n
+            std::string s(asiolib::buffers_begin( bufs ), asiolib::buffers_begin( bufs ) + size);
+            inputBuffer.consume( length );
+
+            Feed( s );
+            Read();
+        }
+        else
+        {
+            input.close();
+        }
+    }
+
+    asiolib::streambuf inputBuffer;
+    asiolib::posix::stream_descriptor input;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_GENERICCLIASYNCSESSION_H_
+
diff --git a/inc/cli/detail/history.h b/inc/cli/detail/history.h
new file mode 100644
index 00000000..d10c7d1a
--- /dev/null
+++ b/inc/cli/detail/history.h
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_HISTORY_H_
+#define CLI_DETAIL_HISTORY_H_
+
+#include <deque>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <cassert>
+
+namespace cli
+{
+namespace detail
+{
+
+class History
+{
+public:
+
+    explicit History(std::size_t size) : maxSize(size) {}
+
+    // Insert a new item in the buffer, changing the current state to "inserting"
+    // If we're browsing the history (eg with arrow keys) the new item overwrites
+    // the current one.
+    // Otherwise, the item is added to the front of the container
+    void NewCommand(const std::string& item)
+    {
+        ++commands;
+        current = 0;
+        if (mode == Mode::browsing)
+        {
+            assert(!buffer.empty());
+            if (buffer.size() > 1 && buffer[1] == item) // try to insert an element identical to last one
+                buffer.pop_front();
+            else // the item was not identical
+                buffer[current] = item;
+        }
+        else // Mode::inserting
+        {
+            if (buffer.empty() || buffer[0] != item) // insert an element not equal to last one
+                Insert(item);
+        }
+        mode = Mode::inserting;
+    }
+
+    // Return the previous item of the history, updating the current item and
+    // changing the current state to "browsing"
+    // If we're already browsing the history (eg with arrow keys) the edit line is inserted
+    // to the front of the container.
+    // Otherwise, the line overwrites the current item.
+    std::string Previous(const std::string& line)
+    {
+        if (mode == Mode::inserting)
+        {
+            Insert(line);
+            mode = Mode::browsing;
+            current = (buffer.size() > 1) ? 1 : 0;
+        }
+        else // Mode::browsing
+        {
+            assert(!buffer.empty());
+            buffer[current] = line;
+            if (current != buffer.size()-1)
+                ++current;
+        }
+        assert(mode == Mode::browsing);
+        assert(current < buffer.size());
+        return buffer[current];
+    }
+
+    // Return the next item of the history, updating the current item.
+    std::string Next()
+    {
+        if (buffer.empty() || current == 0)
+            return {};
+        assert(current != 0);
+        --current;
+        assert(current < buffer.size());
+        return buffer[current];
+    }
+
+    // Show the whole history on the given ostream
+    void Show(std::ostream& out) const
+    {
+        out << '\n';
+        for (auto& item: buffer)
+            out << item << '\n';
+        out << '\n' << std::flush;
+    }
+
+    // cmds[0] is the oldest command, cmds[size-1] the newer
+    void LoadCommands(const std::vector<std::string>& cmds)
+    {
+        for (const auto& c: cmds)
+            Insert(c);
+    }
+
+    // result[0] is the oldest command, result[size-1] the newer
+    std::vector<std::string> GetCommands() const
+    {
+        auto numCmdsToReturn = std::min(commands, buffer.size());
+        auto start = buffer.begin();
+        if (mode == Mode::browsing)
+        {
+            numCmdsToReturn = std::min(commands, buffer.size()-1);
+            start = buffer.begin()+1;
+        }
+        std::vector<std::string> result(numCmdsToReturn);
+        assert(std::distance(start, buffer.end()) >= 0);
+        assert(numCmdsToReturn <= static_cast<std::size_t>(std::distance(buffer.end(), start)));
+        assert(numCmdsToReturn <= static_cast<unsigned long>(std::numeric_limits<long>::max()));
+        std::reverse_copy(start, start+static_cast<long>(numCmdsToReturn), result.begin());
+        return result;
+    }
+
+private:
+
+    void Insert(const std::string& item)
+    {
+        buffer.push_front(item);
+        if (buffer.size() > maxSize)
+            buffer.pop_back();
+    }
+
+    const std::size_t maxSize;
+    std::deque<std::string> buffer;
+    std::size_t current = 0;
+    std::size_t commands = 0; // number of commands issued
+    enum class Mode { inserting, browsing };
+    Mode mode = Mode::inserting;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_HISTORY_H_
diff --git a/inc/cli/detail/inputdevice.h b/inc/cli/detail/inputdevice.h
new file mode 100644
index 00000000..313d79e3
--- /dev/null
+++ b/inc/cli/detail/inputdevice.h
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_INPUTDEVICE_H_
+#define CLI_DETAIL_INPUTDEVICE_H_
+
+#include <functional>
+#include <string>
+#include "../scheduler.h"
+
+namespace cli
+{
+namespace detail
+{
+
+enum class KeyType { ascii, up, down, left, right, backspace, canc, home, end, ret, eof, ignored };
+
+class InputDevice
+{
+public:
+    using Handler = std::function< void( std::pair<KeyType,char> ) >;
+
+    InputDevice(Scheduler& _scheduler) : scheduler(_scheduler) {}
+    virtual ~InputDevice() = default;
+
+    template <typename H>
+    void Register(H&& h) { handler = std::forward<H>(h); }
+
+protected:
+
+    void Notify(std::pair<KeyType,char> k)
+    {
+        scheduler.Post([this,k](){ if (handler) handler(k); });
+    }
+
+private:
+
+    Scheduler& scheduler;
+    Handler handler;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_INPUTDEVICE_H_
+
diff --git a/inc/cli/detail/inputhandler.h b/inc/cli/detail/inputhandler.h
new file mode 100644
index 00000000..d86ab3d9
--- /dev/null
+++ b/inc/cli/detail/inputhandler.h
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_INPUTHANDLER_H_
+#define CLI_DETAIL_INPUTHANDLER_H_
+
+#include <functional>
+#include <string>
+#include "terminal.h"
+#include "inputdevice.h"
+#include "../cli.h" // CliSession
+#include "commonprefix.h"
+
+namespace cli
+{
+namespace detail
+{
+
+class InputHandler
+{
+public:
+    InputHandler(CliSession& _session, InputDevice& kb) :
+        session(_session),
+        terminal(session.OutStream())
+    {
+        kb.Register( [this](auto key){ this->Keypressed(key); } );
+    }
+
+private:
+
+    void Keypressed(std::pair<KeyType, char> k)
+    {
+        const std::pair<Symbol,std::string> s = terminal.Keypressed(k);
+        NewCommand(s);
+    }
+
+    void NewCommand(const std::pair< Symbol, std::string >& s)
+    {
+        switch (s.first)
+        {
+            case Symbol::nothing:
+            {
+                break;
+            }
+            case Symbol::eof:
+            {
+                session.Exit();
+                break;
+            }
+            case Symbol::command:
+            {
+                session.Feed(s.second);
+                session.Prompt();
+                break;
+            }
+            case Symbol::down:
+            {
+                terminal.SetLine(session.NextCmd());
+                break;
+            }
+            case Symbol::up:
+            {
+                auto line = terminal.GetLine();
+                terminal.SetLine(session.PreviousCmd(line));
+                break;
+            }
+            case Symbol::tab:
+            {
+                auto line = terminal.GetLine();
+                auto completions = session.GetCompletions(line);
+
+                if (completions.empty())
+                    break;
+                if (completions.size() == 1)
+                {
+                    terminal.SetLine(completions[0]+' ');
+                    break;
+                }
+
+                auto commonPrefix = CommonPrefix(completions);
+                if (commonPrefix.size() > line.size())
+                {
+                    terminal.SetLine(commonPrefix);
+                    break;
+                }
+                session.OutStream() << '\n';
+                std::string items;
+                std::for_each( completions.begin(), completions.end(), [&items](auto& cmd){ items += '\t' + cmd; } );
+                session.OutStream() << items << '\n';
+                session.Prompt();
+                terminal.ResetCursor();
+                terminal.SetLine( line );
+                break;
+            }
+        }
+
+    }
+
+    CliSession& session;
+    Terminal terminal;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_INPUTHANDLER_H_
+
diff --git a/inc/cli/detail/keyboard.h b/inc/cli/detail/keyboard.h
new file mode 100644
index 00000000..e300d25a
--- /dev/null
+++ b/inc/cli/detail/keyboard.h
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_KEYBOARD_H_
+#define CLI_DETAIL_KEYBOARD_H_
+
+#if defined(__unix__) || defined(__unix) || defined(__linux__)
+    #define OS_LINUX
+#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
+    #define OS_WIN
+#elif defined(__APPLE__) || defined(__MACH__)
+    #define OS_MAC
+#else
+    #error "Platform not supported (yet)."
+#endif
+
+#if defined(OS_LINUX) || defined(OS_MAC)
+    #include "linuxkeyboard.h"
+    namespace cli { namespace detail { using Keyboard = LinuxKeyboard; } }
+#elif defined(OS_WIN)
+    #include "winkeyboard.h"
+    namespace cli { namespace detail { using Keyboard = WinKeyboard; } }
+#else
+    #error "Platform not supported (yet)."
+#endif
+
+#undef OS_LINUX
+#undef OS_WIN
+#undef OS_MAC
+
+#endif // CLI_DETAIL_KEYBOARD_H_
+
diff --git a/inc/cli/detail/linuxkeyboard.h b/inc/cli/detail/linuxkeyboard.h
new file mode 100644
index 00000000..6dc7743f
--- /dev/null
+++ b/inc/cli/detail/linuxkeyboard.h
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_LINUXKEYBOARD_H_
+#define CLI_DETAIL_LINUXKEYBOARD_H_
+
+#include <thread>
+#include <memory>
+#include <atomic>
+
+#include <cstdio>
+#include <termios.h>
+#include <unistd.h>
+
+#include "inputdevice.h"
+
+
+namespace cli
+{
+namespace detail
+{
+
+class LinuxKeyboard : public InputDevice
+{
+public:
+    explicit LinuxKeyboard(Scheduler& _scheduler) :
+        InputDevice(_scheduler)
+    {
+        ToManualMode();
+        servant = std::make_unique<std::thread>( [this](){ Read(); } );
+        servant->detach();
+    }
+    ~LinuxKeyboard()
+    {
+        run = false;
+        ToStandardMode();
+    }
+
+private:
+
+    void Read()
+    {
+        while ( run )
+        {
+            auto k = Get();
+            Notify(k);
+        }
+    }
+
+    std::pair<KeyType,char> Get()
+    {
+        int ch = std::getchar();
+        switch( ch )
+        {
+            case EOF:
+            case 4:  // EOT
+                return std::make_pair(KeyType::eof,' ');
+                break;
+            case 127: return std::make_pair(KeyType::backspace,' '); break;
+            case 10: return std::make_pair(KeyType::ret,' '); break;
+            case 27: // symbol
+                ch = std::getchar();
+                if ( ch == 91 ) // arrow keys
+                {
+                    ch = std::getchar();
+                    switch( ch )
+                    {
+                        case 51:
+                            ch = std::getchar();
+                            if ( ch == 126 ) return std::make_pair(KeyType::canc,' ');
+                            else return std::make_pair(KeyType::ignored,' ');
+                            break;
+                        case 65: return std::make_pair(KeyType::up,' ');
+                        case 66: return std::make_pair(KeyType::down,' ');
+                        case 68: return std::make_pair(KeyType::left,' ');
+                        case 67: return std::make_pair(KeyType::right,' ');
+                        case 70: return std::make_pair(KeyType::end,' ');
+                        case 72: return std::make_pair(KeyType::home,' ');
+                        default: return std::make_pair(KeyType::ignored,' ');
+                    }
+                }
+                break;
+            default: // ascii
+            {
+                const char c = static_cast<char>(ch);
+                return std::make_pair(KeyType::ascii,c);
+            }
+        }
+        return std::make_pair(KeyType::ignored,' ');
+    }
+
+    void ToManualMode()
+    {
+        constexpr tcflag_t ICANON_FLAG = ICANON;
+        constexpr tcflag_t ECHO_FLAG = ECHO;
+
+        tcgetattr( STDIN_FILENO, &oldt );
+        newt = oldt;
+        newt.c_lflag &= ~( ICANON_FLAG | ECHO_FLAG );
+        tcsetattr( STDIN_FILENO, TCSANOW, &newt );
+    }
+    void ToStandardMode()
+    {
+        tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
+    }
+
+    termios oldt;
+    termios newt;
+    std::atomic<bool> run{ true };
+    std::unique_ptr<std::thread> servant;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_LINUXKEYBOARD_H_
+
diff --git a/inc/cli/detail/newboostasiolib.h b/inc/cli/detail/newboostasiolib.h
new file mode 100644
index 00000000..c1af8d60
--- /dev/null
+++ b/inc/cli/detail/newboostasiolib.h
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_NEWBOOSTASIOLIB_H_
+#define CLI_DETAIL_NEWBOOSTASIOLIB_H_
+
+#include <boost/version.hpp>
+#include <boost/asio.hpp>
+
+namespace cli
+{
+namespace detail
+{
+
+namespace asiolib = boost::asio;
+namespace asiolibec = boost::system;
+
+class NewBoostAsioLib
+{
+public:
+
+    using ContextType = boost::asio::io_context;
+
+    class Executor
+    {
+    public:
+        explicit Executor(ContextType& ios) :
+            executor(ios.get_executor()) {}
+        explicit Executor(boost::asio::ip::tcp::socket& socket) :
+            executor(socket.get_executor()) {}
+        template <typename T> void Post(T&& t) { boost::asio::post(executor, std::forward<T>(t)); }
+    private:
+#if BOOST_VERSION >= 107400
+    using AsioExecutor = boost::asio::any_io_executor;
+#else
+    using AsioExecutor = boost::asio::executor;
+#endif
+         AsioExecutor executor;
+    };
+
+    static boost::asio::ip::address IpAddressFromString(const std::string& address)
+    {
+        return boost::asio::ip::make_address(address);
+    }
+
+    static auto MakeWorkGuard(ContextType& context)
+    {
+        return boost::asio::make_work_guard(context);
+    }
+
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_NEWBOOSTASIOLIB_H_
+
diff --git a/inc/cli/detail/newstandaloneasiolib.h b/inc/cli/detail/newstandaloneasiolib.h
new file mode 100644
index 00000000..eb2174af
--- /dev/null
+++ b/inc/cli/detail/newstandaloneasiolib.h
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_NEWSTANDALONEASIOLIB_H_
+#define CLI_DETAIL_NEWSTANDALONEASIOLIB_H_
+
+#include <asio/version.hpp>
+#include <asio.hpp>
+
+namespace cli
+{
+namespace detail
+{
+
+namespace asiolib = asio;
+namespace asiolibec = asio;
+
+class NewStandaloneAsioLib
+{
+public:
+
+    using ContextType = asio::io_context;
+
+    class Executor
+    {
+    public:
+        explicit Executor(ContextType& ios) :
+            executor(ios.get_executor()) {}
+        explicit Executor(asio::ip::tcp::socket& socket) :
+            executor(socket.get_executor()) {}
+        template <typename T> void Post(T&& t) { asio::post(executor, std::forward<T>(t)); }
+    private:
+#if ASIO_VERSION >= 101700
+    using AsioExecutor = asio::any_io_executor;
+#else
+    using AsioExecutor = asio::executor;
+#endif
+         AsioExecutor executor;
+    };
+
+    static asio::ip::address IpAddressFromString(const std::string& address)
+    {
+        return asio::ip::make_address(address);
+    }
+
+    static auto MakeWorkGuard(ContextType& context)
+    {
+        return asio::make_work_guard(context);
+    }
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_NEWSTANDALONEASIOLIB_H_
+
diff --git a/inc/cli/detail/oldboostasiolib.h b/inc/cli/detail/oldboostasiolib.h
new file mode 100644
index 00000000..41c3e154
--- /dev/null
+++ b/inc/cli/detail/oldboostasiolib.h
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_OLDBOOSTASIOLIB_H_
+#define CLI_DETAIL_OLDBOOSTASIOLIB_H_
+
+#include <boost/asio.hpp>
+
+namespace cli
+{
+namespace detail
+{
+
+namespace asiolib = boost::asio;
+namespace asiolibec = boost::system;
+
+class OldBoostAsioLib
+{
+public:
+    using ContextType = boost::asio::io_service;
+
+    class Executor
+    {
+    public:
+        explicit Executor(ContextType& _ios) :
+            ios(_ios) {}
+        explicit Executor(boost::asio::ip::tcp::socket& socket) :
+            ios(socket.get_io_service()) {}
+        template <typename T> void Post(T&& t) { ios.post(std::forward<T>(t)); }
+    private:
+        ContextType& ios;
+    };
+
+    static boost::asio::ip::address IpAddressFromString(const std::string& address)
+    {
+        return boost::asio::ip::address::from_string(address);
+    }
+
+    static auto MakeWorkGuard(ContextType& context)
+    {
+        boost::asio::io_service::work work(context);
+        return work;
+    }
+
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_OLDBOOSTASIOLIB_H_
+
diff --git a/inc/cli/detail/oldstandaloneasiolib.h b/inc/cli/detail/oldstandaloneasiolib.h
new file mode 100644
index 00000000..4e8132e7
--- /dev/null
+++ b/inc/cli/detail/oldstandaloneasiolib.h
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_OLDSTANDALONEASIOLIB_H_
+#define CLI_DETAIL_OLDSTANDALONEASIOLIB_H_
+
+#define ASIO_STANDALONE 1
+
+#include <asio.hpp>
+
+namespace cli
+{
+namespace detail
+{
+
+namespace asiolib = asio;
+namespace asiolibec = asio;
+
+class OldStandaloneAsioLib
+{
+public:
+
+    using ContextType = asio::io_service;
+
+    class Executor
+    {
+    public:
+        explicit Executor(ContextType& _ios) :
+            ios(_ios) {}
+        explicit Executor(asio::ip::tcp::socket& socket) :
+            ios(socket.get_io_service()) {}
+        template <typename T> void Post(T&& t) { ios.post(std::forward<T>(t)); }
+    private:
+        ContextType& ios;
+    };
+
+    static asio::ip::address IpAddressFromString(const std::string& address)
+    {
+        return asio::ip::address::from_string(address);
+    }
+
+    static auto MakeWorkGuard(ContextType& context)
+    {
+        asio::io_service::work work(context);
+        return work;
+    }
+
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_OLDSTANDALONEASIOLIB_H_
+
diff --git a/inc/cli/detail/rang.h b/inc/cli/detail/rang.h
new file mode 100644
index 00000000..d5ff96c3
--- /dev/null
+++ b/inc/cli/detail/rang.h
@@ -0,0 +1,298 @@
+#ifndef RANG_DOT_HPP
+#define RANG_DOT_HPP
+
+#if defined(__unix__) || defined(__unix) || defined(__linux__)
+#define OS_LINUX
+#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64)
+#define OS_WIN
+#elif defined(__APPLE__) || defined(__MACH__)
+#define OS_MAC
+#else
+#error Unknown Platform
+#endif
+
+#if defined(OS_LINUX) || defined(OS_MAC)
+    #include <unistd.h>
+    #include <cstring>
+#elif defined(OS_WIN)
+    #if !defined(NOMINMAX)
+        #define NOMINMAX 1
+    #endif // !defined(NOMINMAX)
+    #include <windows.h>
+    #include <io.h>
+    #include <VersionHelpers.h>
+#endif
+
+#include <algorithm>
+#include <cstdlib>
+#include <ios>
+#include <iostream>
+#include <iterator>
+#include <type_traits>
+
+namespace rang {
+
+enum class style {
+    reset     = 0,
+    bold      = 1,
+    dim       = 2,
+    italic    = 3,
+    underline = 4,
+    blink     = 5,
+    rblink    = 6,
+    reversed  = 7,
+    conceal   = 8,
+    crossed   = 9
+};
+
+enum class fg {
+    black   = 30,
+    red     = 31,
+    green   = 32,
+    yellow  = 33,
+    blue    = 34,
+    magenta = 35,
+    cyan    = 36,
+    gray    = 37,
+    reset   = 39
+};
+
+enum class bg {
+    black   = 40,
+    red     = 41,
+    green   = 42,
+    yellow  = 43,
+    blue    = 44,
+    magenta = 45,
+    cyan    = 46,
+    gray    = 47,
+    reset   = 49
+};
+
+enum class fgB {
+    black   = 90,
+    red     = 91,
+    green   = 92,
+    yellow  = 93,
+    blue    = 94,
+    magenta = 95,
+    cyan    = 96,
+    gray    = 97
+};
+
+enum class bgB {
+    black   = 100,
+    red     = 101,
+    green   = 102,
+    yellow  = 103,
+    blue    = 104,
+    magenta = 105,
+    cyan    = 106,
+    gray    = 107
+};
+
+enum class control { autoColor = 0, forceColor = 1 };
+
+
+namespace rang_implementation {
+
+    inline std::streambuf const *&RANG_coutbuf()
+    {
+        static std::streambuf const *pOutbuff = std::cout.rdbuf();
+        return pOutbuff;
+    }
+
+    inline std::streambuf const *&RANG_cerrbuf()
+    {
+        static std::streambuf const *pErrbuff = std::cerr.rdbuf();
+        return pErrbuff;
+    }
+
+    inline std::streambuf const *&RANG_clogbuf()
+    {
+        static std::streambuf const *pLogbuff = std::clog.rdbuf();
+        return pLogbuff;
+    }
+
+    inline int getIword()
+    {
+        static int i = std::ios_base::xalloc();
+        return i;
+    }
+
+
+    inline bool supportsColor()
+    {
+#if defined(OS_LINUX) || defined(OS_MAC)
+        static constexpr const char* Terms[] = {
+            "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm",
+            "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"
+        };
+
+        const char *env_p = std::getenv("TERM");
+        if (env_p == nullptr) {
+            return false;
+        }
+
+        static const bool result = std::any_of(
+          std::begin(Terms), std::end(Terms), [&](const char* term) {
+              return std::strstr(env_p, term) != nullptr;
+          });
+
+#elif defined(OS_WIN)
+        static constexpr bool result = true;
+#endif
+        return result;
+    }
+
+
+    inline bool isTerminal(const std::streambuf *osbuf)
+    {
+        if (osbuf == RANG_coutbuf()) {
+#if defined(OS_LINUX) || defined(OS_MAC)
+            return isatty(fileno(stdout)) ? true : false;
+#elif defined(OS_WIN)
+            return _isatty(_fileno(stdout)) ? true : false;
+#endif
+        }
+
+        if (osbuf == RANG_cerrbuf() || osbuf == RANG_clogbuf()) {
+#if defined(OS_LINUX) || defined(OS_MAC)
+            return isatty(fileno(stderr)) ? true : false;
+#elif defined(OS_WIN)
+            return _isatty(_fileno(stderr)) ? true : false;
+#endif
+        }
+        return false;
+    }
+
+
+    template <typename T>
+    using enableStd =
+      typename std::enable_if<std::is_same<T, rang::style>::value
+          || std::is_same<T, rang::fg>::value
+          || std::is_same<T, rang::bg>::value
+          || std::is_same<T, rang::fgB>::value
+          || std::is_same<T, rang::bgB>::value,
+        std::ostream &>::type;
+
+
+#ifdef OS_WIN
+    inline HANDLE getVersionDependentHandle()
+    {
+        if (IsWindowsVersionOrGreater(10, 0, 0)) return nullptr;
+        return GetStdHandle(STD_OUTPUT_HANDLE);
+    }
+
+    inline HANDLE getConsoleHandle()
+    {
+        static HANDLE h = getVersionDependentHandle();
+        return h;
+    }
+
+    inline WORD reverseRGB(WORD rgb)
+    {
+        static const WORD rev[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
+        return rev[rgb];
+    }
+
+    inline void setWinAttribute(rang::bg col, WORD &state)
+    {
+        state &= 0xFF0F;
+        state |= reverseRGB(static_cast<WORD>(col) - 40) << 4;
+    }
+
+    inline void setWinAttribute(rang::fg col, WORD &state)
+    {
+        state &= 0xFFF0;
+        state |= reverseRGB(static_cast<WORD>(col) - 30);
+    }
+
+    inline void setWinAttribute(rang::bgB col, WORD &state)
+    {
+        state &= 0xFF0F;
+        state |= (0x8 | reverseRGB(static_cast<WORD>(col) - 100)) << 4;
+    }
+
+    inline void setWinAttribute(rang::fgB col, WORD &state)
+    {
+        state &= 0xFFF0;
+        state |= (0x8 | reverseRGB(static_cast<WORD>(col) - 90));
+    }
+
+    inline void setWinAttribute(rang::style style, WORD &state)
+    {
+        if (style == rang::style::reset) {
+            state = (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
+        }
+    }
+
+    inline WORD &current_state()
+    {
+        static WORD state
+          = (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
+        return state;
+    }
+
+    template <typename T>
+    inline enableStd<T> setColor(std::ostream &os, T const value)
+    {
+        HANDLE h = getConsoleHandle();
+        if (h && isTerminal(os.rdbuf())) {
+            setWinAttribute(value, current_state());
+            SetConsoleTextAttribute(h, current_state());
+            return os;
+        }
+        return os << "\033[" << static_cast<int>(value) << "m";
+    }
+#else
+    template <typename T>
+    inline enableStd<T> setColor(std::ostream &os, T const value)
+    {
+        return os << "\033[" << static_cast<int>(value) << "m";
+    }
+#endif
+
+    template <typename T>
+    using enableControl =
+      typename std::enable_if<std::is_same<T, rang::control>::value,
+        std::ostream &>::type;
+}
+
+inline void init()
+{
+    rang_implementation::RANG_coutbuf();
+    rang_implementation::RANG_cerrbuf();
+    rang_implementation::RANG_clogbuf();
+}
+
+template <typename T>
+inline rang_implementation::enableStd<T> operator<<(
+  std::ostream &os, T const value)
+{
+    std::streambuf const *osbuf = os.rdbuf();
+    return (os.iword(rang_implementation::getIword())
+             || ((rang_implementation::supportsColor())
+             && (rang_implementation::isTerminal(osbuf))))
+      ? rang_implementation::setColor(os, value)
+      : os;
+}
+
+template <typename T>
+inline rang_implementation::enableControl<T> operator<<(
+  std::ostream &os, T const value)
+{
+    if (value == rang::control::forceColor) {
+        os.iword(rang_implementation::getIword()) = 1;
+    } else if (value == rang::control::autoColor) {
+        os.iword(rang_implementation::getIword()) = 0;
+    }
+    return os;
+}
+}
+
+#undef OS_LINUX
+#undef OS_WIN
+#undef OS_MAC
+
+#endif /* ifndef RANG_DOT_HPP */
diff --git a/inc/cli/detail/server.h b/inc/cli/detail/server.h
new file mode 100644
index 00000000..aad0d7ca
--- /dev/null
+++ b/inc/cli/detail/server.h
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_SERVER_H_
+#define CLI_DETAIL_SERVER_H_
+
+#include <memory>
+#include <queue>
+
+namespace cli
+{
+namespace detail
+{
+
+class Session : public std::enable_shared_from_this<Session>, public std::streambuf
+{
+public:
+    virtual ~Session() = default;
+    virtual void Start()
+    {
+        OnConnect();
+        Read();
+    }
+
+protected:
+
+    Session(asiolib::ip::tcp::socket _socket) : socket(std::move(_socket)), outStream( this ) {}
+
+    virtual void Disconnect()
+    {
+        socket.shutdown(asiolib::ip::tcp::socket::shutdown_both);
+        socket.close();
+    }
+
+    virtual void Read()
+    {
+      auto self( shared_from_this() );
+      socket.async_read_some(asiolib::buffer( data, max_length ),
+          [ this, self ]( asiolibec::error_code ec, std::size_t length )
+          {
+              if ( !socket.is_open() || ( ec == asiolib::error::eof ) || ( ec == asiolib::error::connection_reset ) )
+                  OnDisconnect();
+              else if ( ec )
+                  OnError();
+              else
+              {
+                  OnDataReceived( std::string( data, length ));
+                  Read();
+              }
+          });
+    }
+
+    virtual void Send(const std::string& msg)
+    {
+        asiolibec::error_code ec;
+        asiolib::write(socket, asiolib::buffer(msg), ec);
+        if ((ec == asiolib::error::eof) || (ec == asiolib::error::connection_reset))
+            OnDisconnect();
+        else if (ec)
+            OnError();
+    }
+
+    virtual std::ostream& OutStream() { return outStream; }
+
+    virtual void OnConnect() = 0;
+    virtual void OnDisconnect() = 0;
+    virtual void OnError() = 0;
+    virtual void OnDataReceived(const std::string& _data) = 0;
+
+    virtual std::string Encode(const std::string& _data) const { return _data; }
+
+private:
+
+    // std::streambuf
+    std::streamsize xsputn( const char* s, std::streamsize n ) override
+    {
+        Send(Encode(std::string(s, s+n)));
+        return n;
+    }
+    int overflow( int c ) override
+    {
+        Send(Encode(std::string(1, static_cast< char >(c))));
+        return c;
+    }
+
+    asiolib::ip::tcp::socket socket;
+    enum { max_length = 1024 };
+    char data[ max_length ];
+    std::ostream outStream;
+};
+
+
+template <typename ASIOLIB>
+class Server
+{
+public:
+    // disable value semantics
+    Server( const Server& ) = delete;
+    Server& operator = ( const Server& ) = delete;
+
+    Server(typename ASIOLIB::ContextType& ios, unsigned short port) :
+        acceptor(ios, asiolib::ip::tcp::endpoint(asiolib::ip::tcp::v4(), port)),
+        socket(ios)
+    {
+        Accept();
+    }
+    Server(typename ASIOLIB::ContextType& ios, std::string address, unsigned short port) :
+        acceptor(ios, asiolib::ip::tcp::endpoint(ASIOLIB::IpAddressFromString(address), port)),
+        socket(ios)
+    {
+        Accept();
+    }
+    virtual ~Server() = default;
+    // returns shared_ptr instead of unique_ptr because Session needs to use enable_shared_from_this
+    virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket socket) = 0;
+private:
+    void Accept()
+    {
+        acceptor.async_accept(socket, [this](asiolibec::error_code ec)
+            {
+                if (!ec) CreateSession(std::move(socket))->Start();
+                Accept();
+            });
+    }
+    asiolib::ip::tcp::acceptor acceptor;
+    asiolib::ip::tcp::socket socket;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_SERVER_H_
+
diff --git a/inc/cli/detail/split.h b/inc/cli/detail/split.h
new file mode 100644
index 00000000..211c5010
--- /dev/null
+++ b/inc/cli/detail/split.h
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_SPLIT_H_
+#define CLI_DETAIL_SPLIT_H_
+
+#include <string>
+#include <vector>
+#include <algorithm>
+
+namespace cli
+{
+namespace detail
+{
+
+class Text
+{
+public:
+    explicit Text(const std::string& _input) : input(_input)
+    {
+    }
+    void SplitInto(std::vector<std::string>& strs)
+    {
+        Reset();
+        for (char c: input)
+            Eval(c);
+        RemoveEmptyEntries();
+        splitResult.swap(strs); // puts the result back in strs
+    }
+private:
+    void Reset()
+    {
+        state = State::space;
+        prev_state = State::space;
+        sentence_type = SentenceType::double_quote;
+        splitResult.clear();
+    }
+
+    void Eval(char c)
+    {
+        switch(state)
+        {
+            case State::space:
+                EvalSpace(c);
+                break;
+            case State::word:
+                EvalWord(c);
+                break;
+            case State::sentence:
+                EvalSentence(c);
+                break;
+            case State::escape:
+                EvalEscape(c);
+                break;
+        }
+    }
+
+    void EvalSpace(char c)
+    {
+        if (c == ' ' || c == '\t' || c == '\n')
+        {
+            // do nothing
+        }
+        else if (c == '"' || c == '\'')
+        {
+            NewSentence(c);
+        }
+        else if (c == '\\')
+        {
+            // This is the case where the first character of a word is escaped.
+            // Should come back into the word state after this.
+            prev_state = State::word;
+            state = State::escape;
+            splitResult.push_back("");
+        }
+        else
+        {
+            state = State::word;
+            splitResult.push_back(std::string(1, c));
+        }
+    }
+
+    void EvalWord(char c)
+    {
+        if (c == ' ' || c == '\t' || c == '\n')
+        {
+            state = State::space;
+        }
+        else if (c == '"' || c == '\'')
+        {
+            NewSentence(c);
+        }
+        else if (c == '\\')
+        {
+            prev_state = state;
+            state = State::escape;
+        }
+        else
+        {
+            assert(!splitResult.empty());
+            splitResult.back() += c;
+        }
+    }
+
+    void EvalSentence(char c)
+    {
+        if (c == '"' || c == '\'')
+        {
+            auto new_type = c == '"' ? SentenceType::double_quote : SentenceType::quote;
+            if (new_type == sentence_type)
+                state = State::space;
+            else
+            {
+                assert(!splitResult.empty());
+                splitResult.back() += c;
+            }
+        }
+        else if (c == '\\')
+        {
+            prev_state = state;
+            state = State::escape;
+        }
+        else
+        {
+            assert(!splitResult.empty());
+            splitResult.back() += c;
+        }
+    }
+
+    void EvalEscape(char c)
+    {
+        assert(!splitResult.empty());
+        if (c != '"' && c != '\'' && c != '\\')
+            splitResult.back() += "\\";
+        splitResult.back() += c;
+        state = prev_state;
+    }
+
+    void NewSentence(char c)
+    {
+        state = State::sentence;
+        sentence_type = ( c == '"' ? SentenceType::double_quote : SentenceType::quote);
+        splitResult.push_back("");
+    }
+
+    void RemoveEmptyEntries()
+    {
+        // remove null entries from the vector:
+        splitResult.erase(
+            std::remove_if(
+                splitResult.begin(),
+                splitResult.end(),
+                [](const std::string& s){ return s.empty(); }
+            ),
+            splitResult.end()
+        );
+    }
+
+    enum class State { space, word, sentence, escape };
+    enum class SentenceType { quote, double_quote };
+    State state = State::space;
+    State prev_state = State::space;
+    SentenceType sentence_type = SentenceType::double_quote;
+    const std::string input;
+    std::vector<std::string> splitResult;
+};
+
+// Split the string input into a vector of strings.
+// The original string is split where there are spaces.
+// Quotes and double quotes can be used to indicate a substring that should not be splitted
+// (even if it contains spaces)
+
+//          split(strs, "");                => empty vector
+//          split(strs, " ");               => empty vector
+//          split(strs, "  ");              => empty vector
+//          split(strs, "\t");              => empty vector
+//          split(strs, "  \t \t     ");    => empty vector
+
+//          split(strs, "1234567890");      => <"1234567890">
+//          split(strs, "  foo ");          => <"foo">
+//          split(strs, "  foo \t \t bar \t");  => <"foo","bar">
+
+//          split(strs, "\"\"");            => empty vector
+//          split(strs, "\"foo bar\"");     => <"foo bar">
+//          split(strs, "    \t\t \"foo \tbar\"     \t");   => <"foo \tbar">
+//          split(strs, " first   \t\t \"foo \tbar\"     \t last"); => <"first","foo \tbar","last">
+//          split(strs, "first\"foo \tbar\"");  => <"first","foo \tbar">
+//          split(strs, "first \"'second' 'thirdh'\""); => <"first","'second' 'thirdh'">
+
+//          split(strs, "''");      => empty vector
+//          split(strs, "'foo bar'");   => <"foo bar">
+//          split(strs, "    \t\t 'foo \tbar'     \t"); => <"foo \tbar">
+//          split(strs, " first   \t\t 'foo \tbar'     \t last"); => <"first","foo \tbar","last">
+//          split(strs, "first'foo \tbar'"); => <"first","foo \tbar">
+//          split(strs, "first '\"second\" \"thirdh\"'"); => <"first","\"second\" \"thirdh\"">
+
+//          split(strs, R"("foo\"bar")"); // "foo\"bar" => <"foo"bar">
+//          split(strs, R"('foo\'bar')"); // 'foo\'bar' => <"foo'bar">
+//          split(strs, R"("foo\bar")"); // "foo\bar" => <"foo\bar">
+//          split(strs, R"("foo\\"bar")"); // "foo\\"bar" => <"foo\"bar">
+
+inline void split(std::vector<std::string>& strs, const std::string& input)
+{
+    Text sentence(input);
+    sentence.SplitInto(strs);
+}
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_SPLIT_H_
diff --git a/inc/cli/detail/standaloneasiolib.h b/inc/cli/detail/standaloneasiolib.h
new file mode 100644
index 00000000..adb747ea
--- /dev/null
+++ b/inc/cli/detail/standaloneasiolib.h
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_STANDALONEASIOLIB_H_
+#define CLI_DETAIL_STANDALONEASIOLIB_H_
+
+/**
+ * This header file provides the class `cli::StandaloneAsioLib`, using the right
+ * implementation according to the version of asio libraries included.
+ */
+
+#include <asio/version.hpp>
+
+#if ASIO_VERSION < 101300
+    #include "oldstandaloneasiolib.h"
+    namespace cli { namespace detail { using StandaloneAsioLib = OldStandaloneAsioLib; } }
+#else
+    #include "newstandaloneasiolib.h"
+    namespace cli { namespace detail { using StandaloneAsioLib = NewStandaloneAsioLib; } }
+#endif
+
+#endif // CLI_DETAIL_STANDALONEASIOLIB_H_
+
diff --git a/inc/cli/detail/terminal.h b/inc/cli/detail/terminal.h
new file mode 100644
index 00000000..a21b8ac4
--- /dev/null
+++ b/inc/cli/detail/terminal.h
@@ -0,0 +1,213 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_TERMINAL_H_
+#define CLI_DETAIL_TERMINAL_H_
+
+#include <string>
+#include "../colorprofile.h"
+#include "inputdevice.h"
+
+namespace cli
+{
+namespace detail
+{
+
+enum class Symbol
+{
+    nothing,
+    command,
+    up,
+    down,
+    tab,
+    eof
+};
+
+class Terminal
+{
+  public:
+    Terminal(std::ostream &_out) : out(_out) {}
+
+    void ResetCursor() { position = 0; }
+
+    void SetLine(const std::string &newLine)
+    {
+        out << beforeInput
+            << std::string(position, '\b') << newLine
+            << afterInput << std::flush;
+
+        // if newLine is shorter then currentLine, we have
+        // to clear the rest of the string
+        if (newLine.size() < currentLine.size())
+        {
+            out << std::string(currentLine.size() - newLine.size(), ' ');
+            // and go back
+            out << std::string(currentLine.size() - newLine.size(), '\b') << std::flush;
+        }
+
+        currentLine = newLine;
+        position = currentLine.size();
+    }
+
+    std::string GetLine() const { return currentLine; }
+
+    std::pair<Symbol, std::string> Keypressed(std::pair<KeyType, char> k)
+    {
+        switch (k.first)
+        {
+            case KeyType::eof:
+                return std::make_pair(Symbol::eof, std::string{});
+                break;
+            case KeyType::backspace:
+            {
+                if (position == 0)
+                    break;
+
+                --position;
+
+                const auto pos = static_cast<std::string::difference_type>(position);
+                // remove the char from buffer
+                currentLine.erase(currentLine.begin() + pos);
+                // go back to the previous char
+                out << '\b';
+                // output the rest of the line
+                out << std::string(currentLine.begin() + pos, currentLine.end());
+                // remove last char
+                out << ' ';
+                // go back to the original position
+                out << std::string(currentLine.size() - position + 1, '\b') << std::flush;
+                break;
+            }
+            case KeyType::up:
+                return std::make_pair(Symbol::up, std::string{});
+                break;
+            case KeyType::down:
+                return std::make_pair(Symbol::down, std::string{});
+                break;
+            case KeyType::left:
+                if (position > 0)
+                {
+                    out << '\b' << std::flush;
+                    --position;
+                }
+                break;
+            case KeyType::right:
+                if (position < currentLine.size())
+                {
+                    out << beforeInput
+                        << currentLine[position]
+                        << afterInput << std::flush;
+                    ++position;
+                }
+                break;
+            case KeyType::ret:
+            {
+                out << "\r\n";
+                auto cmd = currentLine;
+                currentLine.clear();
+                position = 0;
+                return std::make_pair(Symbol::command, cmd);
+            }
+            break;
+            case KeyType::ascii:
+            {
+                const char c = static_cast<char>(k.second);
+                if (c == '\t')
+                    return std::make_pair(Symbol::tab, std::string());
+                else
+                {
+                    const auto pos = static_cast<std::string::difference_type>(position);
+
+                    // output the new char:
+                    out << beforeInput << c;
+                    // and the rest of the string:
+                    out << std::string(currentLine.begin() + pos, currentLine.end())
+                        << afterInput;
+
+                    // go back to the original position
+                    out << std::string(currentLine.size() - position, '\b') << std::flush;
+
+                    // update the buffer and cursor position:
+                    currentLine.insert(currentLine.begin() + pos, c);
+                    ++position;
+                }
+
+                break;
+            }
+            case KeyType::canc:
+            {
+                if (position == currentLine.size())
+                    break;
+
+                const auto pos = static_cast<std::string::difference_type>(position);
+
+                // output the rest of the line
+                out << std::string(currentLine.begin() + pos + 1, currentLine.end());
+                // remove last char
+                out << ' ';
+                // go back to the original position
+                out << std::string(currentLine.size() - position, '\b') << std::flush;
+                // remove the char from buffer
+                currentLine.erase(currentLine.begin() + pos);
+                break;
+            }
+            case KeyType::end:
+            {
+                const auto pos = static_cast<std::string::difference_type>(position);
+
+                out << beforeInput
+                    << std::string(currentLine.begin() + pos, currentLine.end())
+                    << afterInput << std::flush;
+                position = currentLine.size();
+                break;
+            }
+            case KeyType::home:
+            {
+                out << std::string(position, '\b') << std::flush;
+                position = 0;
+                break;
+            }
+            case KeyType::ignored:
+                // TODO
+                break;
+        }
+
+        return std::make_pair(Symbol::nothing, std::string());
+    }
+
+  private:
+    std::string currentLine;
+    std::size_t position = 0; // next writing position in currentLine
+    std::ostream &out;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_TERMINAL_H_
diff --git a/inc/cli/detail/winkeyboard.h b/inc/cli/detail/winkeyboard.h
new file mode 100644
index 00000000..bee9a0df
--- /dev/null
+++ b/inc/cli/detail/winkeyboard.h
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_DETAIL_WINKEYBOARD_H_
+#define CLI_DETAIL_WINKEYBOARD_H_
+
+#include <functional>
+#include <string>
+#include <thread>
+#include <memory>
+#include <atomic>
+#include <conio.h>
+
+#include "inputdevice.h"
+
+namespace cli
+{
+namespace detail
+{
+
+class WinKeyboard : public InputDevice
+{
+public:
+    explicit WinKeyboard(Scheduler& _scheduler) : InputDevice(_scheduler)
+    {
+        servant = std::make_unique<std::thread>( [this](){ Read(); } );
+        servant->detach();
+    }
+    ~WinKeyboard()
+    {
+        run = false;
+    }
+
+private:
+    void Read()
+    {
+        while (run)
+        {
+            auto k = Get();
+            Notify(k);
+        }
+    }
+
+    std::pair<KeyType, char> Get()
+    {
+        int c = _getch();
+        switch (c)
+        {
+            case EOF:
+            case 4:  // EOT ie CTRL-D
+            case 26: // CTRL-Z
+            case 3:  // CTRL-C
+                return std::make_pair(KeyType::eof, ' ');
+                break;
+
+            case 224: // symbol
+            {
+                c = _getch();
+                switch (c)
+                {
+                case 72:
+                    return std::make_pair(KeyType::up, ' ');
+                    break;
+                case 80:
+                    return std::make_pair(KeyType::down, ' ');
+                    break;
+                case 75:
+                    return std::make_pair(KeyType::left, ' ');
+                    break;
+                case 77:
+                    return std::make_pair(KeyType::right, ' ');
+                    break;
+                case 71:
+                    return std::make_pair(KeyType::home, ' ');
+                    break;
+                case 79:
+                    return std::make_pair(KeyType::end, ' ');
+                    break;
+                case 83:
+                    return std::make_pair(KeyType::canc, ' ');
+                    break;
+                }
+            }
+            case 8:
+                return std::make_pair(KeyType::backspace, c);
+                break;
+            case 13:
+                return std::make_pair(KeyType::ret, c);
+                break;
+            default: // hopefully ascii
+            {
+                const char ch = static_cast<char>(c);
+                return std::make_pair(KeyType::ascii, ch);
+            }
+        }
+        return std::make_pair(KeyType::ignored, ' ');
+    }
+
+    std::atomic<bool> run{true};
+    std::unique_ptr<std::thread> servant;
+};
+
+} // namespace detail
+} // namespace cli
+
+#endif // CLI_DETAIL_WINKEYBOARD_H_
diff --git a/inc/cli/filehistorystorage.h b/inc/cli/filehistorystorage.h
new file mode 100644
index 00000000..19f01ba6
--- /dev/null
+++ b/inc/cli/filehistorystorage.h
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_FILEHISTORYSTORAGE_H_
+#define CLI_FILEHISTORYSTORAGE_H_
+
+#include "historystorage.h"
+#include <fstream>
+
+namespace cli
+{
+
+class FileHistoryStorage : public HistoryStorage
+{
+public:
+    FileHistoryStorage(const std::string& _fileName, std::size_t size = 1000) : 
+        maxSize(size),
+        fileName(_fileName)
+    {
+    }
+    void Store(const std::vector<std::string>& cmds) override
+    {
+        using dt = std::vector<std::string>::difference_type;
+        auto commands = Commands();
+        commands.insert(commands.end(), cmds.begin(), cmds.end());
+        if (commands.size() > maxSize)
+            commands.erase(
+                commands.begin(), 
+                commands.begin() + static_cast<dt>(commands.size() - maxSize)
+            );
+        std::ofstream f(fileName, std::ios_base::out);
+            for (const auto& line: commands)
+                f << line << '\n';
+    }
+    std::vector<std::string> Commands() const override
+    {
+        std::vector<std::string> commands;
+        std::ifstream in(fileName);
+        if (in)
+        {
+            std::string line;
+            while (std::getline(in, line))
+                commands.push_back(line);
+        }
+        return commands;
+    }
+    void Clear() override
+    {
+        std::ofstream f(fileName, std::ios_base::out | std::ios_base::trunc);
+    }
+
+private:
+    const std::size_t maxSize;
+    const std::string fileName;
+};
+
+} // namespace cli
+
+#endif // CLI_FILEHISTORYSTORAGE_H_
diff --git a/inc/cli/historystorage.h b/inc/cli/historystorage.h
new file mode 100644
index 00000000..f5af2b14
--- /dev/null
+++ b/inc/cli/historystorage.h
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_HISTORYSTORAGE_H_
+#define CLI_HISTORYSTORAGE_H_
+
+#include <vector>
+#include <string>
+
+namespace cli
+{
+
+class HistoryStorage
+{
+public:
+    virtual ~HistoryStorage() = default;
+    // Store a vector of commands in the history storage
+    virtual void Store(const std::vector<std::string>& commands) = 0;
+    // Returns all the commands stored
+    virtual std::vector<std::string> Commands() const = 0;
+    // Clear the whole content of the storage
+    // After calling this method, Commands() returns the empty vector
+    virtual void Clear() = 0;
+};
+
+} // namespace cli
+
+#endif // CLI_HISTORYSTORAGE_H_
diff --git a/inc/cli/loopscheduler.h b/inc/cli/loopscheduler.h
new file mode 100644
index 00000000..e3f65447
--- /dev/null
+++ b/inc/cli/loopscheduler.h
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_LOOPSCHEDULER_H_
+#define CLI_LOOPSCHEDULER_H_
+
+#include <queue>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+#include "scheduler.h"
+
+namespace cli
+{
+
+/**
+ * @brief The LoopScheduler is a simple thread-safe scheduler
+ * 
+ */
+class LoopScheduler : public Scheduler
+{
+public:
+    LoopScheduler() = default;
+    ~LoopScheduler()
+    {
+        Stop();
+    }
+
+    // non copyable
+    LoopScheduler(const LoopScheduler&) = delete;
+    LoopScheduler& operator=(const LoopScheduler&) = delete;
+
+    void Stop()
+    {
+        std::lock_guard<std::mutex> lck (mtx);
+        running = false;
+        cv.notify_all();
+    }
+    void Run()
+    {
+        while( ExecOne() ) {};
+    }
+
+    void Post(const std::function<void()>& f) override
+    {
+        std::lock_guard<std::mutex> lck (mtx);
+        tasks.push(f);
+        cv.notify_all();
+    }
+
+    bool ExecOne()
+    {
+        std::function<void()> task;
+        {
+            std::unique_lock<std::mutex> lck(mtx);
+            cv.wait(lck, [this](){ return !running || !tasks.empty(); });
+            if (!running)
+                return false;
+            task = tasks.front();
+            tasks.pop();
+        }
+
+        if (task)
+            task();
+
+        return true;
+    }
+private:
+    std::queue<std::function<void()>> tasks;
+    bool running{ true };
+    std::mutex mtx;
+    std::condition_variable cv;
+};
+
+} // namespace cli
+
+#endif // CLI_LOOPSCHEDULER_H_
diff --git a/inc/cli/scheduler.h b/inc/cli/scheduler.h
new file mode 100644
index 00000000..6c74ec85
--- /dev/null
+++ b/inc/cli/scheduler.h
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_SCHEDULER_H_
+#define CLI_SCHEDULER_H_
+
+#include <functional>
+
+namespace cli
+{
+
+/**
+ * A `Scheduler` represents an engine capable of running a task.
+ * Its method `Post` can be safely called from any thread to submit the task
+ * that will execute in an unspecified thread of execution as soon as possible
+ * (but in any case after the call to `Post` is terminated).
+ */
+class Scheduler
+{
+public:
+    virtual ~Scheduler() = default;
+
+    /// Submits a completion token or function object for execution.
+    virtual void Post(const std::function<void()>& f) = 0;
+};
+
+} // namespace cli
+
+#endif // CLI_SCHEDULER_H_
diff --git a/inc/cli/standaloneasiocliasyncsession.h b/inc/cli/standaloneasiocliasyncsession.h
new file mode 100644
index 00000000..c5eb6809
--- /dev/null
+++ b/inc/cli/standaloneasiocliasyncsession.h
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_STANDALONEASIOCLIASYNCSESSION_H_
+#define CLI_STANDALONEASIOCLIASYNCSESSION_H_
+
+#include "detail/genericcliasyncsession.h"
+#include "detail/standaloneasiolib.h"
+
+
+namespace cli { using StandaloneAsioCliAsyncSession = detail::GenericCliAsyncSession<detail::StandaloneAsioLib>; }
+
+#endif // CLI_STANDALONEASIOCLIASYNCSESSION_H_
+
diff --git a/inc/cli/standaloneasioremotecli.h b/inc/cli/standaloneasioremotecli.h
new file mode 100644
index 00000000..140623bf
--- /dev/null
+++ b/inc/cli/standaloneasioremotecli.h
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_STANDALONEASIOREMOTECLI_H_
+#define CLI_STANDALONEASIOREMOTECLI_H_
+
+#include "detail/genericasioremotecli.h"
+#include "detail/standaloneasiolib.h"
+
+namespace cli { using StandaloneAsioCliTelnetServer = detail::CliGenericTelnetServer<detail::StandaloneAsioLib>; }
+
+
+#endif // CLI_STANDALONEASIOREMOTECLI_H_
+
diff --git a/inc/cli/standaloneasioscheduler.h b/inc/cli/standaloneasioscheduler.h
new file mode 100644
index 00000000..fade453f
--- /dev/null
+++ b/inc/cli/standaloneasioscheduler.h
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_STANDALONEASIOSCHEDULER_H_
+#define CLI_STANDALONEASIOSCHEDULER_H_
+
+#include "detail/genericasioscheduler.h"
+#include "detail/standaloneasiolib.h"
+
+namespace cli { using StandaloneAsioScheduler = detail::GenericAsioScheduler<detail::StandaloneAsioLib>; }
+
+#endif // CLI_STANDALONEASIOSCHEDULER_H_
diff --git a/inc/cli/volatilehistorystorage.h b/inc/cli/volatilehistorystorage.h
new file mode 100644
index 00000000..d357c011
--- /dev/null
+++ b/inc/cli/volatilehistorystorage.h
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * CLI - A simple command line interface.
+ * Copyright (C) 2016-2021 Daniele Pallastrelli
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ******************************************************************************/
+
+#ifndef CLI_VOLATILEHISTORYSTORAGE_H_
+#define CLI_VOLATILEHISTORYSTORAGE_H_
+
+#include "historystorage.h"
+#include <deque>
+
+namespace cli
+{
+
+class VolatileHistoryStorage : public HistoryStorage
+{
+    public:
+        explicit VolatileHistoryStorage(std::size_t size = 1000) : maxSize(size) {}
+        void Store(const std::vector<std::string>& cmds) override
+        {
+            using dt = std::deque<std::string>::difference_type;
+            commands.insert(commands.end(), cmds.begin(), cmds.end());
+            if (commands.size() > maxSize)
+                commands.erase(
+                    commands.begin(),
+                    commands.begin()+static_cast<dt>(commands.size()-maxSize)
+                );
+        }
+        std::vector<std::string> Commands() const override
+        {
+            return std::vector<std::string>(commands.begin(), commands.end());
+        }
+        void Clear() override
+        {
+            commands.clear();
+        }
+    private:
+        const std::size_t maxSize;
+        std::deque<std::string> commands;
+};
+
+} // namespace cli
+
+#endif // CLI_VOLATILEHISTORYSTORAGE_H_
diff --git a/src/Makefile.am b/src/Makefile.am
index 6d4b9724..b6aad01c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -84,12 +84,16 @@ EXTRA_DIST = database/disk.sql database/memory.sql
 ## MAIN FILES ##
 ################
 
-bin_PROGRAMS = lektord lkt
+bin_PROGRAMS = lektord lkt luka
 
 ## The lkt client
 lkt_SOURCES = main/lkt.c base/cmd.c base/common.c base/segv.c
 lkt_LDFLAGS = -pthread -ldl -static
 
+## The luka client
+luka_SOURCES = main/luka.cpp
+luka_LDFLAGS = -pthread -static
+
 ## The lektord server
 lektord_SOURCES = main/server.c
 lektord_LDADD   = liblektor.la
diff --git a/src/Makefile.in b/src/Makefile.in
index b4ae1150..a7164527 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -134,7 +134,8 @@ host_triplet = @host@
 
 # TODO pour le Kubat du futur: c'est pas beau, trouver un truc pour que ça se fasse tout seul
 @LKT_STATIC_MODULE_TRUE@am__append_8 = module/thread.c
-bin_PROGRAMS = lektord$(EXEEXT) lkt$(EXEEXT) $(am__EXEEXT_1)
+bin_PROGRAMS = lektord$(EXEEXT) lkt$(EXEEXT) luka$(EXEEXT) \
+	$(am__EXEEXT_1)
 @LKT_KLKT_TRUE@am__append_9 = klkt/klkt
 @LKT_KLKT_TRUE@am__append_10 = $(BUILT_SOURCES)
 @LKT_KLKT_TRUE@am__append_11 = $(BUILT_SOURCES)
@@ -268,6 +269,12 @@ lkt_LDADD = $(LDADD)
 lkt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
 	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
 	$(lkt_LDFLAGS) $(LDFLAGS) -o $@
+am_luka_OBJECTS = main/luka.$(OBJEXT)
+luka_OBJECTS = $(am_luka_OBJECTS)
+luka_LDADD = $(LDADD)
+luka_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+	$(CXXFLAGS) $(luka_LDFLAGS) $(LDFLAGS) -o $@
 AM_V_P = $(am__v_P_@AM_V@)
 am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
 am__v_P_0 = false
@@ -306,7 +313,8 @@ am__depfiles_remade = base/$(DEPDIR)/cmd.Po base/$(DEPDIR)/common.Po \
 	database/$(DEPDIR)/liblektor_la-user.Plo \
 	klkt/$(DEPDIR)/klkt-icons.qrc.Po klkt/$(DEPDIR)/klkt-klkt.Po \
 	klkt/$(DEPDIR)/klkt-klkt.moc.Po main/$(DEPDIR)/lkt.Po \
-	main/$(DEPDIR)/server.Po mkv/$(DEPDIR)/liblektor_la-mkv.Plo \
+	main/$(DEPDIR)/luka.Po main/$(DEPDIR)/server.Po \
+	mkv/$(DEPDIR)/liblektor_la-mkv.Plo \
 	mkv/$(DEPDIR)/liblektor_la-utils.Plo \
 	mkv/$(DEPDIR)/liblektor_la-write.Plo \
 	module/$(DEPDIR)/liblektor_la-thread.Plo \
@@ -356,11 +364,11 @@ am__v_CXXLD_0 = @echo "  CXXLD   " $@;
 am__v_CXXLD_1 = 
 SOURCES = $(liblektor_la_SOURCES) $(liblktmodrepo_la_SOURCES) \
 	$(liblktmodsdl_la_SOURCES) $(klkt_klkt_SOURCES) \
-	$(lektord_SOURCES) $(lkt_SOURCES)
+	$(lektord_SOURCES) $(lkt_SOURCES) $(luka_SOURCES)
 DIST_SOURCES = $(am__liblektor_la_SOURCES_DIST) \
 	$(liblktmodrepo_la_SOURCES) $(liblktmodsdl_la_SOURCES) \
 	$(am__klkt_klkt_SOURCES_DIST) $(lektord_SOURCES) \
-	$(lkt_SOURCES)
+	$(lkt_SOURCES) $(luka_SOURCES)
 am__can_run_installinfo = \
   case $$AM_UPDATE_INFO_DIR in \
     n|no|NO) false;; \
@@ -593,6 +601,8 @@ CLEANFILES = database/disk.c database/memory.c $(am__append_11)
 EXTRA_DIST = database/disk.sql database/memory.sql $(am__append_10)
 lkt_SOURCES = main/lkt.c base/cmd.c base/common.c base/segv.c
 lkt_LDFLAGS = -pthread -ldl -static
+luka_SOURCES = main/luka.cpp
+luka_LDFLAGS = -pthread -static
 lektord_SOURCES = main/server.c
 lektord_LDADD = liblektor.la
 lektord_LDFLAGS = -static
@@ -898,6 +908,12 @@ base/segv.$(OBJEXT): base/$(am__dirstamp) \
 lkt$(EXEEXT): $(lkt_OBJECTS) $(lkt_DEPENDENCIES) $(EXTRA_lkt_DEPENDENCIES) 
 	@rm -f lkt$(EXEEXT)
 	$(AM_V_CCLD)$(lkt_LINK) $(lkt_OBJECTS) $(lkt_LDADD) $(LIBS)
+main/luka.$(OBJEXT): main/$(am__dirstamp) \
+	main/$(DEPDIR)/$(am__dirstamp)
+
+luka$(EXEEXT): $(luka_OBJECTS) $(luka_DEPENDENCIES) $(EXTRA_luka_DEPENDENCIES) 
+	@rm -f luka$(EXEEXT)
+	$(AM_V_CXXLD)$(luka_LINK) $(luka_OBJECTS) $(luka_LDADD) $(LIBS)
 
 mostlyclean-compile:
 	-rm -f *.$(OBJEXT)
@@ -944,6 +960,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@klkt/$(DEPDIR)/klkt-klkt.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@klkt/$(DEPDIR)/klkt-klkt.moc.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/lkt.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/luka.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/server.Po@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@mkv/$(DEPDIR)/liblektor_la-mkv.Plo@am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@mkv/$(DEPDIR)/liblektor_la-utils.Plo@am__quote@ # am--include-marker
@@ -1458,6 +1475,7 @@ distclean: distclean-am
 	-rm -f klkt/$(DEPDIR)/klkt-klkt.Po
 	-rm -f klkt/$(DEPDIR)/klkt-klkt.moc.Po
 	-rm -f main/$(DEPDIR)/lkt.Po
+	-rm -f main/$(DEPDIR)/luka.Po
 	-rm -f main/$(DEPDIR)/server.Po
 	-rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo
 	-rm -f mkv/$(DEPDIR)/liblektor_la-utils.Plo
@@ -1542,6 +1560,7 @@ maintainer-clean: maintainer-clean-am
 	-rm -f klkt/$(DEPDIR)/klkt-klkt.Po
 	-rm -f klkt/$(DEPDIR)/klkt-klkt.moc.Po
 	-rm -f main/$(DEPDIR)/lkt.Po
+	-rm -f main/$(DEPDIR)/luka.Po
 	-rm -f main/$(DEPDIR)/server.Po
 	-rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo
 	-rm -f mkv/$(DEPDIR)/liblektor_la-utils.Plo
diff --git a/src/main/luka.cpp b/src/main/luka.cpp
new file mode 100644
index 00000000..48f6d820
--- /dev/null
+++ b/src/main/luka.cpp
@@ -0,0 +1,33 @@
+#include <cli/cli.h>
+#include <cli/clifilesession.h>
+
+#include <iostream>
+
+int main()
+{
+    cli::CmdHandler colorCmd;
+    cli::CmdHandler nocolorCmd;
+
+    auto rootMenu = std::make_unique<cli::Menu>("luka");
+    rootMenu->Insert("toto", [](std::ostream &out) noexcept {
+        out << "This is the toto command for the luka client\n";
+    });
+    rootMenu->Insert("color", [](std::ostream &out) noexcept {
+        out << "Enabling colors\n";
+        cli::SetColor();
+    }, "Enable colors in output");
+    rootMenu->Insert("nocolor", [](std::ostream &out) noexcept {
+        out << "Enabling colors\n";
+        cli::SetNoColor();
+    }, "Disable colors in output");
+
+    cli::Cli cli(std::move(rootMenu));
+    cli.ExitAction([] (auto &out) noexcept { out << "The luka client is quitting!\n"; });
+    cli.StdExceptionHandler([](auto& out, const auto& cmd, const std::exception& e) noexcept {
+        out << "Luka caught an exception while handling the command "
+            << cmd << " : " << e.what() << "\n";
+    });
+
+    cli::CliFileSession input(cli);
+    input.Start();
+}
diff --git a/utils/licenses/CLI.LICENSE b/utils/licenses/CLI.LICENSE
new file mode 100644
index 00000000..53f65857
--- /dev/null
+++ b/utils/licenses/CLI.LICENSE
@@ -0,0 +1,28 @@
+The luka client uses the cli header library, that library is distributed with
+the Boost Softaware License version 1.0 that can be seen below.
+
+---
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
-- 
GitLab