diff --git a/project.properties b/project.properties
index c4f1cfe..f990fd0 100644
--- a/project.properties
+++ b/project.properties
@@ -1,4 +1,5 @@
+#Thu, 08 Aug 2013 19:52:35 -0500
 lib.local=true
 name=timestamper-cli
 version=0.1
-build.number=0
+build.number=29
diff --git a/src/main/com/jdblabs/timestamper/cli/TimeStamperCLI.groovy b/src/main/com/jdblabs/timestamper/cli/TimeStamperCLI.groovy
index fda887d..0629c1d 100644
--- a/src/main/com/jdblabs/timestamper/cli/TimeStamperCLI.groovy
+++ b/src/main/com/jdblabs/timestamper/cli/TimeStamperCLI.groovy
@@ -84,48 +84,95 @@ public class TimeStamperCLI {
 
         //out.println ""
         def currentMarker = timeline.getLastMarker(new Date())
+        Reader reader = new InputStreamReader(sin)
 
         boolean running = true
 
-        def reader = new NonBlockingReader(sin)
-        def readerThread = new Thread(reader)
-        readerThread.start()
+        def readNotes = {
+            out.println(ansi().fg(YELLOW).
+                a("Notes (end with EOF or a blank line):").reset())
 
-        def blockingReadLine = {
-            String line = null;
-            while (line == null && readerThread.isAlive()) {
-                line = reader.readLine()
+            String notes = ""
+            String line = null
+            line = reader.readLine()
 
-                Thread.sleep(200) }
+            while(line != "" && line != "EOF" && line != null) {
+                notes += line + EOL
+                line = reader.readLine() }
 
-            return line }
+            return notes
+        }
 
         String line = null
-        println ""
-        while (running && readerThread.isAlive()) {
 
-            // Refresh the display
-            out.print(
-                ansi().saveCursorPosition().
-                cursorUp(1).eraseLine(Erase.ALL).fgBright(CYAN).
-                a(Timeline.shortFormat.format(currentMarker.timestamp) + " ").
-                fg(GREEN).a("(" + getDuration(currentMarker) + ") ").
-                fg(WHITE).a(currentMarker.mark).a(EOL).restorCursorPosition()
-                )
+        while (running) {
+
+            out.println formatMarker(currentMarker)
+            out.print(ansi().fg(YELLOW).a("> ").reset())
+            out.flush();
 
             // Handle user input
-            if ((line = reader.readLine()) != null) {
-                if (line =~ /quit|exit/) running = false }
-                
-            Thread.sleep(200); }
+            line = reader.readLine()
+            
+            switch (line) {
+
+                case null:
+                case ~/quit|exit|\u0004/:
+                    running = false;
+                    break
+
+                case ~/r|refresh|^$/:
+                    out.print(ansi().eraseLine().cursorUp(2).eraseLine())
+                    break
+
+                case ~/n|new/:
+
+                    // Read mark
+                    out.println(ansi().fg(YELLOW).a("New timestamp:").reset())
+                    String mark = reader.readLine()
+
+                    // Read notes
+                    String notes = readNotes();
+
+                    // Create marker
+                    currentMarker = new TimelineMarker(new Date(), mark, notes)
+                    timeline.addMarker(currentMarker)
+                    if (timelineProperties.persistOnUpdate)
+                        timelineProperties.save()
+                    break
+
+                case ~/h|help/:
+                    out.println(ansi().eraseLine().
+                        fg(RED).a("Not yet implemented."));
+                    break
+
+                case ~/l|list|history/:
+                    out.println(ansi().eraseLine().
+                        fg(RED).a("Not yet implemented."));
+                    break
+
+                case ~/s|save/:
+                    timelineProperties.save()
+                    break
+
+                default:
+                    String notes = readNotes()
+                    currentMarker = new TimelineMarker(new Date(), line, notes)
+                    timeline.addMarker(currentMarker)
+                    if (timelineProperties.persistOnUpdate)
+                        timelineProperties.save()
+                    break
+            }
+        }
 
-        if (readerThread.isAlive()) {
-            readerThread.interrupt();
-            readerThread.join(500);
-            if (readerThread.isAlive()) readerThread.stop(); }
     }
 
-    protected String getDuration(TimelineMarker tm) {
+    protected static String formatMarker(TimelineMarker tm) {
+        return ansi().fgBright(CYAN).
+            a(Timeline.shortFormat.format(tm.timestamp) + " ").fg(GREEN).
+            a("(" + getDuration(tm) + ") ").fg(WHITE).a(tm.mark).toString() }
+
+    protected static String getDuration(TimelineMarker tm) {
         Date currentTime = new Date()
         long seconds = currentTime.time - tm.timestamp.time
         seconds /= 1000
@@ -144,23 +191,4 @@ public class TimeStamperCLI {
 
         return sb.toString() }
 
-    class NonBlockingReader implements Runnable {
-
-        private Reader rin
-        private LinkedList buffer = []
-
-        public void run() {
-            String line = null
-            try {
-                while((line = rin.readLine()) != null &&
-                    !Thread.currentThread().isInterrupted())
-                    storeLine(line) }
-            catch (InterruptedException ie) { Thread.currentThread().interrupt() } }
-
-        public synchronized String readLine() { return buffer.poll() }
-        private synchronized void storeLine(String line) { buffer << line }
-
-        public NonBlockingReader(def sin) {
-            this.rin = new InputStreamReader(sin) }
-    }
 }