diff --git a/DARE.py b/DARE.py
index 12db7ce6ca2b6f93d8d6f0f4152ee51e5554cecc..519fc9f94d59795a31cf680215cef25652de44fb 100755
--- a/DARE.py
+++ b/DARE.py
@@ -4,6 +4,25 @@ File that implements DARE dice-system for my tabletop game
 """
 import dice
 import codecs
+import copy
+
+
+
+"""
+import threading
+
+class KeyboardThread(threading.Thread):
+    def __init__(self, input_cbk = None, name='keyboard-input-thread'):
+        self.input_cbk = input_cbk
+        super(KeyboardThread, self).__init__(name=name)
+        self.start()
+
+    def run(self):
+        while True:
+            self.input_cbk(input()) #waits to get input + Return
+"""
+
+abc = "abcdefghijklmnopqrstuvwxyz"
 
 class SpecialDice(dice.Dice):
     def __init__(self, n, name):
@@ -18,8 +37,6 @@ class SpecialDice(dice.Dice):
         else:
             return self.name+str(self.n)
 
-
-
     def __repr__(self):
         return "SDice object : "+str(self)
 
@@ -104,9 +121,11 @@ def art():
     return None
 
 def line():
-    print("\033[38;5;240m------------------------------------------------\033[0m",end="")
+    print("\033[38;5;240m"+50*"-"+"\033[0m")
     return None
 
+ask_history=[]
+
 def ask(symbol):
     out = input("\033[38;5;217m"+symbol+">\033[0m")
     return out
@@ -114,22 +133,263 @@ def ask(symbol):
 if __name__=="__main__":
 
     from types import SimpleNamespace
+
     ##INITVAR
+    COMMAND_LIST={} 
     S = SimpleNamespace()
     S.PHIdicepool = []
     S.PSIdicepool = []
     S.PDiceTray = dice.DiceTray() #PHI DiceTray
     S.pDiceTray = dice.DiceTray() #pSI DiceTray
     S.Latency = LatencyQueue()
-    S.PHIqueue = [SpecialDice(2,"Ψ"),SpecialDice(6,"Ψ")]
-    S.PSIqueue = [SpecialDice(2,"Ψ"),SpecialDice(6,"Ψ")] 
-    S.thro = ["dx"] 
+    S.PHIqueue = []#SpecialDice(2,"Ψ"),SpecialDice(6,"Ψ")]
+    S.PSIqueue = []#SpecialDice(2,"Ψ"),SpecialDice(6,"Ψ")] 
+    S.thro = [] 
     S.name = ""
-    
-    S.PDiceTray.add(SpecialDice(2,"Ψ"))
+    S.inroll = False
+    SOld1 = None
+    SOld2 = None
+    #S.PDiceTray.add(SpecialDice(2,"Ψ"))
 
+    ##INIT CLI
     art()
 
+    def stupidPrint(mlist):
+        out = ""
+        for e in mlist:
+            out += str(e)+" "
+        return out
+    
+    def printDiceTray():
+    #This function crash if more than 26 dice are thrown
+    #This won't ever happen, right ?
+        i = 0
+        for e in S.PDiceTray:
+            print("\t\033[38;5;217m"+abc[i]+"\033[38;5;10m"+e.name+str(e.n)+"["+"\033[0m"+str(e.value)+"\033[38;5;10m]" ,end="")
+            i+=1
+        print("\n",end="")
+        for e in S.pDiceTray:
+            print("\t\033[38;5;217m"+abc[i]+"\033[38;5;159m"+e.name+str(e.n)+"["+"\033[0m"+str(e.value)+"\033[38;5;159m]" ,end="")
+            i+=1
+        print("\033[0m")
+
+    def printallstat(ShowDiceTray=False):
+        print(S.name+": ")
+        print("\033[38;5;214m PHI \033[38;5;10m [ "+stupidPrint(S.PHIdicepool)+"]\033[0m "
+        ,end="")
+        if not S.PHIqueue: print("") #list is empty
+        else:
+            print("\033[38;5;64m < "+str(S.PHIqueue[0])+"\033[38;5;242m "+stupidPrint(S.PHIqueue[1:])+"<     \033[0m " )   
+        print("\033[38;5;214m PSI \033[38;5;159m [ "+stupidPrint(S.PSIdicepool)+"]\033[0m "
+        ,end="")
+        if not S.PSIqueue: print("") 
+        else:  
+            print("\033[38;5;67m < "+str(S.PSIqueue[0])+"\033[38;5;242m "+stupidPrint(S.PSIqueue[1:])+"<     \033[0m " )
+        if S.Latency.oneQueue or S.Latency.twoQueue: # not empty
+            print("\033[38;5;202 LAT \033[38;5;242m 1[ "+stupidPrint(S.Latency.oneQueue)+"] 2[ "+stupidPrint(S.Latency.twoQueue)+"]")
+        
+        if ShowDiceTray:
+            print("\033[38;5;214m ROLL ")
+            printDiceTray()
+
+        if ShowDiceTray and S.thro:
+            print("\033[38;5;242m DEL  [ "+stupidPrint(S.thro)+"]\033[0m")
+
+
+    def helpDARE(args,S):
+        """
+USAGE : help [command name]
+Show command list. If invoked with command name as 
+an argument, then show command's usage and 
+descritpion.
+        """
+        if len(args) == 0:
+            print("Available commands are :")
+            for e in COMMAND_LIST:
+                print(" - \033[1m"+e+"\033[0m")
+            print(
+"""
+Type \033[1m help <command> \033[0m to get help
+on a specific command"""
+                )
+        else:
+            if args[0] in COMMAND_LIST:
+                print(COMMAND_LIST[args[0]].__doc__[1:])
+            else:
+                print(
+"""
+No command found '"""+str(args[0])+"""'"""
+                )
+        return None
+    
+    def move(args,S):
+        """
+        USAGE : move
+GM-purposed command.
+move the LAT, PSI and PHI queue by one."""
+        S.Latency.move()
+        
+        #PHI dice update
+        if len(S.PHIqueue) == 1:
+            S.PHIdicepool.append(S.PHIqueue.pop())
+        elif S.PHIqueue:#List not empty
+            if S.PHIqueue[0]+S.PHIqueue[1] <= 8: #two first dice are d4
+                S.PHIdicepool.append(S.PHIqueue.pop())
+                S.PHIdicepool.append(S.PHIqueue.pop())
+        
+        #PSI dice update
+        if len(S.PSIqueue) == 1:
+            S.PSIdicepool.append(S.PSIqueue.pop())
+        elif S.PSIqueue:
+                if S.PSIqueue[0]+S.PSIqueue[1] <= 8: #two first dice are d4
+                    S.PSIdicepool.append(S.PSIqueue.pop())
+                    S.PSIdicepool.append(S.PSIqueue.pop())
+        
+        S.PSIdicepool.sort()
+        S.PHIdicepool.sort()
+        return None
+
+    def Nothing(args,S):
+        return None
+    
+    def undo(args,S):
+        """
+USAGE : undo
+GM-purposed command.
+Restore (once) the current state to the previous
+state."""
+        if SOld2 == SOld1:
+            print(
+"""
+Nothing to revert !"""
+            )
+        else:
+            S = SOld2
+            return None
+
+    def exitDARE(args,S):
+        """
+USAGE : exit
+Closes this programm while being polite :)"""
+        print("Goodbye !")
+        exit()
+
+    def show(args,S):
+        """
+USAGE : show
+all info about the current state."""
+        printallstat(S.inroll)
+        return None
+    
+    def cont(args,S):
+        """
+USAGE : cont
+skip a turn.If in a roll, finishes roll."""
+        move()
+        return None
+
+    def roll(args,S):
+        """
+USAGE : roll [dices]   
+
+Does a roll within the possibilities 
+of your dice pool, and show the roll 
+result (with PSI and PHI total sum)
+
+To select a PSI dice, use "p" as a prefix.
+To select a PHI dice, use "P" as a prefix.
+For instance :
+    roll p4 p4 P6
+will roll Ψ4, Ψ4 and Φ6
+
+Once in a roll, you can either \033[1m reroll \033[0m 
+up to half of the throw.
+dice, \033[1m reroll ... add \033[0m to add any 
+dice in your roll, \033[1m reroll ... throw \033[0m to 
+remove any dice of your roll, or\033[1m cont \033[0m to finish 
+your roll. Please type \033[1m help reroll \033[0m
+To better know how rerolls works in DARE.
+
+LAT, PSI and PHI queues and pool are proprely 
+updated.
+    """
+        #check for valid input first
+        if len(args) == 0:
+                print("At least one dice is expected.")
+                return None
+ 
+        for d in args:
+            if d[0] != "p" and d[0] != "P":
+                print("Unknown dice '"+str(d)+"' !")
+                return None
+            try:
+                int(d[1:])
+            except ValueError:
+                print("Unknown dice '"+str(d)+"' !")
+                return None
+        
+        # our input are valid !!!
+        p = []
+        P = [] 
+        for d in args:
+            if d[0] == "p":
+                found=False
+                for e in S.PSIdicepool:
+                    if e.n == int(d[1:]):
+                        S.pDiceTray.add(e)
+                        S.PSIdicepool.remove(e)
+                        found = True
+                        break
+                
+                if not found: #valid dices, but not in pool
+                    print("Not enough Ψ"+str(d[1:])+" !")
+                    S.PSIdicepool = S.PSIdicepool + S.pDiceTray.tray
+                    S.PHIdicepool = S.PHIdicepool + S.PDiceTray.tray
+                    S.pDiceTray.tray = []
+                    S.PDiceTray.tray = [] 
+                    return None
+           
+            elif d[0] == "P":
+                found=False
+                for e in S.PHIdicepool:
+                    if e.n == int(d[1:]):
+                        S.PDiceTray.add(e)
+                        S.PHIdicepool.remove(e)
+                        found = True
+                        break
+
+                if not found:
+                    print("Not enough Φ"+str(d[1:])+" !")
+                    S.PSIdicepool = S.PSIdicepool + S.pDiceTray.tray
+                    S.PHIdicepool = S.PHIdicepool + S.PDiceTray.tray
+                    S.pDiceTray.tray = []
+                    S.PDiceTray.tray = [] 
+                    return None
+        
+        S.pDiceTray.tray.sort()
+        S.PDiceTray.tray.sort()
+        S.pDiceTray.rollall()
+        S.PDiceTray.rollall()
+        S.inroll = True
+        
+        print(str(S.name)+" does a roll !")
+        printDiceTray()
+
+        return None
+
+    COMMAND_LIST = {
+            "exit" : exitDARE,
+            "help" : helpDARE,
+            "show" : show,
+            "undo" : undo,
+            "redo" : Nothing,
+            "move" : move,
+            "roll" : roll,
+            "cont" : Nothing,
+            "reroll" : Nothing,
+            "throw" : Nothing, 
+    }
+
     ## get init param :
     print(
 """
@@ -166,42 +426,30 @@ Please give your PSI dice pool
         S.PSIdicepool.append(SpecialDice(dval,"Ψ"))
     S.PSIdicepool.sort()
 
-    def stupidPrint(mlist):
-        out = ""
-        for e in mlist:
-            out += str(e)+" "
-        return out
-    
-    def printDiceTray():
-        print(" ROL  [ \033[38;5;214m"+stupidPrint(S.PDiceTray)+"\033[38;5;159m"+stupidPrint(S.pDiceTray)+"\033[0m] ")
-
-    def printallstat(ShowDiceTray=False):
-        print("\033[38;5;214m PHI \033[38;5;10m [ "+stupidPrint(S.PHIdicepool)+"]\033[0m "
-        ,end="")
-        if not S.PHIqueue: print("") #list is empty
-        else:
-            print("\033[38;5;64m < "+str(S.PHIqueue[0])+"\033[38;5;242m "+stupidPrint(S.PHIqueue[1:])+"<     \033[0m " )   
-        print("\033[38;5;214m PSI \033[38;5;159m [ "+stupidPrint(S.PSIdicepool)+"]\033[0m "
-        ,end="")
-        if not S.PSIqueue: print("") 
-        else:  
-            print("\033[38;5;67m < "+str(S.PSIqueue[0])+"\033[38;5;242m "+stupidPrint(S.PSIqueue[1:])+"<     \033[0m " )
-        if S.Latency.oneQueue or S.Latency.twoQueue: # not empty
-            print("\033[38;5;202 LAT \033[38;5;242m 1[ "+stupidPrint(S.Latency.oneQueue)+"] 2[ "+stupidPrint(S.Latency.twoQueue)+"]")
-        
-        if ShowDiceTray:
-            printDiceTray()
-
-        if S.thro:
-            print("\033[38;5;242m DEL  [ "+stupidPrint(S.thro)+"]\033[0m")
     line() 
+    printallstat()
     print("\nWelcome "+S.name+" !")
-    while True:
-        printallstat(True)
-        print(
+    print(
 """
 Please type your command. Type \033[1m help \033[0m
 To see available command or \033[1m help <command> \033[0m
 To get an explanation of a command"""
         )
-        ask("command?")
+    line()
+    ##Main While Command
+    SOld1 = S #error
+    while True:
+        SOld2 = copy.deepcopy(SOld1) #undo mgmt
+        uinput = ask("command?")
+        SOld1 = copy.deepcopy(S) #undo mgmt
+        args = uinput.split(" ")
+        if args[0] in COMMAND_LIST: COMMAND_LIST[args[0]](args[1:],S)
+        elif args[0] != "":
+            print(
+"""
+No command found '"""+str(args[0])+"""'
+try \033[1mhelp\033[0m to see command list."""
+            )
+
+        line()
+
diff --git a/__pycache__/dice.cpython-311.pyc b/__pycache__/dice.cpython-311.pyc
index 3125ba5cfabf4eca4a148ec883ebde2d7486442f..9d4168d016cf1da2c3a01b140b52486a5d0f4733 100644
Binary files a/__pycache__/dice.cpython-311.pyc and b/__pycache__/dice.cpython-311.pyc differ