diff --git a/bin/spack b/bin/spack
index 5ab805fe549c2def52584e1465505ddc45043cfa..496c705042221dafb6868368379d960ca3132c99 100755
--- a/bin/spack
+++ b/bin/spack
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-# flake8: noqa
 ##############################################################################
 # Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
 # Produced at the Lawrence Livermore National Laboratory.
@@ -26,34 +25,32 @@
 ##############################################################################
 from __future__ import print_function
 
+import os
 import sys
+
 if sys.version_info[:2] < (2, 6):
     v_info = sys.version_info[:3]
     sys.exit("Spack requires Python 2.6 or higher."
              "This is Python %d.%d.%d." % v_info)
 
-import os
-import inspect
-
 # Find spack's location and its prefix.
-SPACK_FILE = os.path.realpath(os.path.expanduser(__file__))
-os.environ["SPACK_FILE"] = SPACK_FILE
-SPACK_PREFIX = os.path.dirname(os.path.dirname(SPACK_FILE))
+spack_file = os.path.realpath(os.path.expanduser(__file__))
+spack_prefix = os.path.dirname(os.path.dirname(spack_file))
 
 # Allow spack libs to be imported in our scripts
-SPACK_LIB_PATH = os.path.join(SPACK_PREFIX, "lib", "spack")
-sys.path.insert(0, SPACK_LIB_PATH)
+spack_lib_path = os.path.join(spack_prefix, "lib", "spack")
+sys.path.insert(0, spack_lib_path)
 
 # Add external libs
-SPACK_EXTERNAL_LIBS = os.path.join(SPACK_LIB_PATH, "external")
-sys.path.insert(0, SPACK_EXTERNAL_LIBS)
+spack_external_libs = os.path.join(spack_lib_path, "external")
+sys.path.insert(0, spack_external_libs)
 
 # Handle vendoring of YAML specially, as it has two versions.
 if sys.version_info[0] == 2:
-    SPACK_YAML_LIBS = os.path.join(SPACK_EXTERNAL_LIBS, "yaml/lib")
+    spack_yaml_libs = os.path.join(spack_external_libs, "yaml/lib")
 else:
-    SPACK_YAML_LIBS = os.path.join(SPACK_EXTERNAL_LIBS, "yaml/lib3")
-sys.path.insert(0, SPACK_YAML_LIBS)
+    spack_yaml_libs = os.path.join(spack_external_libs, "yaml/lib3")
+sys.path.insert(0, spack_yaml_libs)
 
 # Quick and dirty check to clean orphaned .pyc files left over from
 # previous revisions.  These files were present in earlier versions of
@@ -61,13 +58,13 @@ sys.path.insert(0, SPACK_YAML_LIBS)
 # imports.  If we leave them, Spack will fail in mysterious ways.
 # TODO: more elegant solution for orphaned pyc files.
 orphaned_pyc_files = [
-    os.path.join(SPACK_EXTERNAL_LIBS, 'functools.pyc'),
-    os.path.join(SPACK_EXTERNAL_LIBS, 'ordereddict.pyc'),
-    os.path.join(SPACK_LIB_PATH, 'spack', 'platforms', 'cray_xc.pyc'),
-    os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'package-list.pyc'),
-    os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'test-install.pyc'),
-    os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'url-parse.pyc'),
-    os.path.join(SPACK_LIB_PATH, 'spack', 'test', 'yaml.pyc')
+    os.path.join(spack_external_libs, 'functools.pyc'),
+    os.path.join(spack_external_libs, 'ordereddict.pyc'),
+    os.path.join(spack_lib_path, 'spack', 'platforms', 'cray_xc.pyc'),
+    os.path.join(spack_lib_path, 'spack', 'cmd', 'package-list.pyc'),
+    os.path.join(spack_lib_path, 'spack', 'cmd', 'test-install.pyc'),
+    os.path.join(spack_lib_path, 'spack', 'cmd', 'url-parse.pyc'),
+    os.path.join(spack_lib_path, 'spack', 'test', 'yaml.pyc')
 ]
 
 for pyc_file in orphaned_pyc_files:
@@ -79,183 +76,6 @@ for pyc_file in orphaned_pyc_files:
         print("WARNING: Spack may fail mysteriously. "
               "Couldn't remove orphaned .pyc file: %s" % pyc_file)
 
-# If there is no working directory, use the spack prefix.
-try:
-    working_dir = os.getcwd()
-except OSError:
-    os.chdir(SPACK_PREFIX)
-    working_dir = SPACK_PREFIX
-
-# clean up the scope and start using spack package instead.
-del SPACK_FILE, SPACK_PREFIX, SPACK_LIB_PATH
-import llnl.util.tty as tty
-from llnl.util.tty.color import *
-import spack
-from spack.error import SpackError
-import argparse
-import pstats
-
-# Get the allowed names of statistics for cProfile, and make a list of
-# groups of 7 names to wrap them nicely.
-stat_names = pstats.Stats.sort_arg_dict_default
-stat_lines = list(zip(*(iter(stat_names),)*7))
-
-# Command parsing
-parser = argparse.ArgumentParser(
-    formatter_class=argparse.RawTextHelpFormatter,
-    description="Spack: the Supercomputing PACKage Manager." + colorize("""
-
-spec expressions:
-  PACKAGE [CONSTRAINTS]
-
-    CONSTRAINTS:
-      @c{@version}
-      @g{%compiler  @compiler_version}
-      @B{+variant}
-      @r{-variant} or @r{~variant}
-      @m{=architecture}
-      [^DEPENDENCY [CONSTRAINTS] ...]"""))
-
-parser.add_argument('-d', '--debug', action='store_true',
-                    help="write out debug logs during compile")
-parser.add_argument('-D', '--pdb', action='store_true',
-                    help="run spack under the pdb debugger")
-parser.add_argument('-k', '--insecure', action='store_true',
-                    help="do not check ssl certificates when downloading")
-parser.add_argument('-m', '--mock', action='store_true',
-                    help="use mock packages instead of real ones")
-parser.add_argument('-p', '--profile', action='store_true',
-                    help="profile execution using cProfile")
-parser.add_argument('-P', '--sorted-profile', default=None, metavar="STAT",
-                    help="profile and sort by one or more of:\n[%s]" %
-                    ',\n '.join([', '.join(line) for line in stat_lines]))
-parser.add_argument('--lines', default=20, action='store',
-                    help="lines of profile output: default 20; 'all' for all")
-parser.add_argument('-v', '--verbose', action='store_true',
-                    help="print additional output during builds")
-parser.add_argument('-s', '--stacktrace', action='store_true',
-                    help="add stacktrace info to all printed statements")
-parser.add_argument('-V', '--version', action='version',
-                    version="%s" % spack.spack_version)
-
-# each command module implements a parser() function, to which we pass its
-# subparser for setup.
-subparsers = parser.add_subparsers(metavar='SUBCOMMAND', dest="command")
-
-
-import spack.cmd
-for cmd in spack.cmd.commands:
-    module = spack.cmd.get_module(cmd)
-    cmd_name = cmd.replace('_', '-')
-    subparser = subparsers.add_parser(cmd_name, help=module.description)
-    module.setup_parser(subparser)
-
-
-def _main(args, unknown_args):
-    # Set up environment based on args.
-    tty.set_verbose(args.verbose)
-    tty.set_debug(args.debug)
-    tty.set_stacktrace(args.stacktrace)
-    spack.debug = args.debug
-
-    if spack.debug:
-        import spack.util.debug as debug
-        debug.register_interrupt_handler()
-
-    # Run any available pre-run hooks
-    spack.hooks.pre_run()
-
-    spack.spack_working_dir = working_dir
-    if args.mock:
-        from spack.repository import RepoPath
-        spack.repo.swap(RepoPath(spack.mock_packages_path))
-
-    # If the user asked for it, don't check ssl certs.
-    if args.insecure:
-        tty.warn("You asked for --insecure. Will NOT check SSL certificates.")
-        spack.insecure = True
-
-    # Try to load the particular command asked for and run it
-    command = spack.cmd.get_command(args.command.replace('-', '_'))
-
-    # Allow commands to inject an optional argument and get unknown args
-    # if they want to handle them.
-    info = dict(inspect.getmembers(command))
-    varnames = info['__code__'].co_varnames
-    argcount = info['__code__'].co_argcount
-
-    # Actually execute the command
-    try:
-        if argcount == 3 and varnames[2] == 'unknown_args':
-            return_val = command(parser, args, unknown_args)
-        else:
-            if unknown_args:
-                tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
-            return_val = command(parser, args)
-    except SpackError as e:
-        e.die()
-    except Exception as e:
-        tty.die(str(e))
-    except KeyboardInterrupt:
-        sys.stderr.write('\n')
-        tty.die("Keyboard interrupt.")
-
-    # Allow commands to return values if they want to exit with some other code.
-    if return_val is None:
-        sys.exit(0)
-    elif isinstance(return_val, int):
-        sys.exit(return_val)
-    else:
-        tty.die("Bad return value from command %s: %s"
-                % (args.command, return_val))
-
-
-def main(args):
-    # Just print help and exit if run with no arguments at all
-    if len(args) == 1:
-        parser.print_help()
-        sys.exit(1)
-
-    # actually parse the args.
-    args, unknown = parser.parse_known_args()
-
-    if args.profile or args.sorted_profile:
-        import cProfile
-
-        try:
-            nlines = int(args.lines)
-        except ValueError:
-            if args.lines != 'all':
-                tty.die('Invalid number for --lines: %s' % args.lines)
-            nlines = -1
-
-        # allow comma-separated list of fields
-        sortby = ['time']
-        if args.sorted_profile:
-            sortby = args.sorted_profile.split(',')
-            for stat in sortby:
-                if stat not in stat_names:
-                    tty.die("Invalid sort field: %s" % stat)
-
-        try:
-            # make a profiler and run the code.
-            pr = cProfile.Profile()
-            pr.enable()
-            _main(args, unknown)
-        finally:
-            pr.disable()
-
-            # print out  profile stats.
-            stats = pstats.Stats(pr)
-            stats.sort_stats(*sortby)
-            stats.print_stats(nlines)
-
-    elif args.pdb:
-        import pdb
-        pdb.runctx('_main(args, unknown)', globals(), locals())
-    else:
-        _main(args, unknown)
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+# Once we've set up the system path, run the spack main method
+import spack.main  # noqa
+sys.exit(spack.main.main())
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 73963b848c65359b885a3d9fb3357c7debd77df0..27283d10a96851720dabd8277669d4240bc687c1 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -217,5 +217,5 @@
 
 # Add default values for attributes that would otherwise be modified from
 # Spack main script
-debug = True
+debug = False
 spack_working_dir = None
diff --git a/lib/spack/spack/cmd/activate.py b/lib/spack/spack/cmd/activate.py
index f21799753b55360ea0978ee26ecce4456a8a4d5d..f7e826efd6e07d56a8556565ac3d777a45f79d26 100644
--- a/lib/spack/spack/cmd/activate.py
+++ b/lib/spack/spack/cmd/activate.py
@@ -28,6 +28,8 @@
 import spack.cmd
 
 description = "activate a package extension"
+section = "extensions"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/arch.py b/lib/spack/spack/cmd/arch.py
index 1079e7f215fa87a30f4b1e16570ba5323a0be3f7..d4241dcae940c92bf24dd928b2eb4e2b0bec8838 100644
--- a/lib/spack/spack/cmd/arch.py
+++ b/lib/spack/spack/cmd/arch.py
@@ -27,6 +27,8 @@
 import spack.architecture as architecture
 
 description = "print architecture information about this machine"
+section = "system"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index a804086a38961966a5edd8fbabe1409ac14754a7..b6daf4f09bf8940d8a1d3cbb43ae0ca660f71014 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -33,6 +33,8 @@
 _SPACK_UPSTREAM = 'https://github.com/llnl/spack'
 
 description = "create a new installation of spack in another prefix"
+section = "admin"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py
index 877f2ce0cfc1cfddf2f80308cb4048ea28c09557..cc63c6593b82bf2d891d0a5d5aec5c0371d98fcc 100644
--- a/lib/spack/spack/cmd/build.py
+++ b/lib/spack/spack/cmd/build.py
@@ -27,6 +27,9 @@
 from spack import *
 
 description = 'stops at build stage when installing a package, if possible'
+section = "build"
+level = "long"
+
 
 build_system_to_phase = {
     AutotoolsPackage: 'build',
diff --git a/lib/spack/spack/cmd/cd.py b/lib/spack/spack/cmd/cd.py
index 784ad4ac83cefc1cd10fdce56b9bf7b51b626586..531f3c59fd19930a61ac415ec1c8eefac849f8bc 100644
--- a/lib/spack/spack/cmd/cd.py
+++ b/lib/spack/spack/cmd/cd.py
@@ -26,6 +26,8 @@
 import spack.modules
 
 description = "cd to spack directories in the shell"
+section = "environment"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index fda9beed27c4d6b91ef8f08d8ec6378d5a69ef0b..d8a17fd3836301540805681811cc1581d2a7b7f4 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -36,6 +36,8 @@
 from spack.version import *
 
 description = "checksum available versions of a package"
+section = "packaging"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
index 6c70b5bd38f8e4fcb6d58213429016fae3a1e82f..23507822ef4e75e76f5d976dc694a7e226a7a5fe 100644
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -30,6 +30,8 @@
 import spack.cmd
 
 description = "remove build stage and source tarball for packages"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
index 6067d44c5ef31d4f00e71bb73da7d46f0f0c7f81..f2eeca20ab37e94bb5d95894bd06e71cac1c3031 100644
--- a/lib/spack/spack/cmd/compiler.py
+++ b/lib/spack/spack/cmd/compiler.py
@@ -39,6 +39,8 @@
 from spack.util.environment import get_path
 
 description = "manage compilers"
+section = "system"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/compilers.py b/lib/spack/spack/cmd/compilers.py
index 934fc6cf061cdef093ff968ecea7a65bfa2689fb..f0e21f987ec9fd8fff6ffddcc05ce68c3f428759 100644
--- a/lib/spack/spack/cmd/compilers.py
+++ b/lib/spack/spack/cmd/compilers.py
@@ -25,7 +25,9 @@
 import spack
 from spack.cmd.compiler import compiler_list
 
-description = "list available compilers, same as 'spack compiler list'"
+description = "list available compilers"
+section = "system"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
index a647e3ed6e6f2ce3064b05be92ba82643ad8909b..61c2c6f0e8b91dedf51b25c3b1e03e78a66d6c76 100644
--- a/lib/spack/spack/cmd/config.py
+++ b/lib/spack/spack/cmd/config.py
@@ -25,6 +25,8 @@
 import spack.config
 
 description = "get and set configuration options"
+section = "config"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py
index 7f6c07c34b72f9ecff584141c2314cbe186bea28..7cab5660524591e9a2aae9d8cd4833a0f3682e75 100644
--- a/lib/spack/spack/cmd/configure.py
+++ b/lib/spack/spack/cmd/configure.py
@@ -30,7 +30,9 @@
 
 from spack import *
 
-description = 'stops at configuration stage when installing a package, if possible'  # NOQA: ignore=E501
+description = 'stage and configure a package but do not install'
+section = "build"
+level = "long"
 
 
 build_system_to_phase = {
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index adaf388387b9041ed8f27a6de49efd7d350edb63..89ba050a538bb9e077746bd2cb0ed3e5d43613ce 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -40,6 +40,9 @@
 from spack.url import *
 
 description = "create a new package file"
+section = "packaging"
+level = "short"
+
 
 package_template = '''\
 ##############################################################################
diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py
index 7ea20392362059d397e8b6b81238a32eb31f3671..3d8020d064abbf97694919b18201f19cfbf648ad 100644
--- a/lib/spack/spack/cmd/deactivate.py
+++ b/lib/spack/spack/cmd/deactivate.py
@@ -31,6 +31,8 @@
 from spack.graph import topological_sort
 
 description = "deactivate a package extension"
+section = "extensions"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/debug.py b/lib/spack/spack/cmd/debug.py
index 06dea9ea70887145b34e5e9077258c35475a70db..ba5f7838394c5e7ee0a2e515601d9da2c60c5d5e 100644
--- a/lib/spack/spack/cmd/debug.py
+++ b/lib/spack/spack/cmd/debug.py
@@ -34,6 +34,8 @@
 from spack.util.executable import which
 
 description = "debugging commands for troubleshooting Spack"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/dependents.py b/lib/spack/spack/cmd/dependents.py
index c752ffb9432ab52cc464237753fd40fc1cd68f26..6c481548d389823252c39e1886974737f8b6cde3 100644
--- a/lib/spack/spack/cmd/dependents.py
+++ b/lib/spack/spack/cmd/dependents.py
@@ -31,6 +31,8 @@
 import spack.cmd
 
 description = "show installed packages that depend on another"
+section = "basic"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/diy.py b/lib/spack/spack/cmd/diy.py
index c67e189f7330cecd94b3a248c060a93583f81257..14d28bb3f4c256d7d53c412aa8fc6b9e1ab4645d 100644
--- a/lib/spack/spack/cmd/diy.py
+++ b/lib/spack/spack/cmd/diy.py
@@ -34,6 +34,8 @@
 from spack.stage import DIYStage
 
 description = "do-it-yourself: build from an existing source directory"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/docs.py b/lib/spack/spack/cmd/docs.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe026da4a7d25efe2b494201f01de2ebc7f128b7
--- /dev/null
+++ b/lib/spack/spack/cmd/docs.py
@@ -0,0 +1,33 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import webbrowser
+
+description = 'open spack documentation in a web browser'
+section = 'help'
+level = 'short'
+
+
+def docs(parser, args):
+    webbrowser.open('https://spack.readthedocs.io')
diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py
index 01f2b6188768d2b8bfcdbb7009c13aab0011c931..0287b8cd67744c8d0fbb44c3b50e61dee20d8a9f 100644
--- a/lib/spack/spack/cmd/edit.py
+++ b/lib/spack/spack/cmd/edit.py
@@ -33,6 +33,8 @@
 from spack.repository import Repo
 
 description = "open package files in $EDITOR"
+section = "packaging"
+level = "short"
 
 
 def edit_package(name, repo_path, namespace):
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index ed18940ac095372ea79d8f813dd2fdaf13f35d1c..034b710b852eeddd0027ca1e3b8be3ce3419590c 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -31,7 +31,9 @@
 import spack.cmd
 import spack.build_environment as build_env
 
-description = "run a command with the install environment for a spec"
+description = "show install environment for a spec, and run commands"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py
index 94a3e8288f5a3bb710fe4b1d246e14794769e84f..d073d42cc3bba13971a93fdae51009ed5474a091 100644
--- a/lib/spack/spack/cmd/extensions.py
+++ b/lib/spack/spack/cmd/extensions.py
@@ -33,6 +33,8 @@
 import spack.store
 
 description = "list extensions for package"
+section = "extensions"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py
index 35cc23a9631b93f9a69facec6ab8b32036d61381..cf39d4a40bd1719f46943673c23681c02bd2230f 100644
--- a/lib/spack/spack/cmd/fetch.py
+++ b/lib/spack/spack/cmd/fetch.py
@@ -28,6 +28,8 @@
 import spack.cmd
 
 description = "fetch archives for packages"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index 3a6d8270fb87d0d0459c229dac92c0d6ed2286db..0142155fe18c22f44719de01ef4df755a42dbf61 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -29,7 +29,9 @@
 
 from spack.cmd import display_specs
 
-description = "find installed spack packages"
+description = "list and search installed packages"
+section = "basic"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/flake8.py b/lib/spack/spack/cmd/flake8.py
index 42d36a3beb264911ef60b221a37b620c86777bca..9753b20e788e871f3ae316755178bb149a8e0c7b 100644
--- a/lib/spack/spack/cmd/flake8.py
+++ b/lib/spack/spack/cmd/flake8.py
@@ -36,7 +36,11 @@
 import spack
 from spack.util.executable import *
 
+
 description = "runs source code style checks on Spack. requires flake8"
+section = "developer"
+level = "long"
+
 
 """List of directories to exclude from checks."""
 exclude_directories = [spack.external_path]
diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py
index ee401d8fb7a6aeb11de7ef8f88309a436eb29056..3a82f39ce55487547dc0252d4e1c0bc8d4ed9bd9 100644
--- a/lib/spack/spack/cmd/graph.py
+++ b/lib/spack/spack/cmd/graph.py
@@ -34,6 +34,8 @@
 from spack.graph import *
 
 description = "generate graphs of package dependency relationships"
+section = "basic"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/help.py b/lib/spack/spack/cmd/help.py
index e867ca12958322b101cd927fc6c2c9fd76eff9a8..313b082e2f022cd6304ea0d6901584eef4d03985 100644
--- a/lib/spack/spack/cmd/help.py
+++ b/lib/spack/spack/cmd/help.py
@@ -22,16 +22,100 @@
 # License along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
+import sys
+from llnl.util.tty import colorize
+
 description = "get help on spack and its commands"
+section = "help"
+level = "short"
+
+#
+# These are longer guides on particular aspects of Spack. Currently there
+# is only one on spec syntax.
+#
+spec_guide = """\
+spec expression syntax:
+
+  package [constraints] [^dependency [constraints] ...]
+
+  package                           any package from 'spack list'
+
+  constraints:
+    versions:
+      @c{@version}                      single version
+      @c{@min:max}                      version range (inclusive)
+      @c{@min:}                         version <min> or higher
+      @c{@:max}                         up to version <max> (inclusive)
+
+    compilers:
+      @g{%compiler}                     build with <compiler>
+      @g{%compiler@version}             build with specific compiler version
+      @g{%compiler@min:max}             specific version range (see above)
+
+    variants:
+      @B{+variant}                      enable <variant>
+      @r{-variant} or @r{~variant}          disable <variant>
+      @B{variant=value}                 set non-boolean <variant> to <value>
+      @B{variant=value1,value2,value3}  set multi-value <variant> values
+
+    architecture variants:
+      @m{target=target}                 specific <target> processor
+      @m{os=operating_system}           specific <operating_system>
+      @m{platform=platform}             linux, darwin, cray, bgq, etc.
+      @m{arch=platform-os-target}       shortcut for all three above
+
+    cross-compiling:
+      @m{os=backend} or @m{os=be}           build for compute node (backend)
+      @m{os=frontend} or @m{os=fe}          build for login node (frontend)
+
+    dependencies:
+      ^dependency [constraints]     specify constraints on dependencies
+
+  examples:
+      hdf5                          any hdf5 configuration
+      hdf5 @c{@1.10.1}                  hdf5 version 1.10.1
+      hdf5 @c{@1.8:}                    hdf5 1.8 or higher
+      hdf5 @c{@1.8:} @g{%gcc}               hdf5 1.8 or higher built with gcc
+      hdf5 @B{+mpi}                     hdf5 with mpi enabled
+      hdf5 @r{~mpi}                     hdf5 with mpi disabled
+      hdf5 @B{+mpi} ^mpich              hdf5 with mpi, using mpich
+      hdf5 @B{+mpi} ^openmpi@c{@1.7}        hdf5 wtih mpi, using openmpi 1.7
+      boxlib @B{dim=2}                  boxlib built for 2 dimensions
+      libdwarf @g{%intel} ^libelf@g{%gcc}
+          libdwarf, built with intel compiler, linked to libelf built with gcc
+      mvapich2 @g{%pgi} @B{fabrics=psm,mrail,sock}
+          mvapich2, built with pgi compiler, with support for multiple fabrics
+"""
+
+
+guides = {
+    'spec': spec_guide,
+}
 
 
 def setup_parser(subparser):
-    subparser.add_argument('help_command', nargs='?', default=None,
-                           help='command to get help on')
+    help_cmd_group = subparser.add_mutually_exclusive_group()
+    help_cmd_group.add_argument('help_command', nargs='?', default=None,
+                                help='command to get help on')
+
+    help_all_group = subparser.add_mutually_exclusive_group()
+    help_all_group.add_argument(
+        '-a', '--all', action='store_const', const='long', default='short',
+        help='print all available commands')
+
+    help_spec_group = subparser.add_mutually_exclusive_group()
+    help_spec_group.add_argument(
+        '--spec', action='store_const', dest='guide', const='spec',
+        default=None, help='print all available commands')
 
 
 def help(parser, args):
+    if args.guide:
+        print(colorize(guides[args.guide]))
+        return 0
+
     if args.help_command:
+        parser.add_command(args.help_command)
         parser.parse_args([args.help_command, '-h'])
     else:
-        parser.print_help()
+        sys.stdout.write(parser.format_help(level=args.all))
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 86ec839b90e9ddb5d2821edee60cd629bba119eb..62de5484af17ce6ee6c3eaaa7053bde82d754e65 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -31,6 +31,8 @@
 import spack.fetch_strategy as fs
 
 description = "get detailed information on a particular package"
+section = "basic"
+level = "short"
 
 
 def padder(str_list, extra=0):
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index fb01fc2d5e1acfcf50d5e8c06d78daa525c0c589..87fad761818bfe7a463a29e969f13dec02e57d03 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -41,6 +41,8 @@
 from spack.package import PackageBase
 
 description = "build and install packages"
+section = "build"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py
index bcfb092945295fce03c70c0e1146db3f2b8d9be5..72be99d260b304575771c5ff6515af65c1e90eb8 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -35,7 +35,10 @@
 import spack
 from llnl.util.tty.colify import colify
 
-description = "print available spack packages to stdout in different formats"
+description = "list and search available packages"
+section = "basic"
+level = "short"
+
 
 formatters = {}
 
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index cdc3a741ae00634cdc133086c0b29acdd5dd65df..106a95c9c2b118d892bef4cfb6a14be532733171 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -25,7 +25,9 @@
 import argparse
 import spack.modules
 
-description = "add package to environment using modules"
+description = "add package to environment using `module load`"
+section = "environment"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py
index d1a782563024b4faf3696cb65443c0cdebea7b9f..e713d028d2adc919919b8e774cbb5bb80aef0f43 100644
--- a/lib/spack/spack/cmd/location.py
+++ b/lib/spack/spack/cmd/location.py
@@ -31,6 +31,8 @@
 import spack.cmd
 
 description = "print out locations of various directories used by Spack"
+section = "environment"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/md5.py b/lib/spack/spack/cmd/md5.py
index fc205cc6933145c0f04d721f2a7f6064cce75a5b..1d121f012097dcad30df29f001bff2b632f4f6ce 100644
--- a/lib/spack/spack/cmd/md5.py
+++ b/lib/spack/spack/cmd/md5.py
@@ -32,6 +32,8 @@
 from spack.stage import Stage, FailedDownloadError
 
 description = "calculate md5 checksums for files/urls"
+section = "packaging"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
index 528fcbfc3fd3ed6eaa0681844ac3bc4e754a9e8d..e5b3b492b4df3394b24d20442f0c90b3a64b7e79 100644
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -38,6 +38,8 @@
 from spack.util.spack_yaml import syaml_dict
 
 description = "manage mirrors"
+section = "config"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
index 37c79a358bbef780433053c54610527f79eead7f..f8253aad6f4bd725b4c7e8097843476646b17b3c 100644
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -36,6 +36,9 @@
 from spack.modules import module_types
 
 description = "manipulate module files"
+section = "environment"
+level = "short"
+
 
 # Dictionary that will be populated with the list of sub-commands
 # Each sub-command must be callable and accept 3 arguments :
diff --git a/lib/spack/spack/cmd/patch.py b/lib/spack/spack/cmd/patch.py
index 2e332554ad8a1db19596aed390686690a8a5cec0..dfe45b0494ec08b8ff8d6d6e5a3d617c3c0b7a35 100644
--- a/lib/spack/spack/cmd/patch.py
+++ b/lib/spack/spack/cmd/patch.py
@@ -30,6 +30,8 @@
 
 
 description = "patch expanded archive sources in preparation for install"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/pkg.py b/lib/spack/spack/cmd/pkg.py
index 12dcb817924df8ae45f9cd88fa83768de1e91fe9..aca69e9c993b26eb204d77ec6a120cf8960ca6b0 100644
--- a/lib/spack/spack/cmd/pkg.py
+++ b/lib/spack/spack/cmd/pkg.py
@@ -34,6 +34,8 @@
 from spack.util.executable import *
 
 description = "query packages associated with particular git revisions"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/providers.py b/lib/spack/spack/cmd/providers.py
index 470e3e5ed2f451667d3267e652691736c56b5713..f30c28a951de59ffaa93e69c9e57daab1e6c9111 100644
--- a/lib/spack/spack/cmd/providers.py
+++ b/lib/spack/spack/cmd/providers.py
@@ -30,6 +30,8 @@
 import spack.cmd
 
 description = "list packages that provide a particular virtual package"
+section = "basic"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/purge.py b/lib/spack/spack/cmd/purge.py
index 56165d5d97af1a21f056dbc578f9b5147eb0aac4..b7ebb0fc69eeb0ffba7afe1c6bb90670e7ff5dad 100644
--- a/lib/spack/spack/cmd/purge.py
+++ b/lib/spack/spack/cmd/purge.py
@@ -26,6 +26,8 @@
 import spack.stage as stage
 
 description = "remove temporary build files and/or downloaded archives"
+section = "admin"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/doc.py b/lib/spack/spack/cmd/pydoc.py
similarity index 91%
rename from lib/spack/spack/cmd/doc.py
rename to lib/spack/spack/cmd/pydoc.py
index 12ae6b497383c3b8d914c391bee1e9c2af0d29c1..c9003184c4e50ad9cd4e2ebe1cafa219f59e783b 100644
--- a/lib/spack/spack/cmd/doc.py
+++ b/lib/spack/spack/cmd/pydoc.py
@@ -1,5 +1,5 @@
 ##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
 # Produced at the Lawrence Livermore National Laboratory.
 #
 # This file is part of Spack.
@@ -24,11 +24,13 @@
 ##############################################################################
 
 description = "run pydoc from within spack"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
     subparser.add_argument('entity', help="run pydoc help on entity")
 
 
-def doc(parser, args):
+def pydoc(parser, args):
     help(args.entity)
diff --git a/lib/spack/spack/cmd/python.py b/lib/spack/spack/cmd/python.py
index 6df950758036d40aca1b87ea1a838e080745a8d2..3c4fbf9e87bf021cc1951706361bb32db58ebe4e 100644
--- a/lib/spack/spack/cmd/python.py
+++ b/lib/spack/spack/cmd/python.py
@@ -32,6 +32,8 @@
 
 
 description = "launch an interpreter as spack would launch a command"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/reindex.py b/lib/spack/spack/cmd/reindex.py
index 0bbd85069fc9267fd6ab7ad9f571fdfd1b549d13..dff127bc06916adf0ebbb7745753175a2437b6c4 100644
--- a/lib/spack/spack/cmd/reindex.py
+++ b/lib/spack/spack/cmd/reindex.py
@@ -26,6 +26,9 @@
 import spack.store
 description = "rebuild Spack's package database"
 
+section = "admin"
+level = "long"
+
 
 def reindex(parser, args):
     spack.store.db.reindex(spack.store.layout)
diff --git a/lib/spack/spack/cmd/repo.py b/lib/spack/spack/cmd/repo.py
index dd75f148c20098ad5ed005d12748f1f6c5dc7857..5beb0083e2285f37672e1eb3cd3e96a77ef81768 100644
--- a/lib/spack/spack/cmd/repo.py
+++ b/lib/spack/spack/cmd/repo.py
@@ -33,6 +33,8 @@
 from spack.repository import *
 
 description = "manage package source repositories"
+section = "config"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/restage.py b/lib/spack/spack/cmd/restage.py
index 36fee9237b5f9ff36c1962b537f2eba3008b3a95..4cecf4b42e6c7638f23c87a269259c4da136152f 100644
--- a/lib/spack/spack/cmd/restage.py
+++ b/lib/spack/spack/cmd/restage.py
@@ -30,6 +30,8 @@
 import spack.cmd
 
 description = "revert checked out package source code"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/setup.py b/lib/spack/spack/cmd/setup.py
index 82d00f4e11f4d4f9b54d11ed30d1998037898f27..79e7bca1ab0cbbbedb5773d164d8c103891d7694 100644
--- a/lib/spack/spack/cmd/setup.py
+++ b/lib/spack/spack/cmd/setup.py
@@ -39,6 +39,8 @@
 from spack.stage import DIYStage
 
 description = "create a configuration script and module, but don't build"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index 2e917d2ee35e5451b7a77c75764bdc093a9e0cb8..f4105900cb1f4fab1f903b0f089d6c7a6c9ebbb1 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -29,7 +29,9 @@
 import spack.cmd
 import spack.cmd.common.arguments as arguments
 
-description = "print out abstract and concrete versions of a spec"
+description = "show what would be installed, given a spec"
+section = "build"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py
index e0023b7254dc775555387ba2734574039676b44f..a469cd896da979b90129d77d7ce92cab59c0656e 100644
--- a/lib/spack/spack/cmd/stage.py
+++ b/lib/spack/spack/cmd/stage.py
@@ -29,6 +29,8 @@
 import spack.cmd
 
 description = "expand downloaded archive in preparation for install"
+section = "build"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py
index 9384e3a9e62787969e1a5af6cbc81454411cfc17..f7ec6a10e0d86e949b82148d85d7b0e6a4060a87 100644
--- a/lib/spack/spack/cmd/test.py
+++ b/lib/spack/spack/cmd/test.py
@@ -36,7 +36,9 @@
 
 import spack
 
-description = "a thin wrapper around the pytest command"
+description = "run spack's unit tests"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index f3eaddf88aba39eaf61975c927562c3d6fc1e1e1..6880409c56c99e02744560cd4bb47ddca4139c94 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -33,7 +33,9 @@
 
 from llnl.util import tty
 
-description = "remove an installed package"
+description = "remove installed packages"
+section = "build"
+level = "short"
 
 error_message = """You can either:
     a) use a more specific spec, or
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index 5da6f5daa58bbf83698147867c0d2b3a5f999835..8a0511f64c53232be6c0baf5d203b8f126dc040d 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -25,7 +25,9 @@
 import argparse
 import spack.modules
 
-description = "remove package from environment using module"
+description = "remove package from environment using `module unload`"
+section = "environment"
+level = "short"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/unuse.py b/lib/spack/spack/cmd/unuse.py
index e47974945781b5cd9ee5549054c45cce309a5e64..77312d1204fceef19273426dea27273931ceaf87 100644
--- a/lib/spack/spack/cmd/unuse.py
+++ b/lib/spack/spack/cmd/unuse.py
@@ -26,6 +26,8 @@
 import spack.modules
 
 description = "remove package from environment using dotkit"
+section = "environment"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/url.py b/lib/spack/spack/cmd/url.py
index e5cfce0de363da75d5ba54754452d6d907ffad50..28118a178a272d5afa01346e8682887765ad1d34 100644
--- a/lib/spack/spack/cmd/url.py
+++ b/lib/spack/spack/cmd/url.py
@@ -34,6 +34,8 @@
 from spack.util.naming import simplify_name
 
 description = "debugging tool for url parsing"
+section = "developer"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/use.py b/lib/spack/spack/cmd/use.py
index c9714d9de07de9fa15ac9076de10438631f5d7c5..e67de3a8b30f390d9552de847af48026c619c599 100644
--- a/lib/spack/spack/cmd/use.py
+++ b/lib/spack/spack/cmd/use.py
@@ -26,6 +26,8 @@
 import spack.modules
 
 description = "add package to environment using dotkit"
+section = "environment"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/versions.py b/lib/spack/spack/cmd/versions.py
index a6f6805fb023ee38be95a87417d94e73a73771f7..446f0a876d44842d445d6d5a4519309051afb71f 100644
--- a/lib/spack/spack/cmd/versions.py
+++ b/lib/spack/spack/cmd/versions.py
@@ -29,6 +29,8 @@
 import spack
 
 description = "list available versions of a package"
+section = "packaging"
+level = "long"
 
 
 def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/view.py b/lib/spack/spack/cmd/view.py
index 72e139d123cf5fb3ea188041e4c61086f5a22011..8fb94d3f3780d9fc9a9d9797512deec5606e8c81 100644
--- a/lib/spack/spack/cmd/view.py
+++ b/lib/spack/spack/cmd/view.py
@@ -69,7 +69,9 @@
 import spack.cmd
 import llnl.util.tty as tty
 
-description = "produce a single-rooted directory view of a spec"
+description = "produce a single-rooted directory view of packages"
+section = "environment"
+level = "short"
 
 
 def setup_parser(sp):
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..39c64b3ce03b1debda45c2c8e82b8239b9d83ddb
--- /dev/null
+++ b/lib/spack/spack/main.py
@@ -0,0 +1,468 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""This is the implementation of the Spack command line executable.
+
+In a normal Spack installation, this is invoked from the bin/spack script
+after the system path is set up.
+"""
+from __future__ import print_function
+
+import sys
+import os
+import inspect
+from argparse import _ArgumentGroup, ArgumentParser, RawTextHelpFormatter
+import pstats
+
+import llnl.util.tty as tty
+from llnl.util.tty.color import *
+
+import spack
+import spack.cmd
+from spack.error import SpackError
+
+
+# names of profile statistics
+stat_names = pstats.Stats.sort_arg_dict_default
+
+# help levels in order of detail (i.e., number of commands shown)
+levels = ['short', 'long']
+
+# intro text for help at different levels
+intro_by_level = {
+    'short': 'These are common spack commands:',
+    'long':  'Complete list of spack commands:',
+}
+
+# control top-level spack options shown in basic vs. advanced help
+options_by_level = {
+    'short': 'hkV',
+    'long': 'all'
+}
+
+# Longer text for each section, to show in help
+section_descriptions = {
+    'admin':       'administration',
+    'basic':       'query packages',
+    'build':       'build packages',
+    'config':      'configuration',
+    'developer':   'developer',
+    'environment': 'environment',
+    'extensions':  'extensions',
+    'help':        'more help',
+    'packaging':   'create packages',
+    'system':      'system',
+}
+
+# preferential command order for some sections (e.g., build pipeline is
+# in execution order, not alphabetical)
+section_order = {
+    'basic': ['list', 'info', 'find'],
+    'build': ['fetch', 'stage', 'patch', 'configure', 'build', 'restage',
+              'install', 'uninstall', 'clean']
+}
+
+# Properties that commands are required to set.
+required_command_properties = ['level', 'section', 'description']
+
+
+def set_working_dir():
+    """Change the working directory to getcwd, or spack prefix if no cwd."""
+    try:
+        spack.spack_working_dir = os.getcwd()
+    except OSError:
+        os.chdir(spack_prefix)
+        spack.spack_working_dir = spack_prefix
+
+
+def add_all_commands(parser):
+    """Add all spack subcommands to the parser."""
+    for cmd in spack.cmd.commands:
+        parser.add_command(cmd)
+
+
+def index_commands():
+    """create an index of commands by section for this help level"""
+    index = {}
+    for command in spack.cmd.commands:
+        cmd_module = spack.cmd.get_module(command)
+
+        # make sure command modules have required properties
+        for p in required_command_properties:
+            prop = getattr(cmd_module, p, None)
+            if not prop:
+                tty.die("Command doesn't define a property '%s': %s"
+                        % (p, command))
+
+        # add commands to lists for their level and higher levels
+        for level in reversed(levels):
+            level_sections = index.setdefault(level, {})
+            commands = level_sections.setdefault(cmd_module.section, [])
+            commands.append(command)
+            if level == cmd_module.level:
+                break
+
+    return index
+
+
+class SpackArgumentParser(ArgumentParser):
+    def format_help_sections(self, level):
+        """Format help on sections for a particular verbosity level.
+
+        Args:
+            level (str): 'short' or 'long' (more commands shown for long)
+        """
+        if level not in levels:
+            raise ValueError("level must be one of: %s" % levels)
+
+        # lazily add all commands to the parser when needed.
+        add_all_commands(self)
+
+        """Print help on subcommands in neatly formatted sections."""
+        formatter = self._get_formatter()
+
+        # Create a list of subcommand actions. Argparse internals are nasty!
+        # Note: you can only call _get_subactions() once.  Even nastier!
+        if not hasattr(self, 'actions'):
+            self.actions = self._subparsers._actions[-1]._get_subactions()
+
+        # make a set of commands not yet added.
+        remaining = set(spack.cmd.commands)
+
+        def add_group(group):
+            formatter.start_section(group.title)
+            formatter.add_text(group.description)
+            formatter.add_arguments(group._group_actions)
+            formatter.end_section()
+
+        def add_subcommand_group(title, commands):
+            """Add informational help group for a specific subcommand set."""
+            cmd_set = set(commands)
+
+            # make a dict of commands of interest
+            cmds = dict((action.metavar, action) for action in self.actions
+                        if action.metavar in cmd_set)
+
+            # add commands to a group in order, and add the group
+            group = _ArgumentGroup(self, title=title)
+            for name in commands:
+                group._add_action(cmds[name])
+                if name in remaining:
+                    remaining.remove(name)
+            add_group(group)
+
+        # select only the options for the particular level we're showing.
+        show_options = options_by_level[level]
+        if show_options != 'all':
+            opts = dict((opt.option_strings[0].strip('-'), opt)
+                        for opt in self._optionals._group_actions)
+
+            new_actions = [opts[letter] for letter in show_options]
+            self._optionals._group_actions = new_actions
+
+        options = ''.join(opt.option_strings[0].strip('-')
+                          for opt in self._optionals._group_actions)
+
+        index = index_commands()
+
+        # usage
+        formatter.add_text(
+            "usage: %s [-%s] <command> [...]" % (self.prog, options))
+
+        # description
+        formatter.add_text(self.description)
+
+        # start subcommands
+        formatter.add_text(intro_by_level[level])
+
+        # add argument groups based on metadata in commands
+        sections = index[level]
+        for section in sorted(sections):
+            if section == 'help':
+                continue   # Cover help in the epilog.
+
+            group_description = section_descriptions.get(section, section)
+
+            to_display = sections[section]
+            commands = []
+
+            # add commands whose order we care about first.
+            if section in section_order:
+                commands.extend(cmd for cmd in section_order[section]
+                                if cmd in to_display)
+
+            # add rest in alphabetical order.
+            commands.extend(cmd for cmd in sorted(sections[section])
+                            if cmd not in commands)
+
+            # add the group to the parser
+            add_subcommand_group(group_description, commands)
+
+        # optionals
+        add_group(self._optionals)
+
+        # epilog
+        formatter.add_text("""\
+{help}:
+  spack help -a          list all available commands
+  spack help <command>   help on a specific command
+  spack help --spec      help on the spec syntax
+  spack docs             open http://spack.rtfd.io/ in a browser"""
+.format(help=section_descriptions['help']))
+
+        # determine help from format above
+        return formatter.format_help()
+
+    def add_command(self, name):
+        """Add one subcommand to this parser."""
+        # lazily initialize any subparsers
+        if not hasattr(self, 'subparsers'):
+            # remove the dummy "command" argument.
+            self._remove_action(self._actions[-1])
+            self.subparsers = self.add_subparsers(metavar='COMMAND',
+                                                  dest="command")
+
+        # each command module implements a parser() function, to which we
+        # pass its subparser for setup.
+        module = spack.cmd.get_module(name)
+        cmd_name = name.replace('_', '-')
+        subparser = self.subparsers.add_parser(
+            cmd_name, help=module.description, description=module.description)
+        module.setup_parser(subparser)
+        return module
+
+    def format_help(self, level='short'):
+        if self.prog == 'spack':
+            # use format_help_sections for the main spack parser, but not
+            # for subparsers
+            return self.format_help_sections(level)
+        else:
+            # in subparsers, self.prog is, e.g., 'spack install'
+            return super(SpackArgumentParser, self).format_help()
+
+
+def make_argument_parser():
+    """Create an basic argument parser without any subcommands added."""
+    parser = SpackArgumentParser(
+        formatter_class=RawTextHelpFormatter, add_help=False,
+        description=(
+            "A flexible package manager that supports multiple versions,\n"
+            "configurations, platforms, and compilers."))
+
+    # stat names in groups of 7, for nice wrapping.
+    stat_lines = list(zip(*(iter(stat_names),) * 7))
+
+    parser.add_argument('-h', '--help', action='store_true',
+                        help="show this help message and exit")
+    parser.add_argument('-d', '--debug', action='store_true',
+                        help="write out debug logs during compile")
+    parser.add_argument('-D', '--pdb', action='store_true',
+                        help="run spack under the pdb debugger")
+    parser.add_argument('-k', '--insecure', action='store_true',
+                        help="do not check ssl certificates when downloading")
+    parser.add_argument('-m', '--mock', action='store_true',
+                        help="use mock packages instead of real ones")
+    parser.add_argument('-p', '--profile', action='store_true',
+                        help="profile execution using cProfile")
+    parser.add_argument('-P', '--sorted-profile', default=None, metavar="STAT",
+                        help="profile and sort by one or more of:\n[%s]" %
+                        ',\n '.join([', '.join(line) for line in stat_lines]))
+    parser.add_argument('--lines', default=20, action='store',
+                        help="lines of profile output; default 20; or 'all'")
+    parser.add_argument('-v', '--verbose', action='store_true',
+                        help="print additional output during builds")
+    parser.add_argument('-s', '--stacktrace', action='store_true',
+                        help="add stacktraces to all printed statements")
+    parser.add_argument('-V', '--version', action='store_true',
+                        help='show version number and exit')
+    return parser
+
+
+def setup_main_options(args):
+    """Configure spack globals based on the basic options."""
+    # Set up environment based on args.
+    tty.set_verbose(args.verbose)
+    tty.set_debug(args.debug)
+    tty.set_stacktrace(args.stacktrace)
+    spack.debug = args.debug
+
+    if spack.debug:
+        import spack.util.debug as debug
+        debug.register_interrupt_handler()
+
+    if args.mock:
+        from spack.repository import RepoPath
+        spack.repo.swap(RepoPath(spack.mock_packages_path))
+
+    # If the user asked for it, don't check ssl certs.
+    if args.insecure:
+        tty.warn("You asked for --insecure. Will NOT check SSL certificates.")
+        spack.insecure = True
+
+
+def allows_unknown_args(command):
+    """This is a basic argument injection test.
+
+    Commands may add an optional argument called "unknown args" to
+    indicate they can handle unknonwn args, and we'll pass the unknown
+    args in.
+    """
+    info = dict(inspect.getmembers(command))
+    varnames = info['__code__'].co_varnames
+    argcount = info['__code__'].co_argcount
+    return (argcount == 3 and varnames[2] == 'unknown_args')
+
+
+def _main(command, parser, args, unknown_args):
+    # many operations will fail without a working directory.
+    set_working_dir()
+
+    # only setup main options in here, after the real parse (we'll get it
+    # wrong if we do it after the initial, partial parse)
+    setup_main_options(args)
+    spack.hooks.pre_run()
+
+    # Now actually execute the command
+    try:
+        if allows_unknown_args(command):
+            return_val = command(parser, args, unknown_args)
+        else:
+            if unknown_args:
+                tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
+            return_val = command(parser, args)
+    except SpackError as e:
+        e.die()  # gracefully die on any SpackErrors
+    except Exception as e:
+        if spack.debug:
+            raise
+        tty.die(str(e))
+    except KeyboardInterrupt:
+        sys.stderr.write('\n')
+        tty.die("Keyboard interrupt.")
+
+    # Allow commands to return and error code if they want
+    return 0 if return_val is None else return_val
+
+
+def _profile_wrapper(command, parser, args, unknown_args):
+    import cProfile
+
+    try:
+        nlines = int(args.lines)
+    except ValueError:
+        if args.lines != 'all':
+            tty.die('Invalid number for --lines: %s' % args.lines)
+        nlines = -1
+
+    # allow comma-separated list of fields
+    sortby = ['time']
+    if args.sorted_profile:
+        sortby = args.sorted_profile.split(',')
+        for stat in sortby:
+            if stat not in stat_names:
+                tty.die("Invalid sort field: %s" % stat)
+
+    try:
+        # make a profiler and run the code.
+        pr = cProfile.Profile()
+        pr.enable()
+        return _main(command, parser, args, unknown_args)
+
+    finally:
+        pr.disable()
+
+        # print out profile stats.
+        stats = pstats.Stats(pr)
+        stats.sort_stats(*sortby)
+        stats.print_stats(nlines)
+
+
+def main(argv=None):
+    """This is the entry point for the Spack command.
+
+    Args:
+        argv (list of str or None): command line arguments, NOT including
+            the executable name. If None, parses from sys.argv.
+    """
+    # Create a parser with a simple positional argument first.  We'll
+    # lazily load the subcommand(s) we need later. This allows us to
+    # avoid loading all the modules from spack.cmd when we don't need
+    # them, which reduces startup latency.
+    parser = make_argument_parser()
+    parser.add_argument(
+        'command', metavar='COMMAND', nargs='?', action='store')
+    args, unknown = parser.parse_known_args(argv)
+
+    # Just print help and exit if run with no arguments at all
+    no_args = (len(sys.argv) == 1) if argv is None else (len(argv) == 0)
+    if no_args:
+        parser.print_help()
+        return 1
+
+    # -h and -V are special as they do not require a command, but all the
+    # other options do nothing without a command.
+    if not args.command:
+        if args.version:
+            print(spack.spack_version)
+            return 0
+        else:
+            parser.print_help()
+            return 0 if args.help else 1
+
+    # Try to load the particular command the caller asked for.  If there
+    # is no module for it, just die.
+    command_name = args.command.replace('-', '_')
+    try:
+        parser.add_command(command_name)
+    except ImportError:
+        if spack.debug:
+            raise
+        tty.die("Unknown command: %s" % args.command)
+
+    # Re-parse with the proper sub-parser added.
+    args, unknown = parser.parse_known_args()
+
+    # we now know whether options go with spack or the command
+    if args.version:
+        print(spack.spack_version)
+        return 0
+    elif args.help:
+        parser.print_help()
+        return 0
+
+    # now we can actually execute the command.
+    command = spack.cmd.get_command(command_name)
+    try:
+        if args.profile or args.sorted_profile:
+            _profile_wrapper(command, parser, args, unknown)
+        elif args.pdb:
+            import pdb
+            pdb.runctx('_main(command, parser, args, unknown)',
+                       globals(), locals())
+            return 0
+        else:
+            return _main(command, parser, args, unknown)
+
+    except SystemExit as e:
+        return e.code
diff --git a/share/spack/qa/run-unit-tests b/share/spack/qa/run-unit-tests
index fe2ec6f54ac5a867828eec43f9fd9fd445e2c3f3..87203ba915d42c962e877b72adec58871a3d44d3 100755
--- a/share/spack/qa/run-unit-tests
+++ b/share/spack/qa/run-unit-tests
@@ -20,6 +20,10 @@ cd "$SPACK_ROOT"
 # Print compiler information
 spack config get compilers
 
+# Run spack help to cover command import
+${coverage_run} bin/spack -h
+${coverage_run} bin/spack help -a
+
 # Profile and print top 20 lines for a simple call to spack spec
 ${coverage_run} bin/spack -p --lines 20 spec mpileaks