At a glance: that function is way too long and deeply nested. Three levels of loop/branch nesting is, in my experience, the most that any function should have; and the more nested it is the shorter it should be.
Consider extracting branch and loop bodies to separate named functions.
Your code structure also seems to be “determine what command to execute” (add / list / etc), then “execute said command”. I’d make this explicit, have the regexp parsing of an input line in a separate function that returns the command type and parameters. Dispatching this would be a natural use for match
over your own Command type. (That said, in a realistic CLI you might need the commands available to be extensible so don’t get too married to this pattern. I’m guessing you’ll get to dynamic polymorphism later in the book.)