docs: function completeOpen() {
        if (process.stdout.isTTY) {
          rl = readline.createInterface(process.stdin, process.stdout);
          rl.setPrompt(chalk.dim('TinyG# '), 'TinyG# '.length);
          rl.prompt();
  
          // WARNING WARNING WARNING -- using the internals of node readline!!
          //
          // We need to override the default behavior for a few keys.
          // So, we tell stdin to REMOVE all of the listeners to the 'keypress'
          // event, then we will be the only listener. If we don't have a special
          // behavior for the pressed key, then we pass it on to the readline.
          //
          // To avoid using internals too much, we'll snarf in the listeners,
          // store them away, and then call them ourselves.
          var old_keypress_listeners = process.stdin.listeners('keypress');
  
          process.stdin.removeAllListeners('keypress');
          process.stdin.on('keypress', function (ch, k) {
            if (k && k.ctrl) {
              if (k.name == 'd') {
                if (sendingFile) {
                  log(util.format(">>^d\n"));
                  g.write('\x04'); // send the ^d
                }
                return;
              }
              else if (k.name == 'c') {
                var e = util.format("## Received CTRL-C in State '%s' -- sending CTRL-D and exiting.\n", STAT_CODES[latestMotionStatus]);
                log(e);
                if (interactive) {
                  process.stdout.write(chalk.dim(e));
                }
  
                tryToQuit()
                return;
              }
  
            // Single character commands get sent immediately
          } else if (ch && ch.match(/^[!~%]/)) {
              log(util.format(">>%s\n", ch));
              g.write(ch);
              return;
            }
  
            for (var i = 0; i < old_keypress_listeners.length; i++) {
              old_keypress_listeners[i](ch,k);
            }
          })
  
          rl.on('line', function(line) {
            parseCommand(line);
            if (rl !== null) {
              rl.prompt(true);
            }
          });
  
          rl.on('close', function() {
            g.close();
            rl = null;
          });
  
          var leftText = "Progress |";
          var rightText = "|   0% ";
  
          var status = {};
  
          g.on('statusChanged', function(st) {
            for(var prop in st) {
              status[prop] = st[prop];
            }
  
            if (status.stat) {
              latestMotionStatus = status.stat;
            }
  
            if (interactive) {
              readline.moveCursor(process.stdout, 0, -1);
              readline.clearLine(process.stdout, 0);
  
              process.stdout.write(
                sprintf("\rPos: X=%4.2f Y=%4.2f Z=%4.2f A=%4.2f Vel:%4.2f",
                  status.posx||0,
                  status.posy||0,
                  status.posz||0,
                  status.posa||0,
                  status.vel||0
                )
              );
              if (status.he1t) {
                process.stdout.write(
                  sprintf(" He1=%4.2fºC/%4.2fºC",
                    status.he1t||0,
                    status.he1st||0
                  )
                );
              }
              process.stdout.write(
                sprintf(" (%s)\n",
                  STAT_CODES[status.stat] || 'Stopped'
                )
              );
  
              if (!sendingFile) {
                process.stdout.write(
                  JSON.stringify(status, null, 0) + "\n"
                );
  
                rl.prompt(true);
              }
            }
  
  
            if (st.line && sendingFile) {
              if (st.line > maxLineNumber) {
                maxLineNumber = st.line;
              }
              // clear the whole line.
              // readline.moveCursor(process.stdout, 0, -1);
              // readline.clearLine(process.stdout, 0);
              process.stdout.write("\r");
  
              var maxWidth = process.stdout.columns;
              var paddingWidth = leftText.length + rightText.length;
              var barWidth = (maxWidth - paddingWidth) * (st.line/maxLineNumber);
              var barLeft = (maxWidth - paddingWidth);
  
              process.stdout.write(leftText);
              // console.error("maxWidth: %d, paddingWidth: %d, sent: %d, lines: %d", maxWidth, paddingWidth, b.sent, b.lines);
              while (barWidth > 1.0) {
                process.stdout.write('=');
                barWidth = barWidth - 1.0;
                barLeft--;
              }
              if (barWidth > 0.6) {
                process.stdout.write('+');
                barLeft--;
              } else if (barWidth > 0.3) {
                process.stdout.write('-');
                barLeft--;
              }
              while (barLeft-- > 0) {
                process.stdout.write('_');
              }
  
              process.stdout.write("| ")
              var percent = ((st.line/maxLineNumber) * 100);
              process.stdout.write(sprintf("%3.0f%%", percent));
  
              // if (process.stderr.isTTY) {
              //   process.stdout.write("\n")
              // } else {
                process.stdout.write("\r")
              // }
            } // if st.line
            // rl.prompt(true);
          }); // g.on('statusChanged', ... )
        }
  
        if (args.gcode || !process.stdin.isTTY) {
          sendFile(args.gcode || process.stdin, true);
        }
      }