diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..c635cfb
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,16 @@
+Metadata-Version: 1.1
+Name: aiocmd
+Version: 0.1.5
+Summary: Coroutine-based CLI generator using prompt_toolkit
+Home-page: http://github.com/KimiNewt/aiocmd
+Author: Dor Green
+Author-email: dorgreen1@gmail.com
+License: MIT
+Description: UNKNOWN
+Keywords: asyncio,cmd
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..83d09fc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+# aiocmd
+Coroutine-based CLI generator using prompt_toolkit, similarly to the built-in cmd module.
+
+## How to install?
+Simply use `pip3 install aiocmd`
+
+## How to use?
+To use, inherit from the `PromptToolkitCmd` class and implement the `do_<action>` for each command.
+
+Each command can receive arguments and optional (keyword) arguments. You then must run the `run()` coroutine to start the CLI.
+
+For instance:
+```python
+import asyncio
+
+from aiocmd import aiocmd
+
+
+class MyCLI(aiocmd.PromptToolkitCmd):
+
+    def do_my_action(self):
+        """This will appear in help text"""
+        print("You ran my action!")
+        
+    def do_add(self, x, y):
+        print(int(x) + int(y))
+
+    async def do_sleep(self, sleep_time=1):
+        await asyncio.sleep(int(sleep_time))
+        
+        
+if __name__ == "__main__":
+    asyncio.get_event_loop().run_until_complete(MyCLI().run())
+``` 
+
+Will create this CLI:
+
+![CLIImage](./docs/image1.png)
+
+## Extra features
+
+You can implement a custom completion for each command by implementing `_<action>_completions`. 
+
+For example, to complete a single-digit number for the `add` action:
+
+```python
+class MyCLI(aiocmd.PromptToolkitCmd):
+    
+    def _add_completions(self):
+        return WordCompleter([str(i) for i in range(9)])
+```
+
+![CLIImage](./docs/image2.png)
+
+You can also set a custom `prompt` and `aliases` parameters for the class (example in docs).
diff --git a/aiocmd.egg-info/PKG-INFO b/aiocmd.egg-info/PKG-INFO
new file mode 100644
index 0000000..c635cfb
--- /dev/null
+++ b/aiocmd.egg-info/PKG-INFO
@@ -0,0 +1,16 @@
+Metadata-Version: 1.1
+Name: aiocmd
+Version: 0.1.5
+Summary: Coroutine-based CLI generator using prompt_toolkit
+Home-page: http://github.com/KimiNewt/aiocmd
+Author: Dor Green
+Author-email: dorgreen1@gmail.com
+License: MIT
+Description: UNKNOWN
+Keywords: asyncio,cmd
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
diff --git a/aiocmd.egg-info/SOURCES.txt b/aiocmd.egg-info/SOURCES.txt
new file mode 100644
index 0000000..29a5f94
--- /dev/null
+++ b/aiocmd.egg-info/SOURCES.txt
@@ -0,0 +1,10 @@
+README.md
+setup.py
+aiocmd/__init__.py
+aiocmd/aiocmd.py
+aiocmd/nested_completer.py
+aiocmd.egg-info/PKG-INFO
+aiocmd.egg-info/SOURCES.txt
+aiocmd.egg-info/dependency_links.txt
+aiocmd.egg-info/requires.txt
+aiocmd.egg-info/top_level.txt
\ No newline at end of file
diff --git a/aiocmd.egg-info/dependency_links.txt b/aiocmd.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/aiocmd.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/aiocmd.egg-info/requires.txt b/aiocmd.egg-info/requires.txt
new file mode 100644
index 0000000..15b0818
--- /dev/null
+++ b/aiocmd.egg-info/requires.txt
@@ -0,0 +1,2 @@
+prompt_toolkit>=2.0.9
+packaging
diff --git a/aiocmd.egg-info/top_level.txt b/aiocmd.egg-info/top_level.txt
new file mode 100644
index 0000000..399c3ba
--- /dev/null
+++ b/aiocmd.egg-info/top_level.txt
@@ -0,0 +1 @@
+aiocmd
diff --git a/aiocmd/__init__.py b/aiocmd/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/aiocmd/aiocmd.py b/aiocmd/aiocmd.py
new file mode 100644
index 0000000..36bfc74
--- /dev/null
+++ b/aiocmd/aiocmd.py
@@ -0,0 +1,172 @@
+import asyncio
+import inspect
+import shlex
+import signal
+import sys
+
+import packaging.version
+import prompt_toolkit
+from prompt_toolkit import PromptSession
+from prompt_toolkit.completion import WordCompleter
+from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.patch_stdout import patch_stdout
+
+try:
+    from prompt_toolkit.completion.nested import NestedCompleter
+except ImportError:
+    from aiocmd.nested_completer import NestedCompleter
+
+
+def _is_prompt_toolkit3():
+    return packaging.version.parse(prompt_toolkit.__version__) >= packaging.version.parse("3.0")
+
+
+class ExitPromptException(Exception):
+    pass
+
+
+class PromptToolkitCmd:
+    """Baseclass for custom CLIs
+
+    Works similarly to the built-in Cmd class. You can inherit from this class and implement:
+        - do_<action> - This will add the "<action>" command to the cli.
+                        The method may receive arguments (required) and keyword arguments (optional).
+        - _<action>_completions - Returns a custom Completer class to use as a completer for this action.
+    Additionally, the user cant change the "prompt" variable to change how the prompt looks, and add
+    command aliases to the 'aliases' dict.
+    """
+    ATTR_START = "do_"
+    prompt = "$ "
+    doc_header = "Documented commands:"
+    aliases = {"?": "help", "exit": "quit"}
+
+    def __init__(self, ignore_sigint=True):
+        self.completer = self._make_completer()
+        self.session = None
+        self._ignore_sigint = ignore_sigint
+        self._currently_running_task = None
+
+    async def run(self):
+        if self._ignore_sigint and sys.platform != "win32":
+            asyncio.get_event_loop().add_signal_handler(signal.SIGINT, self._sigint_handler)
+        self.session = PromptSession(enable_history_search=True, key_bindings=self._get_bindings())
+        try:
+            with patch_stdout():
+                await self._run_prompt_forever()
+        finally:
+            if self._ignore_sigint and sys.platform != "win32":
+                asyncio.get_event_loop().remove_signal_handler(signal.SIGINT)
+            self._on_close()
+
+    async def _run_prompt_forever(self):
+        while True:
+            try:
+                if _is_prompt_toolkit3():
+                    result = await self.session.prompt_async(self.prompt, completer=self.completer)
+                else:
+                    # This is done because old versions of prompt toolkit don't support Python 3.5.
+                    # When we deprecate 3.5, this can be removed.
+                    from prompt_toolkit.eventloop import use_asyncio_event_loop
+                    use_asyncio_event_loop()
+                    result = await self.session.prompt(self.prompt, async_=True, completer=self.completer)
+            except EOFError:
+                return
+
+            if not result:
+                continue
+            args = shlex.split(result)
+            if args[0] in self.command_list:
+                try:
+                    self._currently_running_task = asyncio.ensure_future(
+                        self._run_single_command(args[0], args[1:]))
+                    await self._currently_running_task
+                except asyncio.CancelledError:
+                    print()
+                    continue
+                except ExitPromptException:
+                    return
+            else:
+                print("Command %s not found!" % args[0])
+
+    def _sigint_handler(self):
+        if self._currently_running_task:
+            self._currently_running_task.cancel()
+
+    def _get_bindings(self):
+        bindings = KeyBindings()
+        bindings.add("c-c")(lambda event: self._interrupt_handler(event))
+        return bindings
+
+    async def _run_single_command(self, command, args):
+        command_real_args, command_real_kwargs = self._get_command_args(command)
+        if len(args) < len(command_real_args) or len(args) > (len(command_real_args)
+                                                              + len(command_real_kwargs)):
+            print("Bad command args. Usage: %s" % self._get_command_usage(command, command_real_args,
+                                                                          command_real_kwargs))
+            return
+
+        try:
+            com_func = self._get_command(command)
+            if asyncio.iscoroutinefunction(com_func):
+                await com_func(*args)
+            else:
+                com_func(*args)
+            return
+        except (ExitPromptException, asyncio.CancelledError):
+            raise
+        except Exception as ex:
+            print("Command failed: ", ex)
+
+    def _interrupt_handler(self, event):
+        event.cli.current_buffer.text = ""
+
+    def _make_completer(self):
+        return NestedCompleter({com: self._completer_for_command(com) for com in self.command_list})
+
+    def _completer_for_command(self, command):
+        if not hasattr(self, "_%s_completions" % command):
+            return WordCompleter([])
+        return getattr(self, "_%s_completions" % command)()
+
+    def _get_command(self, command):
+        if command in self.aliases:
+            command = self.aliases[command]
+        return getattr(self, self.ATTR_START + command)
+
+    def _get_command_args(self, command):
+        args = [param for param in inspect.signature(self._get_command(command)).parameters.values()
+                if param.default == param.empty]
+        kwargs = [param for param in inspect.signature(self._get_command(command)).parameters.values()
+                  if param.default != param.empty]
+        return args, kwargs
+
+    def _get_command_usage(self, command, args, kwargs):
+        return ("%s %s %s" % (command,
+                              " ".join("<%s>" % arg for arg in args),
+                              " ".join("[%s]" % kwarg for kwarg in kwargs),
+                              )).strip()
+
+    @property
+    def command_list(self):
+        return [attr[len(self.ATTR_START):]
+                for attr in dir(self) if attr.startswith(self.ATTR_START)] + list(self.aliases.keys())
+
+    def do_help(self):
+        print()
+        print(self.doc_header)
+        print("=" * len(self.doc_header))
+        print()
+
+        get_usage = lambda command: self._get_command_usage(command, *self._get_command_args(command))
+        max_usage_len = max([len(get_usage(command)) for command in self.command_list])
+        for command in sorted(self.command_list):
+            command_doc = self._get_command(command).__doc__
+            print(("%-" + str(max_usage_len + 2) + "s%s") % (get_usage(command), command_doc or ""))
+
+    def do_quit(self):
+        """Exit the prompt"""
+        raise ExitPromptException()
+
+    def _on_close(self):
+        """Optional hook to call on closing the cmd"""
+        pass
diff --git a/aiocmd/nested_completer.py b/aiocmd/nested_completer.py
new file mode 100644
index 0000000..95cca10
--- /dev/null
+++ b/aiocmd/nested_completer.py
@@ -0,0 +1,97 @@
+"""
+Nestedcompleter for completion of hierarchical data structures.
+"""
+from typing import Dict, Iterable, Mapping, Optional, Set, Union
+
+from prompt_toolkit.completion import CompleteEvent, Completer, Completion
+from prompt_toolkit.completion.word_completer import WordCompleter
+from prompt_toolkit.document import Document
+
+__all__ = [
+    'NestedCompleter'
+]
+
+NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
+
+
+class NestedCompleter(Completer):
+    """
+    Completer which wraps around several other completers, and calls any the
+    one that corresponds with the first word of the input.
+    By combining multiple `NestedCompleter` instances, we can achieve multiple
+    hierarchical levels of autocompletion. This is useful when `WordCompleter`
+    is not sufficient.
+    If you need multiple levels, check out the `from_nested_dict` classmethod.
+    """
+    def __init__(self, options: Dict[str, Optional[Completer]],
+                 ignore_case: bool = True) -> None:
+
+        self.options = options
+        self.ignore_case = ignore_case
+
+    def __repr__(self) -> str:
+        return 'NestedCompleter(%r, ignore_case=%r)' % (self.options, self.ignore_case)
+
+    @classmethod
+    def from_nested_dict(cls, data: NestedDict) -> 'NestedCompleter':
+        """
+        Create a `NestedCompleter`, starting from a nested dictionary data
+        structure, like this:
+        .. code::
+            data = {
+                'show': {
+                    'version': None,
+                    'interfaces': None,
+                    'clock': None,
+                    'ip': {'interface': {'brief'}}
+                },
+                'exit': None
+                'enable': None
+            }
+        The value should be `None` if there is no further completion at some
+        point. If all values in the dictionary are None, it is also possible to
+        use a set instead.
+        Values in this data structure can be a completers as well.
+        """
+        options = {}
+        for key, value in data.items():
+            if isinstance(value, Completer):
+                options[key] = value
+            elif isinstance(value, dict):
+                options[key] = cls.from_nested_dict(value)
+            elif isinstance(value, set):
+                options[key] = cls.from_nested_dict({item: None for item in value})
+            else:
+                assert value is None
+                options[key] = None
+
+        return cls(options)
+
+    def get_completions(self, document: Document,
+                        complete_event: CompleteEvent) -> Iterable[Completion]:
+        # Split document.
+        text = document.text_before_cursor.lstrip()
+
+        # If there is a space, check for the first term, and use a
+        # subcompleter.
+        if ' ' in text:
+            first_term = text.split()[0]
+            completer = self.options.get(first_term)
+
+            # If we have a sub completer, use this for the completions.
+            if completer is not None:
+                remaining_text = document.text[len(first_term):].lstrip()
+                move_cursor = len(document.text) - len(remaining_text)
+
+                new_document = Document(
+                    remaining_text,
+                    cursor_position=document.cursor_position - move_cursor)
+
+                for c in completer.get_completions(new_document, complete_event):
+                    yield c
+
+        # No space in the input: behave exactly like `WordCompleter`.
+        else:
+            completer = WordCompleter(list(self.options.keys()), ignore_case=self.ignore_case)
+            for c in completer.get_completions(document, complete_event):
+                yield c
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..8bfd5a1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..3cac3c1
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+from setuptools import setup, find_packages
+
+setup(name='aiocmd',
+      packages=find_packages("."),
+      version='0.1.5',
+      author='Dor Green',
+      author_email='dorgreen1@gmail.com',
+      description='Coroutine-based CLI generator using prompt_toolkit',
+      url='http://github.com/KimiNewt/aiocmd',
+      keywords=['asyncio', 'cmd'],
+      license='MIT',
+      install_requires=[
+          'prompt_toolkit>=2.0.9', 'packaging'
+      ],
+      classifiers=[
+          'License :: OSI Approved :: MIT License',
+
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.5',
+          'Programming Language :: Python :: 3.6',
+          'Programming Language :: Python :: 3.7'
+      ])