Consolidate all tools under a single command-line interface
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd33166..47bd06b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 0.6.0-dev.0.0
 
+This release contains several **breaking changes**:
+
 * The fields `Info.id` and `Info.serializedId` have been removed. These
   properties were only used for serialization and deserialization. Those values
   are now computed during the serialization process instead.
@@ -9,8 +11,9 @@
   embed code snippets (since they are duplicated with the output program).
  
   Encoder produces a new format for code-spans, but for a transitional period
-  the decoder is still backwards compatible (filling in just the `text` in
-  `CodeSpan` where the json contained a String).
+  a flag is provided to produce the old format. The decoder is still backwards
+  compatible (filling in just the `text` in `CodeSpan` where the json contained
+  a String).
 
 * Deleted unused `Measurements`.
 
@@ -20,8 +23,17 @@
   serialization/deserialization implementation. This will eventually be used by
   default by dart2js.
 
-* Added backwards compatibility to the JSON codec, to make transition to new
-  tools more gradual.
+* Added backwards compatibility flag to the JSON codec, to make transition to
+  new tools more gradual.
+
+* Consolidated all binary tools under a single command. Now you can access all
+  tools as follows:
+  ```
+  pub global activate dart2js_info
+  dart2js_info <command> [arguments] ...
+  ```
+
+  See updated documentation in README.md
 
 ## 0.5.17
 
diff --git a/README.md b/README.md
index 902f195..7152bc8 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
 # Dart2js Info
 
-This package contains libraries and tools you can use to process `.info.json`
-files, which are produced when running dart2js with `--dump-info`.
+This package contains libraries and tools you can use to process info
+files produced when running dart2js with `--dump-info`.
 
-The `.info.json` files contain data about each element included in
-the output of your program. The data includes information such as:
+The info files contain data about each element included in the output of your
+program. The data includes information such as:
 
   * the size that each function adds to the `.dart.js` output,
   * dependencies between functions,
@@ -27,7 +27,7 @@
 
 Currently, most tools available here can be used to analyze code-size and
 attribution of code-size to different parts of your app. With time, we hope to
-add more data to the `.info.json` files, and include better tools to help
+add more data to the info files, and include better tools to help
 understand the results of type inference.
 
 This package is still in flux and we might make breaking changes at any time.
@@ -35,22 +35,53 @@
 functionality and iterate on it.  We recommend that you pin a specific version
 of this package and update when needed.
 
+## Tools
+
+All tools are provided as commands of a single command-line interface. To
+install:
+```console
+pub global activate dart2js_info
+```
+
+To run a tool, then run:
+```console
+dart2js_info <command> [arguments]
+```
+
+There is a short help available on the tool, and more details are provided
+below.
+
+## Format
+
+There are several formats of info files. Dart2js today produces a JSON format,
+but very soon will switch to produce a binary format by default.
+
 ## Info API
 
+This package also exposes libraries to parse and represent the information from
+the info files. If there is data that is stored in the info files but not
+exposed by one of our tools, you may be able to use the info APIs to quickly put
+together your own tool.
+
 [AllInfo][AllInfo] exposes a Dart representation of all of the collected
-information. You can decode an `AllInfo` object from the JSON form produced by
-the `dart2js` `--dump-info` option using the `AllInfoJsonCodec`. For example:
+information. There are deserialization libraries in this package to decode any
+info file produced by the `dart2js` `--dump-info` option. See
+`lib/binary_serialization.dart` and `lib/json_info_codec.dart` to find the
+binary and JSON decoders respectively. For convenience,
+`package:dart2js_info/src/io.dart` also exposes a helper method that can choose,
+depending on the extension of the info file, whether to deserialize it using the
+binary or JSON decoder.  For example:
 
 ```dart
 import 'dart:convert';
 import 'dart:io';
 
 import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/src/io.dart';
 
-main(args) {
+main(args) async {
   var infoPath = args[0];
-  var json = JSON.decode(new File(infoPath).readAsStringSync());
-  var info = new AllInfoJsonCodec().decode(json);
+  var info = await infoFromFile(infoPath);
   ...
 }
 ```
@@ -59,7 +90,7 @@
 
 The following tools are a available today:
 
-  * [`code_deps.dart`][code_deps]: simple tool that can answer queries about the
+  * [`code_deps`][code_deps]: simple tool that can answer queries about the
     dependency between functions and fields in your program. Currently it only
     supports the `some_path` query, which shows a dependency path from one
     function to another.
@@ -68,37 +99,39 @@
     program elements have been added, removed, or changed size. This also
     tells which elements are no longer deferred or have become deferred.
 
-  * [`library_size_split`][lib_split]: a tool that shows how much code was
+  * [`library_size`][library_size]: a tool that shows how much code was
     attributed to each library. This tool is configurable so it can group data
     in many ways (e.g. to tally together all libraries that belong to a package,
     or all libraries that match certain name pattern).
 
-  * [`deferred_library_check`][deferred_lib]: a tool that verifies that code
+  * [`deferred_check`][deferred_check]: a tool that verifies that code
     was split into deferred parts as expected. This tool takes a specification
     of the expected layout of code into deferred parts, and checks that the
     output from `dart2js` meets the specification.
 
-  * [`deferred_library_size`][deferred_size]: a tool that gives a breakdown of
+  * [`deferred_size`][deferred_size]: a tool that gives a breakdown of
     the sizes of the deferred parts of the program. This can show how much of
     your total code size can be loaded deferred.
 
-  * [`deferred_library_layout`][deferred_layout]: a tool that reports which
+  * [`deferred_layout`][deferred_layout]: a tool that reports which
     code is included on each output unit.
 
-  * [`function_size_analysis`][function_analysis]: a tool that shows how much
+  * [`function_size`][function_size]: a tool that shows how much
     code was attributed to each function. This tool also uses dependency
     information to compute dominance and reachability data. This information can
     sometimes help determine how much savings could come if the function was not
     included in the program.
 
-  * [`coverage_log_server`][coverage] and [`live_code_size_analysis`][live]:
+  * [`coverage_server`][coverage_server] and [`coverage_analysis`][coverage_analysis]:
     dart2js has an experimental feature to gather coverage data of your
     application. The `coverage_log_server` can record this data, and
-    `live_code_size_analysis` can correlate that with the `.info.json`, so you
+    `live_code_size_analysis` can correlate that with the info file, so you
     determine why code that is not used is being included in your app.
 
-  * [`info_json_to_proto`][info_json_to_proto]: a tool that converts `info.json`
-    files generated from dart2js to the protobuf schema defined in `info.proto`.
+  * [`convert`][convert]: a tool that converts info files from one format to
+    another. Accepted inputs are JSON or the internal binary form, outputs can
+    be JSON, backward-compatible JSON, binary, or protobuf schema (as defined in
+    `info.proto`).
 
 Next we describe in detail how to use each of these tools.
 
@@ -110,9 +143,9 @@
 
 Run this tool as follows:
 ```console
-# activate is only needed once to install the dart2js_info* executables
+# activate is only needed once to install the dart2js_info tool
 $ pub global activate dart2js_info
-$ dart2js_info_code_deps out.js.info.json some_path main foo
+$ dart2js_info code_deps some_path out.js.info.data main foo
 ```
 
 The arguments to the query are regular expressions that can be used to
@@ -134,7 +167,7 @@
 
 ```console
 $ pub global activate dart2js_info # only needed once
-$ dart2js_info_diff old.js.info.json new.js.info.json [--summary]
+$ dart2js_info diff old.js.info.data new.js.info.data [--summary]
 ```
 
 The tool gives a breakdown of the difference between the two info files.
@@ -206,7 +239,7 @@
 
 ```console
 $ pub global activate dart2js_info # only needed once
-$ dart2js_info_library_size_split out.js.info.json
+$ dart2js_info library_size out.js.info.data
 ```
 
 
@@ -214,7 +247,7 @@
 specify what regular expressions to use by providing a `grouping.yaml` file:
 
 ```console
-$ dart2js_info_library_size_split out.js.info.json grouping.yaml
+$ dart2js_info library_size out.js.info.data grouping.yaml
 ```
 
 The format of the `grouping.yaml` file is as follows:
@@ -286,7 +319,7 @@
 
 ```console
 $ pub global activate dart2js_info # only needed once
-$ dart2js_info_deferred_library_check out.js.info.json manifest.yaml
+$ dart2js_info deferred_check out.js.info.data manifest.yaml
 ```
 
 The format of the YAML file is:
@@ -334,7 +367,7 @@
 
 ```console
 pub global activate dart2js_info # only needed once
-dart2js_info_deferred_library_size out.js.info.json
+dart2js_info deferred_size out.js.info.data
 ```
 
 The tool will output a table listing all of the deferred imports in the program
@@ -359,7 +392,7 @@
 
 ```console
 $ pub global activate dart2js_info # only needed once
-$ dart2js_info_deferred_library_layout out.js.info.json
+$ dart2js_info deferred_layout out.js.info.data
 ```
 
 The tool will output a table listing all of the deferred output units or chunks,
@@ -403,7 +436,7 @@
 When you run:
 ```console
 $ pub global activate dart2js_info # only needed once
-$ dart2js_info_function_size_analysis out.js.info.json
+$ dart2js_info function_size out.js.info.data
 ```
 
 the tool produces a table output with lots of entries. Here is an example entry
@@ -443,7 +476,7 @@
   * Launch the coverage server tool to serve up the JS code of your app:
 
 ```console
-$ dart2js_info_coverage_log_server main.dart.js
+$ dart2js_info coverage_server main.dart.js
 ```
 
   * (optional) If you have a complex application setup, you may need to serve an
@@ -460,7 +493,7 @@
     coverage json files:
 
 ```console
-$ dart2js_info_live_code_size_analysis main.dart.info.json main.dart.coverage.json
+$ dart2js_info coverage_analysis main.dart.info.data main.dart.coverage.json
 ```
 
 ## Code location, features and bugs
@@ -472,12 +505,12 @@
 [tracker]: https://github.com/dart-lang/dart2js_info/issues
 [code_deps]: https://github.com/dart-lang/dart2js_info/blob/master/bin/code_deps.dart
 [diff]: https://github.com/dart-lang/dart2js_info/blob/master/bin/diff.dart
-[lib_split]: https://github.com/dart-lang/dart2js_info/blob/master/bin/library_size_split.dart
-[deferred_lib]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_check.dart
+[library_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/library_size_split.dart
+[deferred_check]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_check.dart
 [deferred_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_size.dart
 [deferred_layout]: https://github.com/dart-lang/dart2js_info/blob/master/bin/deferred_library_layout.dart
-[coverage]: https://github.com/dart-lang/dart2js_info/blob/master/bin/coverage_log_server.dart
-[live]: https://github.com/dart-lang/dart2js_info/blob/master/bin/live_code_size_analysis.dart
-[function_analysis]: https://github.com/dart-lang/dart2js_info/blob/master/bin/function_size_analysis.dart
+[coverage_server]: https://github.com/dart-lang/dart2js_info/blob/master/bin/coverage_log_server.dart
+[coverage_analysis]: https://github.com/dart-lang/dart2js_info/blob/master/bin/live_code_size_analysis.dart
+[function_size]: https://github.com/dart-lang/dart2js_info/blob/master/bin/function_size_analysis.dart
 [AllInfo]: http://dart-lang.github.io/dart2js_info/doc/api/dart2js_info.info/AllInfo-class.html
-[info_json_to_proto]: https://github.com/dart-lang/dart2js_info/blob/master/bin/info_json_to_proto.dart
+[convert]: https://github.com/dart-lang/dart2js_info/blob/master/bin/convert.dart
diff --git a/bin/binary_to_json.dart b/bin/binary_to_json.dart
deleted file mode 100644
index 7fceb2b..0000000
--- a/bin/binary_to_json.dart
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2019, 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:io';
-import 'dart:convert';
-
-import 'package:dart2js_info/info.dart';
-import 'package:dart2js_info/binary_serialization.dart' as binary;
-import 'package:dart2js_info/json_info_codec.dart';
-
-/// Converts a dump-info file emitted by dart2js in binary format to JSON.
-main(args) async {
-  if (args.length < 1) {
-    print('usage: binary_to_json <input.data> [--compat-mode]'
-        '\n\n'
-        '    By default files are converted to the latest JSON format, but\n'
-        '    passing `--compat-mode` will produce a JSON file that may still\n'
-        '    work in the visualizer tool at: \n'
-        '    https://dart-lang.github.io/dump-info-visualizer/.\n\n'
-        '    Note, however, that files produced in this mode do not contain\n'
-        '    all the data available in the input file.');
-    exit(1);
-  }
-
-  var input = new File(args[0]).readAsBytesSync();
-  bool isBackwardCompatible = args.length > 1 && args.contains('--compat-mode');
-  AllInfo info = binary.decode(input);
-
-  // Fill the text of each code span. The binary form produced by dart2js
-  // produces code spans, but excludes the orignal text
-  info.functions.forEach((f) {
-    f.code.forEach((span) => _fillSpan(span, f.outputUnit));
-  });
-  info.fields.forEach((f) {
-    f.code.forEach((span) => _fillSpan(span, f.outputUnit));
-  });
-  info.constants.forEach((c) {
-    c.code.forEach((span) => _fillSpan(span, c.outputUnit));
-  });
-
-  var json = new AllInfoJsonCodec(isBackwardCompatible: isBackwardCompatible)
-      .encode(info);
-  new File("${args[0]}.json")
-      .writeAsStringSync(const JsonEncoder.withIndent("  ").convert(json));
-}
-
-Map<String, String> _cache = {};
-
-_getContents(OutputUnitInfo unit) => _cache.putIfAbsent(unit.filename, () {
-      var uri = Uri.base.resolve(unit.filename);
-      return new File.fromUri(uri).readAsStringSync();
-    });
-
-_fillSpan(CodeSpan span, OutputUnitInfo unit) {
-  if (span.text == null && span.start != null && span.end != 0) {
-    var contents = _getContents(unit);
-    span.text = contents.substring(span.start, span.end);
-  }
-}
diff --git a/bin/code_deps.dart b/bin/code_deps.dart
index 27278b6..91b9222 100644
--- a/bin/code_deps.dart
+++ b/bin/code_deps.dart
@@ -2,14 +2,14 @@
 // 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.
 
-/// Command-line tool to query for code dependencies. Currently this tool only
+/// Command to query for code dependencies. Currently this tool only
 /// supports the `some_path` query, which gives you the shortest path for how
 /// one function depends on another.
 ///
 /// You can run this tool as follows:
 /// ```bash
 /// pub global activate dart2js_info
-/// dart2js_info_code_deps out.js.info.json some_path main foo
+/// dart2js_info code_deps some_path out.js.info.json main foo
 /// ```
 ///
 /// The arguments to the query are regular expressions that can be used to
@@ -26,44 +26,52 @@
 library dart2js_info.bin.code_deps;
 
 import 'dart:collection';
-import 'dart:io';
+
+import 'package:args/command_runner.dart';
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) async {
-  if (args.length < 2) {
-    print('usage: dart2js_info_code_deps path-to.info.json <query>');
-    print('   where <query> can be:');
-    print('     - some_path <element-regexp-1> <element-regexp-2>');
-    // TODO(sigmund): add other queries, such as 'all_paths'.
-    exit(1);
+import 'usage_exception.dart';
+
+class CodeDepsCommand extends Command<void> with PrintUsageException {
+  final String name = "code_deps";
+  final String description = "";
+
+  CodeDepsCommand() {
+    addSubcommand(new _SomePathQuery());
   }
+}
 
-  var info = await infoFromFile(args.first);
-  var graph = graphFromInfo(info);
+class _SomePathQuery extends Command<void> with PrintUsageException {
+  final String name = "some_path";
+  final String description = "find a call-graph path between two elements.";
 
-  var queryName = args[1];
-  if (queryName == 'some_path') {
-    if (args.length < 4) {
-      print('missing arguments for `some_path`');
-      exit(1);
+  @override
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 3) {
+      usageException("Missing arguments for some_path, expected: "
+          "info.data <element-regexp-1> <element-regexp-2>");
+      return;
     }
+
+    var info = await infoFromFile(args.first);
+    var graph = graphFromInfo(info);
+
     var source = info.functions
-        .firstWhere(_longNameMatcher(new RegExp(args[2])), orElse: () => null);
+        .firstWhere(_longNameMatcher(new RegExp(args[1])), orElse: () => null);
     var target = info.functions
-        .firstWhere(_longNameMatcher(new RegExp(args[3])), orElse: () => null);
+        .firstWhere(_longNameMatcher(new RegExp(args[2])), orElse: () => null);
     print('query: some_path');
     if (source == null) {
-      print("source '${args[2]}' not found in '${args[0]}'");
-      exit(1);
+      usageException("source '${args[1]}' not found in '${args[0]}'");
     }
     print('source: ${longName(source)}');
     if (target == null) {
-      print("target '${args[3]}' not found in '${args[0]}'");
-      exit(1);
+      usageException("target '${args[2]}' not found in '${args[0]}'");
     }
     print('target: ${longName(target)}');
     var path = new SomePathQuery(source, target).run(graph);
@@ -75,8 +83,6 @@
         print('  $i. ${longName(path[i])}');
       }
     }
-  } else {
-    print('unrecognized query: $queryName');
   }
 }
 
@@ -96,7 +102,7 @@
   SomePathQuery(this.source, this.target);
 
   List<Info> run(Graph<Info> graph) {
-    var seen = {source: null};
+    var seen = <Info, Info>{source: null};
     var queue = new Queue<Info>();
     queue.addLast(source);
     while (queue.isNotEmpty) {
diff --git a/bin/convert.dart b/bin/convert.dart
new file mode 100644
index 0000000..a1d2f3b
--- /dev/null
+++ b/bin/convert.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2019, 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 'package:args/command_runner.dart';
+
+import 'to_json.dart' show ToJsonCommand;
+import 'to_binary.dart' show ToBinaryCommand;
+import 'to_proto.dart' show ToProtoCommand;
+import 'usage_exception.dart';
+
+/// This tool reports how code is divided among deferred chunks.
+class ConvertCommand extends Command<void> with PrintUsageException {
+  final String name = "convert";
+  final String description = "Convert between the binary and JSON info format.";
+
+  ConvertCommand() {
+    addSubcommand(new ToJsonCommand());
+    addSubcommand(new ToBinaryCommand());
+    addSubcommand(new ToProtoCommand());
+  }
+}
diff --git a/bin/coverage_log_server.dart b/bin/coverage_log_server.dart
index bda7f14..d43d410 100644
--- a/bin/coverage_log_server.dart
+++ b/bin/coverage_log_server.dart
@@ -23,47 +23,54 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
 import 'package:path/path.dart' as path;
 import 'package:shelf/shelf.dart' as shelf;
 import 'package:shelf/shelf_io.dart' as shelf;
 
-const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json';
+import 'usage_exception.dart';
 
-main(List<String> argv) async {
-  var parser = new ArgParser()
-    ..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080")
-    ..addOption('host',
-        help: 'host name (use 0.0.0.0 for all interfaces)',
-        defaultsTo: 'localhost')
-    ..addFlag('help',
-        abbr: 'h', help: 'show this help message', negatable: false)
-    ..addOption('uri-prefix',
-        help: 'uri path prefix that will hit this server. This will be injected'
-            ' into the .js file',
-        defaultsTo: '')
-    ..addOption('out',
-        abbr: 'o', help: 'output log file', defaultsTo: _DEFAULT_OUT_TEMPLATE);
-  var args = parser.parse(argv);
-  if (args['help'] == true || args.rest.isEmpty) {
-    print('usage: dart coverage_logging.dart [options] '
-        '<dart2js-out-file> [<html-file>]');
-    print(parser.usage);
-    exit(1);
+class CoverageLogServerCommand extends Command<void> with PrintUsageException {
+  final String name = 'coverage_server';
+  final String description = 'Server to gather code coverage data';
+
+  CoverageLogServerCommand() {
+    argParser
+      ..addOption('port', abbr: 'p', help: 'port number', defaultsTo: "8080")
+      ..addOption('host',
+          help: 'host name (use 0.0.0.0 for all interfaces)',
+          defaultsTo: 'localhost')
+      ..addOption('uri-prefix',
+          help:
+              'uri path prefix that will hit this server. This will be injected'
+              ' into the .js file',
+          defaultsTo: '')
+      ..addOption('out',
+          abbr: 'o',
+          help: 'output log file',
+          defaultsTo: _DEFAULT_OUT_TEMPLATE);
   }
 
-  var jsPath = args.rest[0];
-  var htmlPath = null;
-  if (args.rest.length > 1) {
-    htmlPath = args.rest[1];
+  void run() async {
+    if (argResults.rest.isEmpty) {
+      usageException('Missing arguments: <dart2js-out-file> [<html-file>]');
+    }
+
+    var jsPath = argResults.rest[0];
+    var htmlPath = null;
+    if (argResults.rest.length > 1) {
+      htmlPath = argResults.rest[1];
+    }
+    var outPath = argResults['out'];
+    if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json';
+    var server = new _Server(argResults['host'], int.parse(argResults['port']),
+        jsPath, htmlPath, outPath, argResults['uri-prefix']);
+    await server.run();
   }
-  var outPath = args['out'];
-  if (outPath == _DEFAULT_OUT_TEMPLATE) outPath = '$jsPath.coverage.json';
-  var server = new _Server(args['host'], int.parse(args['port']), jsPath,
-      htmlPath, outPath, args['uri-prefix']);
-  await server.run();
 }
 
+const _DEFAULT_OUT_TEMPLATE = '<dart2js-out-file>.coverage.json';
+
 class _Server {
   /// Server hostname, typically `localhost`,  but can be `0.0.0.0`.
   final String hostname;
diff --git a/bin/debug_info.dart b/bin/debug_info.dart
index c7925ea..7256b82 100644
--- a/bin/debug_info.dart
+++ b/bin/debug_info.dart
@@ -6,31 +6,38 @@
 /// that it is consistent and that it covers all the data we expect it to cover.
 library dart2js_info.bin.debug_info;
 
-import 'dart:io';
+import 'package:args/command_runner.dart';
 
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) async {
-  if (args.length < 1) {
-    print('usage: dart tool/debug_info.dart path-to-info.json '
-        '[--show-library libname]');
-    exit(1);
+import 'usage_exception.dart';
+
+class DebugCommand extends Command<void> with PrintUsageException {
+  final String name = "debug";
+  final String description = "Dart2js-team diagnostics on a dump-info file.";
+
+  DebugCommand() {
+    argParser.addOption('show-library',
+        help: "Show detailed data for a library with the given name");
   }
 
-  var info = await infoFromFile(args.first);
-  var debugLibName;
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 1) {
+      usageException('Missing argument: info.data');
+    }
 
-  if (args.length > 2 && args[1] == '--show-library') {
-    debugLibName = args[2];
+    var info = await infoFromFile(args.first);
+    var debugLibName = argResults['show-library'];
+
+    validateSize(info, debugLibName);
+    validateParents(info);
+    compareGraphs(info);
+    verifyDeps(info);
   }
-
-  validateSize(info, debugLibName);
-  validateParents(info);
-  compareGraphs(info);
-  verifyDeps(info);
 }
 
 /// Validates that codesize of elements adds up to total codesize.
diff --git a/bin/deferred_library_check.dart b/bin/deferred_library_check.dart
index 995c366..4244b14 100644
--- a/bin/deferred_library_check.dart
+++ b/bin/deferred_library_check.dart
@@ -2,8 +2,8 @@
 // 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.
 
-/// A command-line tool that verifies that deferred libraries split the code as
-/// expected.
+/// A command that verifies that deferred libraries split the code as expected.
+///
 /// This tool checks that the output from dart2js meets a given specification,
 /// given in a YAML file. The format of the YAML file is:
 ///
@@ -38,29 +38,35 @@
 import 'dart:async';
 import 'dart:io';
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/deferred_library_check.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:yaml/yaml.dart';
 
-Future main(List<String> args) async {
-  if (args.length < 2) {
-    usage();
-    exit(1);
-  }
-  var info = await infoFromFile(args[0]);
-  var manifest = await manifestFromFile(args[1]);
+import 'usage_exception.dart';
 
-  var failures = checkDeferredLibraryManifest(info, manifest);
-  failures.forEach(print);
-  if (failures.isNotEmpty) exitCode = 1;
+/// A command that computes the diff between two info files.
+class DeferredLibraryCheck extends Command<void> with PrintUsageException {
+  final String name = "deferred_check";
+  final String description =
+      "Verify that deferred libraries are split as expected";
+
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 2) {
+      usageException('Missing arguments, expected: info.data manifest.yaml');
+    }
+    var info = await infoFromFile(args[0]);
+    var manifest = await manifestFromFile(args[1]);
+
+    var failures = checkDeferredLibraryManifest(info, manifest);
+    failures.forEach(print);
+    if (failures.isNotEmpty) exitCode = 1;
+  }
 }
 
 Future manifestFromFile(String fileName) async {
   var file = await new File(fileName).readAsString();
   return loadYaml(file);
 }
-
-void usage() {
-  print('''
-usage: dart2js_info_deferred_library_check dump.info.json manifest.yaml''');
-}
diff --git a/bin/deferred_library_layout.dart b/bin/deferred_library_layout.dart
index 5a124ad..c647344 100644
--- a/bin/deferred_library_layout.dart
+++ b/bin/deferred_library_layout.dart
@@ -3,15 +3,33 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /// This tool reports how code is divided among deferred chunks.
-library dart2js_info.bin.deferred_library_size;
+library dart2js_info.bin.deferred_library_layout;
 
 import 'dart:io';
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/io.dart';
 
-main(args) async {
-  AllInfo info = await infoFromFile(args.first);
+import 'usage_exception.dart';
+
+/// This tool reports how code is divided among deferred chunks.
+class DeferredLibraryLayout extends Command<void> with PrintUsageException {
+  final String name = "deferred_layout";
+  final String description = "Show how code is divided among deferred parts.";
+
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 1) {
+      usageException('Missing argument: info.data');
+    }
+    await _showLayout(args.first);
+  }
+}
+
+_showLayout(String file) async {
+  AllInfo info = await infoFromFile(file);
 
   Map<OutputUnitInfo, Map<LibraryInfo, List<BasicInfo>>> hunkMembers = {};
   Map<LibraryInfo, Set<OutputUnitInfo>> libToHunks = {};
diff --git a/bin/deferred_library_size.dart b/bin/deferred_library_size.dart
index 9800966..2ea7089 100644
--- a/bin/deferred_library_size.dart
+++ b/bin/deferred_library_size.dart
@@ -7,14 +7,28 @@
 
 import 'dart:math';
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/io.dart';
 
-main(args) async {
-  // TODO(het): Would be faster to only parse the 'outputUnits' part
-  var info = await infoFromFile(args.first);
-  var sizeByImport = getSizeByImport(info);
-  printSizes(sizeByImport, info.program.size);
+import 'usage_exception.dart';
+
+/// This tool gives a breakdown of code size by deferred part in the program.
+class DeferredLibrarySize extends Command<void> with PrintUsageException {
+  final String name = "deferred_size";
+  final String description = "Show breakdown of codesize by deferred part.";
+
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 1) {
+      usageException('Missing argument: info.data');
+    }
+    // TODO(het): Would be faster to only parse the 'outputUnits' part
+    var info = await infoFromFile(args.first);
+    var sizeByImport = getSizeByImport(info);
+    printSizes(sizeByImport, info.program.size);
+  }
 }
 
 class ImportSize {
diff --git a/bin/diff.dart b/bin/diff.dart
index 4d2c229..d013349 100644
--- a/bin/diff.dart
+++ b/bin/diff.dart
@@ -2,103 +2,113 @@
 // 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 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/diff.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:dart2js_info/src/util.dart';
 
-/// A command-line tool that computes the diff between two info files.
-main(List<String> args) async {
-  if (args.length < 2 || args.length > 3) {
-    print('usage: dart2js_info_diff old.info.json new.info.json [--summary]');
-    return;
+import 'usage_exception.dart';
+
+/// A command that computes the diff between two info files.
+class DiffCommand extends Command<void> with PrintUsageException {
+  final String name = "diff";
+  final String description =
+      "See code size differences between two dump-info files.";
+
+  DiffCommand() {
+    argParser.addFlag('summary-only',
+        defaultsTo: false,
+        help: "Show only a summary and hide details of each library");
   }
 
-  var oldInfo = await infoFromFile(args[0]);
-  var newInfo = await infoFromFile(args[1]);
-  var summary = false;
-  if (args.length == 3) {
-    if (args[2] == "--summary") {
-      summary = true;
-    } else {
-      print('Unrecognized argument: ${args[2]}');
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 2) {
+      usageException(
+          'Missing arguments, expected two dump-info files to compare');
       return;
     }
-  }
 
-  var diffs = diff(oldInfo, newInfo);
+    var oldInfo = await infoFromFile(args[0]);
+    var newInfo = await infoFromFile(args[1]);
+    var summaryOnly = argResults['summary-only'];
 
-  // Categorize the diffs
-  var adds = <AddDiff>[];
-  var removals = <RemoveDiff>[];
-  var sizeChanges = <SizeDiff>[];
-  var becameDeferred = <DeferredStatusDiff>[];
-  var becameUndeferred = <DeferredStatusDiff>[];
+    var diffs = diff(oldInfo, newInfo);
 
-  for (var diff in diffs) {
-    switch (diff.kind) {
-      case DiffKind.add:
-        adds.add(diff as AddDiff);
-        break;
-      case DiffKind.remove:
-        removals.add(diff as RemoveDiff);
-        break;
-      case DiffKind.size:
-        sizeChanges.add(diff as SizeDiff);
-        break;
-      case DiffKind.deferred:
-        var deferredDiff = diff as DeferredStatusDiff;
-        if (deferredDiff.wasDeferredBefore) {
-          becameUndeferred.add(deferredDiff);
-        } else {
-          becameDeferred.add(deferredDiff);
-        }
-        break;
-    }
-  }
+    // Categorize the diffs
+    var adds = <AddDiff>[];
+    var removals = <RemoveDiff>[];
+    var sizeChanges = <SizeDiff>[];
+    var becameDeferred = <DeferredStatusDiff>[];
+    var becameUndeferred = <DeferredStatusDiff>[];
 
-  // Sort the changes by the size of the element that changed.
-  for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
-    diffs.sort((a, b) => b.info.size - a.info.size);
-  }
-
-  // Sort changes in size by size difference.
-  sizeChanges.sort((a, b) => b.sizeDifference - a.sizeDifference);
-
-  var totalSizes = <List<Diff>, int>{};
-  for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
-    var totalSize = 0;
     for (var diff in diffs) {
-      // Only count diffs from leaf elements so we don't double count
-      // them when we account for class size diff or library size diff.
-      if (diff.info.kind == InfoKind.field ||
-          diff.info.kind == InfoKind.function ||
-          diff.info.kind == InfoKind.closure ||
-          diff.info.kind == InfoKind.typedef) {
-        totalSize += diff.info.size;
+      switch (diff.kind) {
+        case DiffKind.add:
+          adds.add(diff as AddDiff);
+          break;
+        case DiffKind.remove:
+          removals.add(diff as RemoveDiff);
+          break;
+        case DiffKind.size:
+          sizeChanges.add(diff as SizeDiff);
+          break;
+        case DiffKind.deferred:
+          var deferredDiff = diff as DeferredStatusDiff;
+          if (deferredDiff.wasDeferredBefore) {
+            becameUndeferred.add(deferredDiff);
+          } else {
+            becameDeferred.add(deferredDiff);
+          }
+          break;
       }
     }
-    totalSizes[diffs] = totalSize;
-  }
-  var totalSizeChange = 0;
-  for (var sizeChange in sizeChanges) {
-    // Only count diffs from leaf elements so we don't double count
-    // them when we account for class size diff or library size diff.
-    if (sizeChange.info.kind == InfoKind.field ||
-        sizeChange.info.kind == InfoKind.function ||
-        sizeChange.info.kind == InfoKind.closure ||
-        sizeChange.info.kind == InfoKind.typedef) {
-      totalSizeChange += sizeChange.sizeDifference;
-    }
-  }
-  totalSizes[sizeChanges] = totalSizeChange;
 
-  reportSummary(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
-      becameUndeferred, totalSizes);
-  if (!summary) {
-    print('');
-    reportFull(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
+    // Sort the changes by the size of the element that changed.
+    for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
+      diffs.sort((a, b) => b.info.size - a.info.size);
+    }
+
+    // Sort changes in size by size difference.
+    sizeChanges.sort((a, b) => b.sizeDifference - a.sizeDifference);
+
+    var totalSizes = <List<Diff>, int>{};
+    for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) {
+      var totalSize = 0;
+      for (var diff in diffs) {
+        // Only count diffs from leaf elements so we don't double count
+        // them when we account for class size diff or library size diff.
+        if (diff.info.kind == InfoKind.field ||
+            diff.info.kind == InfoKind.function ||
+            diff.info.kind == InfoKind.closure ||
+            diff.info.kind == InfoKind.typedef) {
+          totalSize += diff.info.size;
+        }
+      }
+      totalSizes[diffs] = totalSize;
+    }
+    var totalSizeChange = 0;
+    for (var sizeChange in sizeChanges) {
+      // Only count diffs from leaf elements so we don't double count
+      // them when we account for class size diff or library size diff.
+      if (sizeChange.info.kind == InfoKind.field ||
+          sizeChange.info.kind == InfoKind.function ||
+          sizeChange.info.kind == InfoKind.closure ||
+          sizeChange.info.kind == InfoKind.typedef) {
+        totalSizeChange += sizeChange.sizeDifference;
+      }
+    }
+    totalSizes[sizeChanges] = totalSizeChange;
+
+    reportSummary(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
         becameUndeferred, totalSizes);
+    if (!summaryOnly) {
+      print('');
+      reportFull(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred,
+          becameUndeferred, totalSizes);
+    }
   }
 }
 
diff --git a/bin/function_size_analysis.dart b/bin/function_size_analysis.dart
index 18acf3a..ad6e8ad 100644
--- a/bin/function_size_analysis.dart
+++ b/bin/function_size_analysis.dart
@@ -2,20 +2,33 @@
 // 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.
 
-/// Command-line tool presenting how much each function contributes to the total
-/// code.
-library compiler.tool.live_code_size_analysis;
+/// Tool presenting how much each function contributes to the total code.
+library compiler.tool.function_size_analysis;
 
 import 'dart:math' as math;
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/graph.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:dart2js_info/src/util.dart';
 
-main(args) async {
-  var info = await infoFromFile(args.first);
-  showCodeDistribution(info);
+import 'usage_exception.dart';
+
+/// Command presenting how much each function contributes to the total code.
+class FunctionSizeCommand extends Command<void> with PrintUsageException {
+  final String name = "function_size";
+  final String description = "See breakdown of code size by function.";
+
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 1) {
+      usageException('Missing argument: info.data');
+    }
+    var info = await infoFromFile(args.first);
+    showCodeDistribution(info);
+  }
 }
 
 showCodeDistribution(AllInfo info,
diff --git a/bin/info_json_to_proto.dart b/bin/info_json_to_proto.dart
deleted file mode 100644
index bd964cf..0000000
--- a/bin/info_json_to_proto.dart
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2018, 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.
-
-/// Command-line tool to convert an info.json file ouputted by dart2js to the
-/// alternative protobuf format.
-
-import 'dart:io';
-
-import 'package:dart2js_info/proto_info_codec.dart';
-import 'package:dart2js_info/src/io.dart';
-
-main(args) async {
-  if (args.length != 2) {
-    print('usage: dart tool/info_json_to_proto.dart '
-        'path-to-info.json path-to-info.pb');
-    exit(1);
-  }
-
-  final info = await infoFromFile(args.first);
-  final proto = new AllInfoProtoCodec().encode(info);
-  final outputFile = new File(args.last);
-  await outputFile.writeAsBytes(proto.writeToBuffer(), mode: FileMode.write);
-}
diff --git a/bin/json_to_binary.dart b/bin/json_to_binary.dart
deleted file mode 100644
index f6df462..0000000
--- a/bin/json_to_binary.dart
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2019, 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:io';
-import 'dart:convert';
-
-import 'package:dart2js_info/info.dart';
-import 'package:dart2js_info/binary_serialization.dart' as binary;
-import 'package:dart2js_info/json_info_codec.dart';
-
-main(args) async {
-  if (args.length < 1) {
-    print('usage: json_to_binary <input.json>');
-    exit(1);
-  }
-
-  var input = new File(args[0]).readAsStringSync();
-  AllInfo info = new AllInfoJsonCodec().decode(jsonDecode(input));
-  var outstream = new File("${args[0]}.data").openWrite();
-  binary.encode(info, outstream);
-  await outstream.done;
-  binary.decode(new File("${args[0]}.data").readAsBytesSync());
-}
diff --git a/bin/library_size_split.dart b/bin/library_size_split.dart
index 810f50b..1d252c4 100644
--- a/bin/library_size_split.dart
+++ b/bin/library_size_split.dart
@@ -62,88 +62,107 @@
 import 'dart:io';
 import 'dart:math' show max;
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:yaml/yaml.dart';
 
-main(args) async {
-  if (args.length < 1) {
-    print('usage: dart tool/library_size_split.dart '
-        'path-to-info.json [grouping.yaml]');
-    exit(1);
+import 'usage_exception.dart';
+
+/// Command presenting how much each library contributes to the total code.
+class LibrarySizeCommand extends Command<void> with PrintUsageException {
+  final String name = "library_size";
+  final String description = "See breakdown of code size by library.";
+
+  LibrarySizeCommand() {
+    argParser.addOption('grouping',
+        help: 'YAML file specifying how libraries should be grouped.');
   }
 
-  var info = await infoFromFile(args.first);
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 1) {
+      usageException('Missing argument: info.data');
+      print('usage: dart tool/library_size_split.dart '
+          'path-to-info.json [grouping.yaml]');
+      exit(1);
+    }
 
-  var groupingText =
-      args.length > 1 ? new File(args[1]).readAsStringSync() : defaultGrouping;
-  var groupingYaml = loadYaml(groupingText);
-  var groups = [];
-  for (var group in groupingYaml['groups']) {
-    groups.add(new _Group(
-        group['name'], new RegExp(group['regexp']), group['cluster'] ?? 0));
-  }
+    var info = await infoFromFile(args.first);
 
-  var sizes = {};
-  var allLibs = 0;
-  for (LibraryInfo lib in info.libraries) {
-    allLibs += lib.size;
-    groups.forEach((group) {
-      var match = group.matcher.firstMatch('${lib.uri}');
-      if (match != null) {
-        var name = group.name;
-        if (name == null && match.groupCount > 0) name = match.group(1);
-        if (name == null) name = match.group(0);
-        sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster));
-        sizes[name].size += lib.size;
+    var groupingFile = argResults['grouping'];
+    var groupingText = groupingFile != null
+        ? new File(groupingFile).readAsStringSync()
+        : defaultGrouping;
+    var groupingYaml = loadYaml(groupingText);
+    var groups = [];
+    for (var group in groupingYaml['groups']) {
+      groups.add(new _Group(
+          group['name'], new RegExp(group['regexp']), group['cluster'] ?? 0));
+    }
+
+    var sizes = {};
+    var allLibs = 0;
+    for (LibraryInfo lib in info.libraries) {
+      allLibs += lib.size;
+      groups.forEach((group) {
+        var match = group.matcher.firstMatch('${lib.uri}');
+        if (match != null) {
+          var name = group.name;
+          if (name == null && match.groupCount > 0) name = match.group(1);
+          if (name == null) name = match.group(0);
+          sizes.putIfAbsent(name, () => new _SizeEntry(name, group.cluster));
+          sizes[name].size += lib.size;
+        }
+      });
+    }
+
+    var allConstants = 0;
+    for (var constant in info.constants) {
+      allConstants += constant.size;
+    }
+
+    var all = sizes.keys.toList();
+    all.sort((a, b) => sizes[a].compareTo(sizes[b]));
+    var realTotal = info.program.size;
+    var longest = 0;
+    var rows = <_Row>[];
+    _addRow(String label, int value) {
+      rows.add(new _Row(label, value));
+      longest = max(longest, label.length);
+    }
+
+    _printRow(_Row row) {
+      if (row is _Divider) {
+        print(' ' + ('-' * (longest + 18)));
+        return;
       }
-    });
-  }
 
-  var allConstants = 0;
-  for (var constant in info.constants) {
-    allConstants += constant.size;
-  }
-
-  var all = sizes.keys.toList();
-  all.sort((a, b) => sizes[a].compareTo(sizes[b]));
-  var realTotal = info.program.size;
-  var longest = 0;
-  var rows = <_Row>[];
-  _addRow(String label, int value) {
-    rows.add(new _Row(label, value));
-    longest = max(longest, label.length);
-  }
-
-  _printRow(_Row row) {
-    if (row is _Divider) {
-      print(' ' + ('-' * (longest + 18)));
-      return;
+      var percent = row.value == realTotal
+          ? '100'
+          : (row.value * 100 / realTotal).toStringAsFixed(2);
+      print(' ${_pad(row.label, longest + 1, right: true)}'
+          ' ${_pad(row.value, 8)} ${_pad(percent, 6)}%');
     }
 
-    var percent = row.value == realTotal
-        ? '100'
-        : (row.value * 100 / realTotal).toStringAsFixed(2);
-    print(' ${_pad(row.label, longest + 1, right: true)}'
-        ' ${_pad(row.value, 8)} ${_pad(percent, 6)}%');
-  }
-
-  var lastCluster = 0;
-  for (var name in all) {
-    var entry = sizes[name];
-    if (lastCluster < entry.cluster) {
-      rows.add(const _Divider());
-      lastCluster = entry.cluster;
+    var lastCluster = 0;
+    for (var name in all) {
+      var entry = sizes[name];
+      if (lastCluster < entry.cluster) {
+        rows.add(const _Divider());
+        lastCluster = entry.cluster;
+      }
+      var size = entry.size;
+      _addRow(name, size);
     }
-    var size = entry.size;
-    _addRow(name, size);
+    rows.add(const _Divider());
+    _addRow("All libraries (excludes preambles, statics & consts)", allLibs);
+    _addRow("Shared consts", allConstants);
+    _addRow("Total accounted", allLibs + allConstants);
+    _addRow("Program Size", realTotal);
+    rows.forEach(_printRow);
   }
-  rows.add(const _Divider());
-  _addRow("All libraries (excludes preambles, statics & consts)", allLibs);
-  _addRow("Shared consts", allConstants);
-  _addRow("Total accounted", allLibs + allConstants);
-  _addRow("Program Size", realTotal);
-  rows.forEach(_printRow);
 }
 
 /// A group defined in the configuration.
diff --git a/bin/live_code_size_analysis.dart b/bin/live_code_size_analysis.dart
index c721409..77c02fd 100644
--- a/bin/live_code_size_analysis.dart
+++ b/bin/live_code_size_analysis.dart
@@ -37,22 +37,37 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/io.dart';
 import 'package:dart2js_info/src/util.dart';
 
 import 'function_size_analysis.dart';
+import 'usage_exception.dart';
 
-main(args) async {
-  if (args.length < 2) {
-    print('usage: dart tool/live_code_size_analysis.dart path-to-info.json '
-        'path-to-coverage.json [-v]');
-    exit(1);
+class LiveCodeAnalysisCommand extends Command<void> with PrintUsageException {
+  final String name = "coverage_analysis";
+  final String description = "Analyze coverage data collected via the"
+      " 'coverage_server' command";
+
+  LiveCodeAnalysisCommand() {
+    argParser.addFlag('verbose',
+        abbr: 'v', negatable: false, help: 'Show verbose details.');
   }
 
-  var info = await infoFromFile(args.first);
-  var coverage = jsonDecode(new File(args[1]).readAsStringSync());
-  var verbose = args.length > 2 && args[2] == '-v';
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 2) {
+      usageException('Missing arguments, expected: info.data coverage.json');
+    }
+    await _liveCodeAnalysis(args[0], args[1], argResults['verbose']);
+  }
+}
+
+_liveCodeAnalysis(infoFile, coverageFile, bool verbose) async {
+  var info = await infoFromFile(infoFile);
+  var coverage = jsonDecode(new File(coverageFile).readAsStringSync());
 
   int realTotal = info.program.size;
   int totalLib = info.libraries.fold(0, (n, lib) => n + lib.size);
@@ -109,8 +124,7 @@
         filter: (f) => !coverage.containsKey(f.coverageId) && f.size > 0,
         showLibrarySizes: true);
   } else {
-    print('\nTo see details about the size of unreachable code, run:');
-    print('  dart live_code_analysis.dart ${args[0]} ${args[1]} -v | less');
+    print('\nUse `-v` to see details about the size of unreachable code');
   }
 }
 
diff --git a/bin/show_inferred_types.dart b/bin/show_inferred_types.dart
index a4fe0c8..521af34 100644
--- a/bin/show_inferred_types.dart
+++ b/bin/show_inferred_types.dart
@@ -7,21 +7,35 @@
 
 import 'dart:io';
 
+import 'package:args/command_runner.dart';
+
 import 'package:dart2js_info/src/util.dart';
 import 'package:dart2js_info/src/io.dart';
 
-main(args) async {
-  if (args.length < 2) {
-    var scriptName = Platform.script.pathSegments.last;
-    print('usage: dart $scriptName <info.json> <function-name-regex> [-l]');
-    print('  -l: print the long qualified name for a function.');
-    exit(1);
+import 'usage_exception.dart';
+
+class ShowInferredTypesCommand extends Command<void> with PrintUsageException {
+  final String name = "show_inferred";
+  final String description = "Show data inferred by dart2js global inference";
+
+  ShowInferredTypesCommand() {
+    argParser.addFlag('long-names',
+        abbr: 'l', negatable: false, help: 'Show long qualified names.');
   }
 
-  var showLongName = args.length > 2 && args[2] == '-l';
+  void run() async {
+    var args = argResults.rest;
+    if (args.length < 2) {
+      usageException(
+          'Missing arguments, expected: info.data <function-name-regex>');
+    }
+    _showInferredTypes(args[0], args[1], argResults['long-names']);
+  }
+}
 
-  var info = await infoFromFile(args[0]);
-  var nameRegExp = new RegExp(args[1]);
+_showInferredTypes(String infoFile, String pattern, bool showLongName) async {
+  var info = await infoFromFile(infoFile);
+  var nameRegExp = new RegExp(pattern);
   matches(e) => nameRegExp.hasMatch(longName(e));
 
   bool noResults = true;
@@ -49,7 +63,7 @@
   showMethods();
   showFields();
   if (noResults) {
-    print('error: no function or field that matches ${args[1]} was found.');
+    print('error: no function or field that matches $pattern was found.');
     exit(1);
   }
 }
diff --git a/bin/to_binary.dart b/bin/to_binary.dart
new file mode 100644
index 0000000..23c0cc8
--- /dev/null
+++ b/bin/to_binary.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, 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:io';
+
+import 'package:args/command_runner.dart';
+
+import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/binary_serialization.dart' as binary;
+import 'package:dart2js_info/src/io.dart';
+
+import 'usage_exception.dart';
+
+/// Converts a dump-info file emitted by dart2js in JSON to binary format.
+class ToBinaryCommand extends Command<void> with PrintUsageException {
+  final String name = "to_binary";
+  final String description = "Convert any info file to binary format.";
+
+  ToBinaryCommand() {
+    argParser.addOption('out',
+        abbr: 'o', help: 'Output file (defauts to <input>.data)');
+  }
+
+  void run() async {
+    if (argResults.rest.length < 1) {
+      usageException('Missing argument: <input-info>');
+      exit(1);
+    }
+
+    String filename = argResults.rest[0];
+    AllInfo info = await infoFromFile(filename);
+    String outputFilename = argResults['out'] ?? '$filename.data';
+    var outstream = new File(outputFilename).openWrite();
+    binary.encode(info, outstream);
+    await outstream.done;
+  }
+}
diff --git a/bin/to_json.dart b/bin/to_json.dart
new file mode 100644
index 0000000..ba81c55
--- /dev/null
+++ b/bin/to_json.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2019, 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:io';
+import 'dart:convert';
+
+import 'package:args/command_runner.dart';
+
+import 'package:dart2js_info/info.dart';
+import 'package:dart2js_info/json_info_codec.dart';
+import 'package:dart2js_info/src/io.dart';
+
+import 'usage_exception.dart';
+
+/// Converts a dump-info file emitted by dart2js in binary format to JSON.
+class ToJsonCommand extends Command<void> with PrintUsageException {
+  final String name = "to_json";
+  final String description = "Convert any info file to JSON format.";
+
+  ToJsonCommand() {
+    argParser.addOption('out',
+        abbr: 'o', help: 'Output file (defauts to <input>.json)');
+
+    argParser.addFlag('inject-text',
+        negatable: false,
+        help: 'Whether to inject output code snippets.\n\n'
+            'By default dart2js produces code spans, but excludes the text. This\n'
+            'option can be used to embed the text directly in the output JSON.\n'
+            'This is implied by `--compat-mode`.');
+
+    argParser.addFlag('compat-mode',
+        negatable: false,
+        help: 'Whether to generate an older version of the JSON format.\n\n'
+            'By default files are converted to the latest JSON format, but\n'
+            'passing `--compat-mode` will produce a JSON file that may still\n'
+            'work in the visualizer tool at:\n'
+            'https://dart-lang.github.io/dump-info-visualizer/.\n\n'
+            'Note, however, that files produced in this mode do not contain\n'
+            'all the data available in the input file.');
+  }
+
+  void run() async {
+    if (argResults.rest.length < 1) {
+      usageException('Missing argument: <input-info>');
+    }
+
+    String filename = argResults.rest[0];
+    bool isBackwardCompatible = argResults['compat-mode'];
+    AllInfo info = await infoFromFile(filename);
+
+    if (isBackwardCompatible || argResults['inject-text']) {
+      // Fill the text of each code span. The binary form produced by dart2js
+      // produces code spans, but excludes the orignal text
+      info.functions.forEach((f) {
+        f.code.forEach((span) => _fillSpan(span, f.outputUnit));
+      });
+      info.fields.forEach((f) {
+        f.code.forEach((span) => _fillSpan(span, f.outputUnit));
+      });
+      info.constants.forEach((c) {
+        c.code.forEach((span) => _fillSpan(span, c.outputUnit));
+      });
+    }
+
+    var json = new AllInfoJsonCodec(isBackwardCompatible: isBackwardCompatible)
+        .encode(info);
+    String outputFilename = argResults['out'] ?? '$filename.json';
+    new File(outputFilename)
+        .writeAsStringSync(const JsonEncoder.withIndent("  ").convert(json));
+  }
+}
+
+Map<String, String> _cache = {};
+
+_getContents(OutputUnitInfo unit) => _cache.putIfAbsent(unit.filename, () {
+      var uri = Uri.base.resolve(unit.filename);
+      return new File.fromUri(uri).readAsStringSync();
+    });
+
+_fillSpan(CodeSpan span, OutputUnitInfo unit) {
+  if (span.text == null && span.start != null && span.end != 0) {
+    var contents = _getContents(unit);
+    span.text = contents.substring(span.start, span.end);
+  }
+}
diff --git a/bin/to_proto.dart b/bin/to_proto.dart
new file mode 100644
index 0000000..616b8c8
--- /dev/null
+++ b/bin/to_proto.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, 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.
+
+/// Command-line tool to convert an info.json file ouputted by dart2js to the
+/// alternative protobuf format.
+
+import 'dart:io';
+
+import 'package:args/command_runner.dart';
+
+import 'package:dart2js_info/proto_info_codec.dart';
+import 'package:dart2js_info/src/io.dart';
+
+import 'usage_exception.dart';
+
+/// Converts a dump-info file emitted by dart2js to the proto format
+class ToProtoCommand extends Command<void> with PrintUsageException {
+  final String name = "to_proto";
+  final String description = "Convert any info file to proto format.";
+
+  ToProtoCommand() {
+    argParser.addOption('out',
+        abbr: 'o', help: 'Output file (defauts to <input>.pb)');
+  }
+
+  void run() async {
+    if (argResults.rest.length < 1) {
+      usageException('Missing argument: <input-info>');
+      exit(1);
+    }
+
+    String filename = argResults.rest[0];
+    final info = await infoFromFile(filename);
+    final proto = new AllInfoProtoCodec().encode(info);
+    String outputFilename = argResults['out'] ?? '$filename.pb';
+    final outputFile = new File(outputFilename);
+    await outputFile.writeAsBytes(proto.writeToBuffer(), mode: FileMode.write);
+  }
+}
diff --git a/bin/tools.dart b/bin/tools.dart
new file mode 100644
index 0000000..85f5281
--- /dev/null
+++ b/bin/tools.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, 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 'package:args/command_runner.dart';
+
+import 'code_deps.dart';
+import 'coverage_log_server.dart';
+import 'debug_info.dart';
+import 'diff.dart';
+import 'deferred_library_check.dart';
+import 'deferred_library_size.dart';
+import 'deferred_library_layout.dart';
+import 'convert.dart';
+import 'function_size_analysis.dart';
+import 'library_size_split.dart';
+import 'live_code_size_analysis.dart';
+import 'show_inferred_types.dart';
+
+/// Entrypoint to run all dart2js_info tools.
+void main(args) {
+  var commandRunner = new CommandRunner("dart2js_info",
+      "collection of tools to digest the output of dart2js's --dump-info")
+    ..addCommand(new CodeDepsCommand())
+    ..addCommand(new CoverageLogServerCommand())
+    ..addCommand(new DebugCommand())
+    ..addCommand(new DiffCommand())
+    ..addCommand(new DeferredLibraryCheck())
+    ..addCommand(new DeferredLibrarySize())
+    ..addCommand(new DeferredLibraryLayout())
+    ..addCommand(new ConvertCommand())
+    ..addCommand(new FunctionSizeCommand())
+    ..addCommand(new LibrarySizeCommand())
+    ..addCommand(new LiveCodeAnalysisCommand())
+    ..addCommand(new ShowInferredTypesCommand());
+  commandRunner.run(args);
+}
diff --git a/bin/usage_exception.dart b/bin/usage_exception.dart
new file mode 100644
index 0000000..0f59e5f
--- /dev/null
+++ b/bin/usage_exception.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2019, 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:io';
+import 'package:args/command_runner.dart';
+
+abstract class PrintUsageException implements Command<void> {
+  void usageException(String message) {
+    print(message);
+    printUsage();
+    exit(1);
+  }
+}
diff --git a/lib/proto_info_codec.dart b/lib/proto_info_codec.dart
index 40270f9..5cdf96d 100644
--- a/lib/proto_info_codec.dart
+++ b/lib/proto_info_codec.dart
@@ -58,9 +58,12 @@
   AllInfoPB convert(AllInfo info) => _convertToAllInfoPB(info);
 
   DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) {
-    return new DependencyInfoPB()
-      ..targetId = idFor(info.target)?.serializedId
-      ..mask = info.mask;
+    var result = new DependencyInfoPB()
+      ..targetId = idFor(info.target)?.serializedId;
+    if (info.mask != null) {
+      result.mask = info.mask;
+    }
+    return result;
   }
 
   static ParameterInfoPB _convertToParameterInfoPB(ParameterInfo info) {
@@ -222,10 +225,9 @@
   }
 
   ProgramInfoPB _convertToProgramInfoPB(ProgramInfo info) {
-    return new ProgramInfoPB()
+    var result = new ProgramInfoPB()
       ..entrypointId = idFor(info.entrypoint).serializedId
       ..size = info.size
-      ..dart2jsVersion = info.dart2jsVersion
       ..compilationMoment =
           new Int64(info.compilationMoment.microsecondsSinceEpoch)
       ..compilationDuration = new Int64(info.compilationDuration.inMicroseconds)
@@ -237,6 +239,11 @@
       ..isFunctionApplyUsed = info.isFunctionApplyUsed ?? false
       ..isMirrorsUsed = info.isMirrorsUsed ?? false
       ..minified = info.minified ?? false;
+
+    if (info.dart2jsVersion != null) {
+      result.dart2jsVersion = info.dart2jsVersion;
+    }
+    return result;
   }
 
   Iterable<AllInfoPB_AllInfosEntry> _convertToAllInfosEntries<T extends Info>(
diff --git a/pubspec.yaml b/pubspec.yaml
index 1a12162..8631f40 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -26,15 +26,4 @@
   test: ^1.2.0
 
 executables:
-  dart2js_info_code_deps:               code_deps
-  dart2js_info_coverage_log_server:     coverage_log_server
-  dart2js_info_debug_info:              debug_info
-  dart2js_info_diff:                    diff
-  dart2js_info_deferred_library_check:  deferred_library_check
-  dart2js_info_deferred_library_size:   deferred_library_size
-  dart2js_info_deferred_library_layout: deferred_library_layout
-  dart2js_info_json_to_proto:           info_json_to_proto
-  dart2js_info_function_size_analysis:  function_size_analysis
-  dart2js_info_library_size_split:      library_size_split
-  dart2js_info_live_code_size_analysis: live_code_size_analysis
-  dart2js_info_show_inferred_types:     show_inferred_types
+  dart2js_info: tools