add cli_logging.dart
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4cd8cd..7d05c61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
 ## unreleased
 
 - Use the new `Platform.resolvedExecutable` API to locate the SDK
+- add the `cli_logging.dart` library - some utilities to help cli tools
+  display output
 
 ## 0.0.1+3
 
diff --git a/example/main.dart b/example/main.dart
new file mode 100644
index 0000000..0cc6846
--- /dev/null
+++ b/example/main.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:cli_util/cli_logging.dart';
+
+main(List<String> args) async {
+  bool verbose = args.contains('-v');
+  Logger logger = verbose ? new Logger.verbose() : new Logger.standard();
+
+  logger.stdout('Hello world!');
+  logger.trace('message 1');
+  await new Future.delayed(new Duration(milliseconds: 200));
+  logger.trace('message 2');
+  logger.trace('message 3');
+
+  Progress progress = logger.progress('doing some work');
+  await new Future.delayed(new Duration(seconds: 2));
+  progress.finish(showTiming: true);
+
+  logger.stdout('All ${logger.ansi.emphasized('done')}.');
+  logger.flush();
+}
diff --git a/lib/cli_logging.dart b/lib/cli_logging.dart
new file mode 100644
index 0000000..72ec89a
--- /dev/null
+++ b/lib/cli_logging.dart
@@ -0,0 +1,266 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library contains functionality to help command-line utilities to easily
+/// create aesthetic output.
+
+import 'dart:async';
+import 'dart:io' as io;
+
+/// create aesthetic output.
+
+/// A small utility class to make it easier to work with common ANSI escape
+/// sequences.
+class Ansi {
+  /// Return whether the current stdout terminal supports ANSI escape sequences.
+  static bool get terminalSupportsAnsi {
+    return io.stdout.supportsAnsiEscapes &&
+        io.stdioType(io.stdout) == io.StdioType.TERMINAL;
+  }
+
+  final bool useAnsi;
+
+  Ansi(this.useAnsi);
+
+  String get cyan => _code('\u001b[36m');
+  String get green => _code('\u001b[32m');
+  String get magenta => _code('\u001b[35m');
+  String get red => _code('\u001b[31m');
+  String get yellow => _code('\u001b[33m');
+  String get blue => _code('\u001b[34m');
+  String get gray => _code('\u001b[1;30m');
+  String get noColor => _code('\u001b[39m');
+
+  String get none => _code('\u001b[0m');
+
+  String get bold => _code('\u001b[1m');
+
+  String get backspace => '\b';
+
+  String get bullet => io.stdout.supportsAnsiEscapes ? '•' : '-';
+
+  /// Display [message] in an emphasized format.
+  String emphasized(String message) => '$bold$message$none';
+
+  /// Display [message] in an subtle (gray) format.
+  String subtle(String message) => '$gray$message$none';
+
+  /// Display [message] in an error (red) format.
+  String error(String message) => '$red$message$none';
+
+  String _code(String ansiCode) => useAnsi ? ansiCode : '';
+}
+
+/// An abstract representation of a [Logger] - used to pretty print errors,
+/// standard status messages, trace level output, and indeterminate progress.
+abstract class Logger {
+  /// Create a normal [Logger]; this logger will not display trace level output.
+  factory Logger.standard({Ansi ansi}) => new _StandardLogger(ansi: ansi);
+
+  /// Create a [Logger] that will display trace level output.
+  factory Logger.verbose({Ansi ansi}) => new _VerboseLogger(ansi: ansi);
+
+  Ansi get ansi;
+
+  /// Print an error message.
+  void stderr(String message);
+
+  /// Print a standard status message.
+  void stdout(String message);
+
+  /// Print trace output.
+  void trace(String message);
+
+  /// Start an indeterminate progress display.
+  Progress progress(String message);
+  void _progressFinished(Progress progress);
+
+  /// Flush any un-written output.
+  void flush();
+}
+
+/// A handle to an indeterminate progress display.
+abstract class Progress {
+  final String message;
+  final Stopwatch _stopwatch;
+
+  Progress._(this.message) : _stopwatch = new Stopwatch()..start();
+
+  Duration get elapsed => _stopwatch.elapsed;
+
+  /// Finish the indeterminate progress display.
+  void finish({String message, bool showTiming});
+
+  /// Cancel the indeterminate progress display.
+  void cancel();
+}
+
+class _StandardLogger implements Logger {
+  Ansi ansi;
+
+  _StandardLogger({this.ansi}) {
+    ansi ??= new Ansi(Ansi.terminalSupportsAnsi);
+  }
+
+  Progress _currentProgress;
+
+  void stderr(String message) {
+    io.stderr.writeln(message);
+    _currentProgress?.cancel();
+    _currentProgress = null;
+  }
+
+  void stdout(String message) {
+    print(message);
+    _currentProgress?.cancel();
+    _currentProgress = null;
+  }
+
+  void trace(String message) {}
+
+  Progress progress(String message) {
+    _currentProgress?.cancel();
+    _currentProgress = null;
+
+    Progress progress = ansi.useAnsi
+        ? new _AnsiProgress(this, ansi, message)
+        : new _SimpleProgress(this, message);
+    _currentProgress = progress;
+    return progress;
+  }
+
+  void _progressFinished(Progress progress) {
+    if (_currentProgress == progress) {
+      _currentProgress = null;
+    }
+  }
+
+  void flush() {}
+}
+
+class _SimpleProgress extends Progress {
+  final Logger logger;
+
+  _SimpleProgress(this.logger, String message) : super._(message) {
+    logger.stdout('$message...');
+  }
+
+  @override
+  void cancel() {
+    logger._progressFinished(this);
+  }
+
+  @override
+  void finish({String message, bool showTiming}) {
+    logger._progressFinished(this);
+  }
+}
+
+class _AnsiProgress extends Progress {
+  static const List<String> kAnimationItems = const ['/', '-', '\\', '|'];
+
+  final Logger logger;
+  final Ansi ansi;
+
+  int _index = 0;
+  Timer _timer;
+
+  _AnsiProgress(this.logger, this.ansi, String message) : super._(message) {
+    io.stdout.write('${message}...  '.padRight(40));
+
+    _timer = new Timer.periodic(new Duration(milliseconds: 80), (t) {
+      _index++;
+      _updateDisplay();
+    });
+
+    _updateDisplay();
+  }
+
+  @override
+  void cancel() {
+    if (_timer.isActive) {
+      _timer.cancel();
+      _updateDisplay(cancelled: true);
+      logger._progressFinished(this);
+    }
+  }
+
+  @override
+  void finish({String message, bool showTiming: false}) {
+    if (_timer.isActive) {
+      _timer.cancel();
+      _updateDisplay(isFinal: true, message: message, showTiming: showTiming);
+      logger._progressFinished(this);
+    }
+  }
+
+  void _updateDisplay(
+      {bool isFinal: false,
+      bool cancelled: false,
+      String message,
+      bool showTiming: false}) {
+    String char = kAnimationItems[_index % kAnimationItems.length];
+    if (isFinal || cancelled) {
+      char = ' ';
+    }
+    io.stdout.write('${ansi.backspace}${char}');
+    if (isFinal || cancelled) {
+      if (message != null) {
+        io.stdout.write(message);
+      } else if (showTiming) {
+        String time = (elapsed.inMilliseconds / 1000.0).toStringAsFixed(1);
+        io.stdout.write('${time}s');
+      }
+      io.stdout.writeln();
+    }
+  }
+}
+
+class _VerboseLogger implements Logger {
+  Ansi ansi;
+  Stopwatch _timer;
+
+  String _previousErr;
+  String _previousMsg;
+
+  _VerboseLogger({this.ansi}) {
+    ansi ??= new Ansi(Ansi.terminalSupportsAnsi);
+    _timer = new Stopwatch()..start();
+  }
+
+  void stderr(String message) {
+    flush();
+    _previousErr = '${ansi.red}$message${ansi.none}';
+  }
+
+  void stdout(String message) {
+    flush();
+    _previousMsg = message;
+  }
+
+  void trace(String message) {
+    flush();
+    _previousMsg = '${ansi.gray}$message${ansi.none}';
+  }
+
+  Progress progress(String message) => new _SimpleProgress(this, message);
+
+  void _progressFinished(Progress progress) {}
+
+  void flush() {
+    if (_previousErr != null) {
+      io.stderr.writeln('${_createTag()} $_previousErr');
+      _previousErr = null;
+    } else if (_previousMsg != null) {
+      io.stdout.writeln('${_createTag()} $_previousMsg');
+      _previousMsg = null;
+    }
+  }
+
+  String _createTag() {
+    int millis = _timer.elapsedMilliseconds;
+    _timer.reset();
+    return '[${millis.toString().padLeft(4)} ms]';
+  }
+}
diff --git a/lib/cli_util.dart b/lib/cli_util.dart
index 62191ec..2c8faa2 100644
--- a/lib/cli_util.dart
+++ b/lib/cli_util.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-library cli_util;
+/// Utilities to return the Dart SDK location.
 
 import 'dart:io';