diff --git a/.gitignore b/.gitignore
index 94b65a47bef7b53ece3c0622771eed35da3804a1..6eb6012acdd1e3006b1d4c508c0d35bbea10d7a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 venv/*
 __pycache__/*
-dist/*
\ No newline at end of file
+dist/*
+__pycache__/
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..918e8289956f10fd166504696758c5edd60bbe8f 100644
--- a/src/__init__.py
+++ b/src/__init__.py
@@ -0,0 +1 @@
+from main import *
diff --git a/src/bus.py b/src/bus.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ef95aa8835277d8c0ddb7cc376298842f01f8dc
--- /dev/null
+++ b/src/bus.py
@@ -0,0 +1,91 @@
+import collections
+import pydash
+
+
+class BusError(Exception):
+    pass
+
+
+class Bus:
+
+    def __init__(self):
+        self.ID = 0
+        self.providers = {}
+        self.listeners = collections.defaultdict(list)
+
+
+    def provide(self, topic, callback):
+        '''Provide an RPC to be used by the bus.'''
+        self.providers[topic] = callback
+
+
+    def call(self, topic, *args, **kwargs):
+        '''Call an RPC that exists on the bus and any listeners.'''
+
+        # Check if RPC exists, throw error on fault.
+        def non_existant_rpc():
+            return topic not in self.providers
+
+        def raise_RPC_error():
+            raise BusError('No RPC Provider: {}'.format(topic))
+
+
+        # Else call listeners and then return with RPC result.
+        def call_listeners():
+            if topic in self.listeners:
+                for _, listener in self.listeners[topic]:
+                    listener(*args, **kwargs)
+
+        def call_rpc():
+            return self.providers[topic](*args, **kwargs)
+
+        call_listeners_and_rpc = pydash.flow(call_listeners, call_rpc)
+
+        return pydash.cond([
+            (non_existant_rpc, raise_RPC_error),
+            (pydash.stub_true, call_listeners_and_rpc)
+        ])()
+
+
+    def listen(self, topic, callback):
+        '''Listen to a specific topic and receive a callback for when it is called.'''
+        ID = self.ID
+        self.ID += 1
+        self.listeners[topic].append((ID, callback))
+        return ID
+
+
+    def unlisten(self, topic, ID):
+        '''Stop recieving updates for a topic.'''
+
+        def has_id():
+            return any(i == ID for i, _ in self.listeners[topic])
+
+        def remove_id():
+            items = self.listeners[topic]
+            keep = lambda i, _: i != ID
+            self.listeners[topic] = [(a, b) for a, b in items if keep(a, b)]
+
+        return pydash.cond([
+            (has_id, remove_id)
+        ])()
+
+
+    def unprovide(self, topic):
+        '''Unregister a topic.'''
+
+        def has_rpc():
+            return topic in self.providers
+
+        def raise_RPC_error():
+            raise BusError('No RPC Provider: {}'.format(topic))
+
+
+        def remove_RPC():
+            del self.providers[topic]
+
+
+        return pydash.cond([
+            (has_rpc, raise_RPC_error),
+            (pydash.stub_true, remove_RPC)
+        ])()
diff --git a/src/core.py b/src/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ba35da24e4f1e6c6a1208220cc9dce714b1485a
--- /dev/null
+++ b/src/core.py
@@ -0,0 +1,50 @@
+import os
+import configparser
+
+
+class CredentialsException(Exception):
+    '''An exception type for missing credentials.'''
+
+
+def TouchStructure():
+    '''Initializes the .kattcmd file in the users home directory.'''
+    # Create ~/.kattcmd and store:
+    # - Path to .kattisrc file
+    # - Empty list of plugins
+
+    # [options]
+    # kattisrc=~/.kattisrc (check that it exists)
+    # plugins=[]
+
+    user_folder = os.path.expanduser('~')
+    kattcmd_file = os.path.join(user_folder, '.kattcmd')
+    if not os.path.isfile(kattcmd_file):
+        config = configparser.ConfigParser()
+        config['options'] = {
+            'kattisrc': os.path.expanduser('~/.kattisrc'),
+            'plugins': []
+        }
+
+        with open(kattcmd_file, 'w') as configfile:
+            configfile.write(config)
+
+        return True
+    return False
+
+
+def _ListBuiltins():
+    '''Returns a list of all the builtin plugins.'''
+    return []
+
+
+def _ListExternals():
+    '''Returns a list of all user-added plugins.'''
+    config_path = os.path.expanduser('~/.kattcmd')
+    config = configparser.ConfigParser()
+    config.read(config_path)
+    return config['options']['plugins']
+
+
+def ListPlugins():
+    '''Returns a list of all plugins, which is all available commands used by the program.'''
+    return _ListBuiltins() + _ListExternals()
diff --git a/src/doc.py b/src/doc.py
new file mode 100644
index 0000000000000000000000000000000000000000..854eb38644ca5f455193c58096fa64b81f46c910
--- /dev/null
+++ b/src/doc.py
@@ -0,0 +1,6 @@
+
+Interactive = '''If the session should be interactive or not. Brings up the prompt.'''
+
+Command = '''What command to run, cannot be used with --interactive'''
+
+
diff --git a/src/main.py b/src/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..64d49afb5f6bf770710bed2c10412aa5b8e9b6b8
--- /dev/null
+++ b/src/main.py
@@ -0,0 +1,41 @@
+import os
+import pydash
+import click
+
+import doc
+import bus
+import core
+
+
+def interactive_mode(bus, plugins):
+    pass
+
+
+def command_mode(bus, plugins, command):
+    pass
+
+
+@click.command()
+@click.option('--interactive', default=True, type=bool, help=doc.Interactive)
+@click.option('--command', default='', help=doc.Command)
+def main(interactive, command):
+    '''Command line tool for helping with administrative tasks around Kattis.'''
+    the_bus = bus.Bus()
+
+    core.TouchStructure()
+    plugins = core.ListPlugins()
+    for plugin in plugins:
+        plugin.Init(the_bus)
+
+
+    # Check if interactive or not, if yes: run as interactive, if not then run command
+    if interactive:
+        interactive_mode(the_bus, plugins)
+    elif not command:
+        raise ValueError('Either you must specify a command or use interactive mode.')
+    else:
+        command_mode(the_bus, plugins, command)
+
+
+if __name__ == '__main__':
+    main()