ref: Convert all cmdline users to cmdline2.

This also includes changes in cmdline2 to allow commands with
both Children and Runner set, as long as the runner doesn't take
any args.  This makes sense since the runner handles the no-arg
case, while the children handle the with-args case.  This was
already being used by "v23 buildcop" and "deviced", and it seemed
better to allow this behavior.

Also fixed a bug in the cmdline2 package where the "at least one
of Children and Runner" check wasn't returning an error, if the
bad Command wasn't the root.  Added tests for this case.

Finally, but also important, changed how global flags are
handled.  Previously I was hoping we could simply change
"v.io/x/ref/lib/flags" to check flag.Parsed and avoid
double-parsing the flags.  But the previous strategy always
merged flags into a new FlagSet, and then overwrote
flag.CommandLine with that value.  Our usage of ref/lib/flags
stashes the flag.CommandLine pointer away in an init function,
which breaks that strategy.

In addition, ref/lib/flags has some weird logic to set certain
flags to their "default values".  Presumably this was a hack to
deal with double-parsing the flags for flags that are specified
multiple times on the command line, and append their values.
This makes it questionable whether we can really depend on not
double-parsing flags.

I'll look into changing our flags so that they don't depend on
the multi-specification-append behavior, since that's just weird.
But I'll do that separately.

The actual conversions of all programs is mechanical.

MultiPart: 3/3

Change-Id: I3292256e5c8e4a6acf99c9b92c98b009a7fbfcf4
diff --git a/cmd/gclogs/doc.go b/cmd/gclogs/doc.go
index 95cbf6c..c4aabd1 100644
--- a/cmd/gclogs/doc.go
+++ b/cmd/gclogs/doc.go
@@ -33,50 +33,7 @@
    If true, each deleted file is shown on stdout.
 
 The global flags are:
- -alsologtostderr=true
-   log to standard error as well as files
- -log_backtrace_at=:0
-   when logging hits line file:N, emit a stack trace
- -log_dir=
-   if non-empty, write log files to this directory
- -logtostderr=false
-   log to standard error instead of files
- -max_stack_buf_size=4292608
-   max size in bytes of the buffer to use for logging stack traces
- -stderrthreshold=2
-   logs at or above this threshold go to stderr
- -v=0
-   log level for V logs
- -v23.credentials=
-   directory to use for storing security credentials
- -v23.i18n-catalogue=
-   18n catalogue files to load, comma separated
  -v23.metadata=<just specify -v23.metadata to activate>
    Displays metadata for the program and exits.
- -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
-   local namespace root; can be repeated to provided multiple roots
- -v23.permissions.file=map[]
-   specify a perms file as <name>:<permsfile>
- -v23.permissions.literal=
-   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
-   Overrides all --v23.permissions.file flags.
- -v23.proxy=
-   object name of proxy service to use to export services across network
-   boundaries
- -v23.tcp.address=
-   address to listen on
- -v23.tcp.protocol=wsh
-   protocol to listen with
- -v23.vtrace.cache-size=1024
-   The number of vtrace traces to store in memory.
- -v23.vtrace.collect-regexp=
-   Spans and annotations that match this regular expression will trigger trace
-   collection.
- -v23.vtrace.dump-on-shutdown=true
-   If true, dump all stored traces on runtime shutdown.
- -v23.vtrace.sample-rate=0
-   Rate (from 0.0 to 1.0) to sample vtrace traces.
- -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
 */
 package main
diff --git a/cmd/gclogs/gclogs.go b/cmd/gclogs/gclogs.go
index 76cb946..4ba69d5 100644
--- a/cmd/gclogs/gclogs.go
+++ b/cmd/gclogs/gclogs.go
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
 package main
 
 import (
@@ -13,9 +16,7 @@
 	"regexp"
 	"time"
 
-	"v.io/v23"
-	"v.io/x/lib/cmdline"
-	_ "v.io/x/ref/profiles/static"
+	"v.io/x/lib/cmdline2"
 )
 
 var (
@@ -24,10 +25,10 @@
 	flagVerbose  bool
 	flagDryrun   bool
 
-	cmdGCLogs = &cmdline.Command{
-		Run:   garbageCollectLogs,
-		Name:  "gclogs",
-		Short: "safely deletes old log files",
+	cmdGCLogs = &cmdline2.Command{
+		Runner: cmdline2.RunnerFunc(garbageCollectLogs),
+		Name:   "gclogs",
+		Short:  "safely deletes old log files",
 		Long: `
 Command gclogs safely deletes old log files.
 
@@ -44,19 +45,19 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept()
 	cmdGCLogs.Flags.DurationVar(&flagCutoff, "cutoff", 24*time.Hour, "The age cut-off for a log file to be considered for garbage collection.")
 	cmdGCLogs.Flags.StringVar(&flagProgname, "program", ".*", `A regular expression to apply to the program part of the log file name, e.g ".*test".`)
 	cmdGCLogs.Flags.BoolVar(&flagVerbose, "verbose", false, "If true, each deleted file is shown on stdout.")
 	cmdGCLogs.Flags.BoolVar(&flagDryrun, "n", false, "If true, log files that would be deleted are shown on stdout, but not actually deleted.")
 }
 
-func garbageCollectLogs(cmd *cmdline.Command, args []string) error {
-	_, shutdown := v23.Init()
-	defer shutdown()
+func main() {
+	cmdline2.Main(cmdGCLogs)
+}
 
+func garbageCollectLogs(env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		cmd.UsageErrorf("gclogs requires at least one argument")
+		env.UsageErrorf("gclogs requires at least one argument")
 	}
 	timeCutoff := time.Now().Add(-flagCutoff)
 	currentUser, err := user.Current()
@@ -69,15 +70,15 @@
 	}
 	var lastErr error
 	for _, logdir := range args {
-		if err := processDirectory(cmd, logdir, timeCutoff, programRE, currentUser.Username); err != nil {
+		if err := processDirectory(env, logdir, timeCutoff, programRE, currentUser.Username); err != nil {
 			lastErr = err
 		}
 	}
 	return lastErr
 }
 
-func processDirectory(cmd *cmdline.Command, logdir string, timeCutoff time.Time, programRE *regexp.Regexp, username string) error {
-	fmt.Fprintf(cmd.Stdout(), "Processing: %q\n", logdir)
+func processDirectory(env *cmdline2.Env, logdir string, timeCutoff time.Time, programRE *regexp.Regexp, username string) error {
+	fmt.Fprintf(env.Stdout, "Processing: %q\n", logdir)
 
 	f, err := os.Open(logdir)
 	if err != nil {
@@ -101,26 +102,26 @@
 			fullname := filepath.Join(logdir, file.Name())
 			if file.IsDir() {
 				if flagVerbose {
-					fmt.Fprintf(cmd.Stdout(), "Skipped directory: %q\n", fullname)
+					fmt.Fprintf(env.Stdout, "Skipped directory: %q\n", fullname)
 				}
 				continue
 			}
 			lf, err := parseFileInfo(logdir, file)
 			if err != nil {
 				if flagVerbose {
-					fmt.Fprintf(cmd.Stdout(), "Not a log file: %q\n", fullname)
+					fmt.Fprintf(env.Stdout, "Not a log file: %q\n", fullname)
 				}
 				continue
 			}
 			if lf.user != username {
 				if flagVerbose {
-					fmt.Fprintf(cmd.Stdout(), "Skipped log file created by other user: %q\n", fullname)
+					fmt.Fprintf(env.Stdout, "Skipped log file created by other user: %q\n", fullname)
 				}
 				continue
 			}
 			if !programRE.MatchString(lf.program) {
 				if flagVerbose {
-					fmt.Fprintf(cmd.Stdout(), "Skipped log file doesn't match %q: %q\n", flagProgname, fullname)
+					fmt.Fprintf(env.Stdout, "Skipped log file doesn't match %q: %q\n", flagProgname, fullname)
 				}
 				continue
 			}
@@ -130,11 +131,11 @@
 			}
 			if file.ModTime().Before(timeCutoff) {
 				if flagDryrun {
-					fmt.Fprintf(cmd.Stdout(), "Would delete %q\n", fullname)
+					fmt.Fprintf(env.Stdout, "Would delete %q\n", fullname)
 					continue
 				}
 				if flagVerbose {
-					fmt.Fprintf(cmd.Stdout(), "Deleting %q\n", fullname)
+					fmt.Fprintf(env.Stdout, "Deleting %q\n", fullname)
 				}
 				if err := os.Remove(fullname); err != nil {
 					lastErr = err
@@ -148,11 +149,11 @@
 	for _, sl := range symlinks {
 		if _, err := os.Stat(sl); err != nil && os.IsNotExist(err) {
 			if flagDryrun {
-				fmt.Fprintf(cmd.Stdout(), "Would delete symlink %q\n", sl)
+				fmt.Fprintf(env.Stdout, "Would delete symlink %q\n", sl)
 				continue
 			}
 			if flagVerbose {
-				fmt.Fprintf(cmd.Stdout(), "Deleting symlink %q\n", sl)
+				fmt.Fprintf(env.Stdout, "Deleting symlink %q\n", sl)
 			}
 			if err := os.Remove(sl); err != nil {
 				lastErr = err
@@ -162,6 +163,6 @@
 		}
 
 	}
-	fmt.Fprintf(cmd.Stdout(), "Number of files deleted: %d\n", deleted)
+	fmt.Fprintf(env.Stdout, "Number of files deleted: %d\n", deleted)
 	return lastErr
 }
diff --git a/cmd/gclogs/gclogs_test.go b/cmd/gclogs/gclogs_test.go
index 480b9d6..da7b0ea 100644
--- a/cmd/gclogs/gclogs_test.go
+++ b/cmd/gclogs/gclogs_test.go
@@ -15,6 +15,8 @@
 	"strings"
 	"testing"
 	"time"
+
+	"v.io/x/lib/cmdline2"
 )
 
 func setup(t *testing.T, workdir, username string) (tmpdir string) {
@@ -70,10 +72,6 @@
 		t.Fatalf("user.Current failed: %v", err)
 	}
 
-	cmd := cmdGCLogs
-	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
-
 	testcases := []struct {
 		cutoff   time.Duration
 		verbose  bool
@@ -162,7 +160,9 @@
 		cutoff := fmt.Sprintf("--cutoff=%s", tc.cutoff)
 		verbose := fmt.Sprintf("--verbose=%v", tc.verbose)
 		dryrun := fmt.Sprintf("--n=%v", tc.dryrun)
-		if err := cmd.Execute([]string{cutoff, verbose, dryrun, testdir}); err != nil {
+		var stdout, stderr bytes.Buffer
+		env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := cmdline2.ParseAndRun(cmdGCLogs, env, []string{cutoff, verbose, dryrun, testdir}); err != nil {
 			t.Fatalf("%v: %v", stderr.String(), err)
 		}
 		gotsl := strings.Split(stdout.String(), "\n")
@@ -176,6 +176,5 @@
 		if got != expected {
 			t.Errorf("Unexpected result for (%v, %v): got %q, expected %q", tc.cutoff, tc.verbose, got, expected)
 		}
-		stdout.Reset()
 	}
 }
diff --git a/cmd/gclogs/main.go b/cmd/gclogs/main.go
deleted file mode 100644
index 9c62ff3..0000000
--- a/cmd/gclogs/main.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// The following enables go generate to generate the doc.go file.
-//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
-
-package main
-
-import (
-	"os"
-)
-
-func main() {
-	os.Exit(cmdGCLogs.Main())
-}
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index ae4f429..4910fb7 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -90,8 +90,10 @@
 Usage:
    principal create [flags] <directory> <blessing>
 
-	<directory> is the directory to which the new principal will be persisted.
-	<blessing> is the self-blessed blessing that the principal will be setup to use by default.
+<directory> is the directory to which the new principal will be persisted.
+
+<blessing> is the self-blessed blessing that the principal will be setup to use
+by default.
 
 The principal create flags are:
  -overwrite=false
@@ -114,8 +116,9 @@
 Usage:
    principal fork [flags] <directory> <extension>
 
-	<directory> is the directory to which the forked principal will be persisted.
-	<extension> is the extension under which the forked principal is blessed.
+<directory> is the directory to which the forked principal will be persisted.
+
+<extension> is the extension under which the forked principal is blessed.
 
 The principal fork flags are:
  -caveat=[]
@@ -515,11 +518,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    principal help [flags] [command/topic ...]
 
@@ -532,5 +530,9 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 */
 package main
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index 834a0ed..714ec5b 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -27,9 +27,10 @@
 	"v.io/v23/rpc"
 	"v.io/v23/security"
 	"v.io/v23/vom"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/ref/envvar"
 	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles/static"
 )
 
@@ -80,17 +81,14 @@
 
 	errNoCaveats = fmt.Errorf("no caveats provided: it is generally dangerous to bless another principal without any caveats as that gives them almost unrestricted access to the blesser's credentials. If you really want to do this, set --require-caveats=false")
 
-	cmdDump = &cmdline.Command{
+	cmdDump = &cmdline2.Command{
 		Name:  "dump",
 		Short: "Dump out information about the principal",
 		Long: `
 Prints out information about the principal specified by the environment
 that this tool is running in.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			p := v23.GetPrincipal(ctx)
 			if flagDumpShort {
 				fmt.Printf("%v\n", p.BlessingStore().Default())
@@ -102,10 +100,10 @@
 			fmt.Println("---------------- BlessingRoots ----------------")
 			fmt.Printf("%v", p.Roots().DebugString())
 			return nil
-		},
+		}),
 	}
 
-	cmdDumpBlessings = &cmdline.Command{
+	cmdDumpBlessings = &cmdline2.Command{
 		Name:  "dumpblessings",
 		Short: "Dump out information about the provided blessings",
 		Long: `
@@ -117,7 +115,7 @@
 <file> is the path to a file containing blessings typically obtained from
 this tool. - is used for STDIN.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: cmdline2.RunnerFunc(func(env *cmdline2.Env, args []string) error {
 			if len(args) != 1 {
 				return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
 			}
@@ -149,10 +147,10 @@
 				}
 			}
 			return nil
-		},
+		}),
 	}
 
-	cmdBlessSelf = &cmdline.Command{
+	cmdBlessSelf = &cmdline2.Command{
 		Name:  "blessself",
 		Short: "Generate a self-signed blessing",
 		Long: `
@@ -167,7 +165,7 @@
 specified, a name will be generated based on the hostname of the
 machine and the name of the user running this command.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			var name string
 			switch len(args) {
 			case 0:
@@ -177,13 +175,10 @@
 			default:
 				return fmt.Errorf("requires at most one argument, provided %d", len(args))
 			}
-
 			caveats, err := caveatsFromFlags(flagBlessSelfFor, &flagBlessSelfCaveats)
 			if err != nil {
 				return err
 			}
-			ctx, shutdown := v23.Init()
-			defer shutdown()
 			principal := v23.GetPrincipal(ctx)
 			blessing, err := principal.BlessSelf(name, caveats...)
 			if err != nil {
@@ -191,10 +186,10 @@
 			}
 
 			return dumpBlessings(blessing)
-		},
+		}),
 	}
 
-	cmdBless = &cmdline.Command{
+	cmdBless = &cmdline2.Command{
 		Name:  "bless",
 		Short: "Bless another principal",
 		Long: `
@@ -238,16 +233,12 @@
 blessing.
 
 	`,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(flagRemoteArgFile) > 0 && len(args) != 1 {
 				return fmt.Errorf("when --remote-arg-file is provided, only <extension> is expected, provided %d", len(args))
 			} else if len(flagRemoteArgFile) == 0 && len(args) != 2 {
 				return fmt.Errorf("require exactly two arguments when --remote-arg-file is not provided, provided %d", len(args))
 			}
-
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			p := v23.GetPrincipal(ctx)
 
 			var (
@@ -290,10 +281,10 @@
 				return err
 			}
 			return dumpBlessings(blessings)
-		},
+		}),
 	}
 
-	cmdGetPublicKey = &cmdline.Command{
+	cmdGetPublicKey = &cmdline2.Command{
 		Name:  "publickey",
 		Short: "Prints the public key of the principal.",
 		Long: `
@@ -308,9 +299,7 @@
 for humans to read and is used in output of other commands in this program, but
 is not suitable as an argument to the 'recognize' command.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			key := v23.GetPrincipal(ctx).PublicKey()
 			if flagGetPublicKeyPretty {
 				fmt.Println(key)
@@ -322,10 +311,10 @@
 			}
 			fmt.Println(base64.URLEncoding.EncodeToString(der))
 			return nil
-		},
+		}),
 	}
 
-	cmdGetTrustedRoots = &cmdline.Command{
+	cmdGetTrustedRoots = &cmdline2.Command{
 		Name:  "recognizedroots",
 		Short: "Return recognized blessings, and their associated public key.",
 		Long: `
@@ -334,15 +323,13 @@
 appear on this list. If the principal is operating as a server, clients must
 present blessings derived from this list.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			fmt.Printf(v23.GetPrincipal(ctx).Roots().DebugString())
 			return nil
-		},
+		}),
 	}
 
-	cmdGetPeerMap = &cmdline.Command{
+	cmdGetPeerMap = &cmdline2.Command{
 		Name:  "peermap",
 		Short: "Shows the map from peer pattern to which blessing name to present.",
 		Long: `
@@ -351,15 +338,13 @@
 If the principal operates as a client, it presents the map value associated with
 the peer it contacts.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			fmt.Printf(v23.GetPrincipal(ctx).BlessingStore().DebugString())
 			return nil
-		},
+		}),
 	}
 
-	cmdGetForPeer = &cmdline.Command{
+	cmdGetForPeer = &cmdline2.Command{
 		Name:  "forpeer",
 		Short: "Return blessings marked for the provided peer",
 		Long: `
@@ -380,14 +365,12 @@
 store.forpeer returns the blessings that are marked for all peers (i.e.,
 blessings set on the store with the "..." pattern).
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().ForPeer(args...))
-		},
+		}),
 	}
 
-	cmdGetDefault = &cmdline.Command{
+	cmdGetDefault = &cmdline2.Command{
 		Name:  "default",
 		Short: "Return blessings marked as default",
 		Long: `
@@ -399,14 +382,12 @@
 Providing --caveats <chain_name> will print the caveats on the certificate chain
 with chain_name.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			ctx, shutdown := v23.Init()
-			defer shutdown()
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			return printBlessingsInfo(v23.GetPrincipal(ctx).BlessingStore().Default())
-		},
+		}),
 	}
 
-	cmdSetForPeer = &cmdline.Command{
+	cmdSetForPeer = &cmdline2.Command{
 		Name:  "forpeer",
 		Short: "Set provided blessings for peer",
 		Long: `
@@ -431,7 +412,7 @@
 <pattern> is the BlessingPattern used to identify peers with whom this
 blessing can be shared with.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(args) != 2 {
 				return fmt.Errorf("requires exactly two arguments <file>, <pattern>, provided %d", len(args))
 			}
@@ -441,9 +422,6 @@
 			}
 			pattern := security.BlessingPattern(args[1])
 
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			p := v23.GetPrincipal(ctx)
 			if _, err := p.BlessingStore().Set(blessings, pattern); err != nil {
 				return fmt.Errorf("failed to set blessings %v for peers %v: %v", blessings, pattern, err)
@@ -454,10 +432,10 @@
 				}
 			}
 			return nil
-		},
+		}),
 	}
 
-	cmdRecognize = &cmdline.Command{
+	cmdRecognize = &cmdline2.Command{
 		Name:  "recognize",
 		Short: "Add to the set of identity providers recognized by this principal",
 		Long: `
@@ -485,13 +463,10 @@
 
 <blessing pattern> is the blessing pattern for which <key> should be recognized.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(args) != 1 && len(args) != 2 {
 				return fmt.Errorf("requires either one argument <file>, or two arguments <key> <blessing pattern>, provided %d", len(args))
 			}
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			p := v23.GetPrincipal(ctx)
 			if len(args) == 1 {
 				blessings, err := decodeBlessings(args[0])
@@ -513,10 +488,10 @@
 				return fmt.Errorf("invalid DER encoding of public key: %v", err)
 			}
 			return p.Roots().Add(key, security.BlessingPattern(args[0]))
-		},
+		}),
 	}
 
-	cmdSetDefault = &cmdline.Command{
+	cmdSetDefault = &cmdline2.Command{
 		Name:  "default",
 		Short: "Set provided blessings as default",
 		Long: `
@@ -531,7 +506,7 @@
 <file> is the path to a file containing a blessing typically obtained from
 this tool. - is used for STDIN.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(args) != 1 {
 				return fmt.Errorf("requires exactly one argument, <file>, provided %d", len(args))
 			}
@@ -540,9 +515,6 @@
 				return fmt.Errorf("failed to decode provided blessings: %v", err)
 			}
 
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			p := v23.GetPrincipal(ctx)
 			if err := p.BlessingStore().SetDefault(blessings); err != nil {
 				return fmt.Errorf("failed to set blessings %v as default: %v", blessings, err)
@@ -553,10 +525,10 @@
 				}
 			}
 			return nil
-		},
+		}),
 	}
 
-	cmdCreate = &cmdline.Command{
+	cmdCreate = &cmdline2.Command{
 		Name:  "create",
 		Short: "Create a new principal and persist it into a directory",
 		Long: `
@@ -570,10 +542,11 @@
 `,
 		ArgsName: "<directory> <blessing>",
 		ArgsLong: `
-	<directory> is the directory to which the new principal will be persisted.
-	<blessing> is the self-blessed blessing that the principal will be setup to use by default.
+<directory> is the directory to which the new principal will be persisted.
+
+<blessing> is the self-blessed blessing that the principal will be setup to use by default.
 	`,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: cmdline2.RunnerFunc(func(env *cmdline2.Env, args []string) error {
 			if len(args) != 2 {
 				return fmt.Errorf("requires exactly two arguments: <directory> and <blessing>, provided %d", len(args))
 			}
@@ -595,10 +568,10 @@
 				return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
 			}
 			return nil
-		},
+		}),
 	}
 
-	cmdFork = &cmdline.Command{
+	cmdFork = &cmdline2.Command{
 		Name:  "fork",
 		Short: "Fork a new principal from the principal that this tool is running as and persist it into a directory",
 		Long: `
@@ -616,18 +589,15 @@
 `,
 		ArgsName: "<directory> <extension>",
 		ArgsLong: `
-	<directory> is the directory to which the forked principal will be persisted.
-	<extension> is the extension under which the forked principal is blessed.
+<directory> is the directory to which the forked principal will be persisted.
+
+<extension> is the extension under which the forked principal is blessed.
 	`,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(args) != 2 {
 				return fmt.Errorf("requires exactly two arguments: <directory> and <extension>, provided %d", len(args))
 			}
 			dir, extension := args[0], args[1]
-
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			caveats, err := caveatsFromFlags(flagForkFor, &flagForkCaveats)
 			if err != nil {
 				return err
@@ -667,10 +637,10 @@
 				return fmt.Errorf("could not set blessings %v as default: %v", blessings, err)
 			}
 			return nil
-		},
+		}),
 	}
 
-	cmdSeekBlessings = &cmdline.Command{
+	cmdSeekBlessings = &cmdline2.Command{
 		Name:  "seekblessings",
 		Short: "Seek blessings from a web-based Vanadium blessing service",
 		Long: `
@@ -685,12 +655,7 @@
 set to true, and are also set for sharing with all peers, unless a more
 specific peer pattern is provided using the --for-peer flag.
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
-			// Initialize the runtime first so that any local errors are reported
-			// before the HTTP roundtrips for obtaining the macaroon begin.
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			p := v23.GetPrincipal(ctx)
 
 			blessedChan := make(chan string)
@@ -723,12 +688,12 @@
 					return fmt.Errorf("AddToRoots failed: %v", err)
 				}
 			}
-			fmt.Fprintf(cmd.Stdout(), "Received blessings: %v\n", blessings)
+			fmt.Fprintf(env.Stdout, "Received blessings: %v\n", blessings)
 			return nil
-		},
+		}),
 	}
 
-	cmdRecvBlessings = &cmdline.Command{
+	cmdRecvBlessings = &cmdline2.Command{
 		Name:  "recvblessings",
 		Short: "Receive blessings sent by another principal and use them as the default",
 		Long: `
@@ -768,14 +733,10 @@
 		principal bless --remote-arg-file FILE EXTENSION
 
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if len(args) != 0 {
 				return fmt.Errorf("command accepts no arguments")
 			}
-
-			ctx, shutdown := v23.Init()
-			defer shutdown()
-
 			server, err := v23.NewServer(ctx)
 			if err != nil {
 				return fmt.Errorf("failed to create server to listen for blessings: %v", err)
@@ -817,7 +778,7 @@
 			fmt.Println()
 			fmt.Println("...waiting for sender..")
 			return <-service.notify
-		},
+		}),
 	}
 )
 
@@ -892,7 +853,7 @@
 }
 
 func main() {
-	cmdline.HideGlobalFlagsExcept()
+	cmdline2.HideGlobalFlagsExcept()
 	cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
 	cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
 
@@ -942,7 +903,7 @@
 
 	cmdGetPublicKey.Flags.BoolVar(&flagGetPublicKeyPretty, "pretty", false, "If true, print the key out in a more human-readable but lossy representation.")
 
-	cmdSet := &cmdline.Command{
+	cmdSet := &cmdline2.Command{
 		Name:  "set",
 		Short: "Mutate the principal's blessings.",
 		Long: `
@@ -951,10 +912,10 @@
 All input blessings are expected to be serialized using base64-VOM-encoding.
 See 'principal get'.
 `,
-		Children: []*cmdline.Command{cmdSetDefault, cmdSetForPeer},
+		Children: []*cmdline2.Command{cmdSetDefault, cmdSetForPeer},
 	}
 
-	cmdGet := &cmdline.Command{
+	cmdGet := &cmdline2.Command{
 		Name:  "get",
 		Short: "Read the principal's blessings.",
 		Long: `
@@ -962,10 +923,10 @@
 
 All blessings are printed to stdout using base64-VOM-encoding.
 `,
-		Children: []*cmdline.Command{cmdGetDefault, cmdGetForPeer, cmdGetPublicKey, cmdGetTrustedRoots, cmdGetPeerMap},
+		Children: []*cmdline2.Command{cmdGetDefault, cmdGetForPeer, cmdGetPublicKey, cmdGetTrustedRoots, cmdGetPeerMap},
 	}
 
-	root := &cmdline.Command{
+	root := &cmdline2.Command{
 		Name:  "principal",
 		Short: "creates and manages Vanadium principals and blessings",
 		Long: `
@@ -973,9 +934,9 @@
 
 All objects are printed using base64-VOM-encoding.
 `,
-		Children: []*cmdline.Command{cmdCreate, cmdFork, cmdSeekBlessings, cmdRecvBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdSet, cmdGet, cmdRecognize},
+		Children: []*cmdline2.Command{cmdCreate, cmdFork, cmdSeekBlessings, cmdRecvBlessings, cmdDump, cmdDumpBlessings, cmdBlessSelf, cmdBless, cmdSet, cmdGet, cmdRecognize},
 	}
-	os.Exit(root.Main())
+	cmdline2.Main(root)
 }
 
 func decodeBlessings(fname string) (security.Blessings, error) {
diff --git a/cmd/uniqueid/doc.go b/cmd/uniqueid/doc.go
index ce5859d..08b1405 100644
--- a/cmd/uniqueid/doc.go
+++ b/cmd/uniqueid/doc.go
@@ -18,51 +18,8 @@
    help        Display help for commands or topics
 
 The global flags are:
- -alsologtostderr=true
-   log to standard error as well as files
- -log_backtrace_at=:0
-   when logging hits line file:N, emit a stack trace
- -log_dir=
-   if non-empty, write log files to this directory
- -logtostderr=false
-   log to standard error instead of files
- -max_stack_buf_size=4292608
-   max size in bytes of the buffer to use for logging stack traces
- -stderrthreshold=2
-   logs at or above this threshold go to stderr
- -v=0
-   log level for V logs
- -v23.credentials=
-   directory to use for storing security credentials
- -v23.i18n-catalogue=
-   18n catalogue files to load, comma separated
  -v23.metadata=<just specify -v23.metadata to activate>
    Displays metadata for the program and exits.
- -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
-   local namespace root; can be repeated to provided multiple roots
- -v23.permissions.file=map[]
-   specify a perms file as <name>:<permsfile>
- -v23.permissions.literal=
-   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
-   Overrides all --v23.permissions.file flags.
- -v23.proxy=
-   object name of proxy service to use to export services across network
-   boundaries
- -v23.tcp.address=
-   address to listen on
- -v23.tcp.protocol=wsh
-   protocol to listen with
- -v23.vtrace.cache-size=1024
-   The number of vtrace traces to store in memory.
- -v23.vtrace.collect-regexp=
-   Spans and annotations that match this regular expression will trigger trace
-   collection.
- -v23.vtrace.dump-on-shutdown=true
-   If true, dump all stored traces on runtime shutdown.
- -v23.vtrace.sample-rate=0
-   Rate (from 0.0 to 1.0) to sample vtrace traces.
- -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
 
 Uniqueid generate
 
@@ -89,11 +46,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    uniqueid help [flags] [command/topic ...]
 
@@ -106,5 +58,9 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 */
 package main
diff --git a/cmd/uniqueid/main.go b/cmd/uniqueid/main.go
index 9cd2737..3d27018 100644
--- a/cmd/uniqueid/main.go
+++ b/cmd/uniqueid/main.go
@@ -11,43 +11,31 @@
 	"bytes"
 	"fmt"
 	"io/ioutil"
-	"os"
 	"regexp"
 
-	"v.io/v23"
 	"v.io/v23/uniqueid"
-	"v.io/x/lib/cmdline"
-	_ "v.io/x/ref/profiles/static"
+	"v.io/x/lib/cmdline2"
 )
 
 func main() {
-	cmdline.HideGlobalFlagsExcept()
-	os.Exit(cmdUniqueId.Main())
+	cmdline2.Main(cmdUniqueId)
 }
 
-func runHelper(run cmdline.Runner) cmdline.Runner {
-	return func(cmd *cmdline.Command, args []string) error {
-		_, shutdown := v23.Init()
-		defer shutdown()
-		return run(cmd, args)
-	}
-}
-
-var cmdUniqueId = &cmdline.Command{
+var cmdUniqueId = &cmdline2.Command{
 	Name:  "uniqueid",
 	Short: "generates unique identifiers",
 	Long: `
 Command uniqueid generates unique identifiers.
 It also has an option of automatically substituting unique ids with placeholders in files.
 `,
-	Children: []*cmdline.Command{cmdGenerate, cmdInject},
-	Topics:   []cmdline.Topic{},
+	Children: []*cmdline2.Command{cmdGenerate, cmdInject},
+	Topics:   []cmdline2.Topic{},
 }
 
-var cmdGenerate = &cmdline.Command{
-	Run:   runHelper(runGenerate),
-	Name:  "generate",
-	Short: "Generates UniqueIds",
+var cmdGenerate = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runGenerate),
+	Name:   "generate",
+	Short:  "Generates UniqueIds",
 	Long: `
 Generates unique ids and outputs them to standard out.
 `,
@@ -55,10 +43,10 @@
 	ArgsLong: "",
 }
 
-var cmdInject = &cmdline.Command{
-	Run:   runHelper(runInject),
-	Name:  "inject",
-	Short: "Injects UniqueIds into existing files",
+var cmdInject = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runInject),
+	Name:   "inject",
+	Short:  "Injects UniqueIds into existing files",
 	Long: `
 Injects UniqueIds into existing files.
 Strings of the form "$UNIQUEID$" will be replaced with generated ids.
@@ -68,9 +56,9 @@
 }
 
 // runGenerate implements the generate command which outputs generated ids to stdout.
-func runGenerate(command *cmdline.Command, args []string) error {
+func runGenerate(env *cmdline2.Env, args []string) error {
 	if len(args) > 0 {
-		return command.UsageErrorf("expected 0 args, got %d", len(args))
+		return env.UsageErrorf("expected 0 args, got %d", len(args))
 	}
 	id, err := uniqueid.Random()
 	if err != nil {
@@ -81,9 +69,9 @@
 }
 
 // runInject implements the inject command which replaces $UNIQUEID$ strings with generated ids.
-func runInject(command *cmdline.Command, args []string) error {
+func runInject(env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		return command.UsageErrorf("expected at least one file arg, got 0")
+		return env.UsageErrorf("expected at least one file arg, got 0")
 	}
 	for _, arg := range args {
 		if err := injectIntoFile(arg); err != nil {
diff --git a/cmd/vdl/doc.go b/cmd/vdl/doc.go
index 3e1f842..f06e0e8 100644
--- a/cmd/vdl/doc.go
+++ b/cmd/vdl/doc.go
@@ -192,11 +192,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    vdl help [flags] [command/topic ...]
 
@@ -209,6 +204,10 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 
 Vdl packages - help topic
 
diff --git a/cmd/vdl/main.go b/cmd/vdl/main.go
index 43d3285..4c8c557 100644
--- a/cmd/vdl/main.go
+++ b/cmd/vdl/main.go
@@ -17,7 +17,7 @@
 	"strings"
 
 	"v.io/v23/vdlroot/vdltool"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/textutil"
 	"v.io/x/ref/lib/vdl/build"
 	"v.io/x/ref/lib/vdl/codegen/golang"
@@ -32,7 +32,7 @@
 }
 
 func main() {
-	os.Exit(cmdVDL.Main())
+	cmdline2.Main(cmdVDL)
 }
 
 func checkErrors(errs *vdlutil.Errors) error {
@@ -45,8 +45,8 @@
 
 // runHelper returns a function that generates a sorted list of transitive
 // targets, and calls the supplied run function.
-func runHelper(run func(targets []*build.Package, env *compile.Env)) func(cmd *cmdline.Command, args []string) error {
-	return func(cmd *cmdline.Command, args []string) error {
+func runHelper(run func(targets []*build.Package, env *compile.Env)) cmdline2.Runner {
+	return cmdline2.RunnerFunc(func(_ *cmdline2.Env, args []string) error {
 		if flagVerbose {
 			vdlutil.SetVerbose()
 		}
@@ -69,10 +69,10 @@
 		}
 		run(targets, env)
 		return checkErrors(env.Errors)
-	}
+	})
 }
 
-var topicPackages = cmdline.Topic{
+var topicPackages = cmdline2.Topic{
 	Name:  "packages",
 	Short: "Description of package lists",
 	Long: `
@@ -105,7 +105,7 @@
 `,
 }
 
-var topicVdlPath = cmdline.Topic{
+var topicVdlPath = cmdline2.Topic{
 	Name:  "vdlpath",
 	Short: "Description of VDLPATH environment variable",
 	Long: `
@@ -135,7 +135,7 @@
 `,
 }
 
-var topicVdlRoot = cmdline.Topic{
+var topicVdlRoot = cmdline2.Topic{
 	Name:  "vdlroot",
 	Short: "Description of VDLROOT environment variable",
 	Long: `
@@ -150,7 +150,7 @@
 `,
 }
 
-var topicVdlConfig = cmdline.Topic{
+var topicVdlConfig = cmdline2.Topic{
 	Name:  "vdl.config",
 	Short: "Description of vdl.config files",
 	Long: `
@@ -170,10 +170,10 @@
 For more information, run "vdl help packages".
 `
 
-var cmdCompile = &cmdline.Command{
-	Run:   runHelper(runCompile),
-	Name:  "compile",
-	Short: "Compile packages and dependencies, but don't generate code",
+var cmdCompile = &cmdline2.Command{
+	Runner: runHelper(runCompile),
+	Name:   "compile",
+	Short:  "Compile packages and dependencies, but don't generate code",
 	Long: `
 Compile compiles packages and their transitive dependencies, but does not
 generate code.  This is useful to sanity-check that your VDL files are valid.
@@ -182,10 +182,10 @@
 	ArgsLong: pkgArgLong,
 }
 
-var cmdGenerate = &cmdline.Command{
-	Run:   runHelper(runGenerate),
-	Name:  "generate",
-	Short: "Compile packages and dependencies, and generate code",
+var cmdGenerate = &cmdline2.Command{
+	Runner: runHelper(runGenerate),
+	Name:   "generate",
+	Short:  "Compile packages and dependencies, and generate code",
 	Long: `
 Generate compiles packages and their transitive dependencies, and generates code
 in the specified languages.
@@ -194,10 +194,10 @@
 	ArgsLong: pkgArgLong,
 }
 
-var cmdAudit = &cmdline.Command{
-	Run:   runHelper(runAudit),
-	Name:  "audit",
-	Short: "Check if any packages are stale and need generation",
+var cmdAudit = &cmdline2.Command{
+	Runner: runHelper(runAudit),
+	Name:   "audit",
+	Short:  "Check if any packages are stale and need generation",
 	Long: `
 Audit runs the same logic as generate, but doesn't write out generated files.
 Returns a 0 exit code if all packages are up-to-date, otherwise returns a
@@ -207,10 +207,10 @@
 	ArgsLong: pkgArgLong,
 }
 
-var cmdList = &cmdline.Command{
-	Run:   runHelper(runList),
-	Name:  "list",
-	Short: "List package and dependency info in transitive order",
+var cmdList = &cmdline2.Command{
+	Runner: runHelper(runList),
+	Name:   "list",
+	Short:  "List package and dependency info in transitive order",
 	Long: `
 List returns information about packages and their transitive dependencies, in
 transitive order.  This is the same order the generate and compile commands use
@@ -243,7 +243,7 @@
 }
 
 func (gls *genLangs) Set(value string) error {
-	// If the flag is repeated on the cmdline it is overridden.  Duplicates within
+	// If the flag is repeated on the cmdline2 it is overridden.  Duplicates within
 	// the comma separated list are ignored, and retain their original ordering.
 	*gls = genLangs{}
 	seen := make(map[vdltool.GenLanguage]bool)
@@ -354,15 +354,15 @@
 )
 
 // Root returns the root command for the VDL tool.
-var cmdVDL = &cmdline.Command{
+var cmdVDL = &cmdline2.Command{
 	Name:  "vdl",
 	Short: "manages Vanadium Definition Language source code",
 	Long: `
 Command vdl manages Vanadium Definition Language source code.  It's similar to
 the go tool used for managing Go source code.
 `,
-	Children: []*cmdline.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
-	Topics:   []cmdline.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
+	Children: []*cmdline2.Command{cmdGenerate, cmdCompile, cmdAudit, cmdList},
+	Topics:   []cmdline2.Topic{topicPackages, topicVdlPath, topicVdlRoot, topicVdlConfig},
 }
 
 func init() {
diff --git a/cmd/vdl/vdl_test.go b/cmd/vdl/vdl_test.go
index 2e4de41..12848ab 100644
--- a/cmd/vdl/vdl_test.go
+++ b/cmd/vdl/vdl_test.go
@@ -12,6 +12,8 @@
 	"path/filepath"
 	"strings"
 	"testing"
+
+	"v.io/x/lib/cmdline2"
 )
 
 const (
@@ -23,8 +25,6 @@
 
 // Compares generated VDL files against the copy in the repo.
 func TestVDLGenerator(t *testing.T) {
-	// Setup the vdl command-line.
-	cmdVDL.Init(nil, os.Stdout, os.Stderr)
 	// Use vdl to generate Go code from input, into a temporary directory.
 	outDir, err := ioutil.TempDir("", "vdltest")
 	if err != nil {
@@ -33,7 +33,8 @@
 	defer os.RemoveAll(outDir)
 	// TODO(toddw): test the generated java and javascript files too.
 	outOpt := fmt.Sprintf("--go-out-dir=%s", outDir)
-	if err := cmdVDL.Execute([]string{"generate", "--lang=go", outOpt, testDir}); err != nil {
+	env := cmdline2.NewEnv()
+	if err := cmdline2.ParseAndRun(cmdVDL, env, []string{"generate", "--lang=go", outOpt, testDir}); err != nil {
 		t.Fatalf("Execute() failed: %v", err)
 	}
 	// Check that each *.vdl.go file in the testDir matches the generated output.
diff --git a/cmd/vom/doc.go b/cmd/vom/doc.go
index 09aebc3..e34f185 100644
--- a/cmd/vom/doc.go
+++ b/cmd/vom/doc.go
@@ -17,51 +17,8 @@
    help        Display help for commands or topics
 
 The global flags are:
- -alsologtostderr=true
-   log to standard error as well as files
- -log_backtrace_at=:0
-   when logging hits line file:N, emit a stack trace
- -log_dir=
-   if non-empty, write log files to this directory
- -logtostderr=false
-   log to standard error instead of files
- -max_stack_buf_size=4292608
-   max size in bytes of the buffer to use for logging stack traces
- -stderrthreshold=2
-   logs at or above this threshold go to stderr
- -v=0
-   log level for V logs
- -v23.credentials=
-   directory to use for storing security credentials
- -v23.i18n-catalogue=
-   18n catalogue files to load, comma separated
  -v23.metadata=<just specify -v23.metadata to activate>
    Displays metadata for the program and exits.
- -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
-   local namespace root; can be repeated to provided multiple roots
- -v23.permissions.file=map[]
-   specify a perms file as <name>:<permsfile>
- -v23.permissions.literal=
-   explicitly specify the runtime perms as a JSON-encoded access.Permissions.
-   Overrides all --v23.permissions.file flags.
- -v23.proxy=
-   object name of proxy service to use to export services across network
-   boundaries
- -v23.tcp.address=
-   address to listen on
- -v23.tcp.protocol=wsh
-   protocol to listen with
- -v23.vtrace.cache-size=1024
-   The number of vtrace traces to store in memory.
- -v23.vtrace.collect-regexp=
-   Spans and annotations that match this regular expression will trigger trace
-   collection.
- -v23.vtrace.dump-on-shutdown=true
-   If true, dump all stored traces on runtime shutdown.
- -v23.vtrace.sample-rate=0
-   Rate (from 0.0 to 1.0) to sample vtrace traces.
- -vmodule=
-   comma-separated list of pattern=N settings for file-filtered logging
 
 Vom decode
 
@@ -119,11 +76,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    vom help [flags] [command/topic ...]
 
@@ -136,5 +88,9 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 */
 package main
diff --git a/cmd/vom/vom.go b/cmd/vom/vom.go
index 1c7398f..f387762 100644
--- a/cmd/vom/vom.go
+++ b/cmd/vom/vom.go
@@ -17,39 +17,28 @@
 	"strings"
 	"unicode"
 
-	"v.io/v23"
 	"v.io/v23/vdl"
 	"v.io/v23/vom"
-	"v.io/x/lib/cmdline"
-	_ "v.io/x/ref/profiles/static"
+	"v.io/x/lib/cmdline2"
 )
 
 func main() {
-	cmdline.HideGlobalFlagsExcept()
-	os.Exit(cmdVom.Main())
+	cmdline2.Main(cmdVom)
 }
 
-func runHelper(run cmdline.Runner) cmdline.Runner {
-	return func(cmd *cmdline.Command, args []string) error {
-		_, shutdown := v23.Init()
-		defer shutdown()
-		return run(cmd, args)
-	}
-}
-
-var cmdVom = &cmdline.Command{
+var cmdVom = &cmdline2.Command{
 	Name:  "vom",
 	Short: "helps debug the Vanadium Object Marshaling wire protocol",
 	Long: `
 Command vom helps debug the Vanadium Object Marshaling wire protocol.
 `,
-	Children: []*cmdline.Command{cmdDecode, cmdDump},
+	Children: []*cmdline2.Command{cmdDecode, cmdDump},
 }
 
-var cmdDecode = &cmdline.Command{
-	Run:   runHelper(runDecode),
-	Name:  "decode",
-	Short: "Decode data encoded in the vom format",
+var cmdDecode = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runDecode),
+	Name:   "decode",
+	Short:  "Decode data encoded in the vom format",
 	Long: `
 Decode decodes data encoded in the vom format.  If no arguments are provided,
 decode reads the data from stdin, otherwise the argument is the data.
@@ -63,10 +52,10 @@
 	ArgsLong: "[data] is the data to decode; if not specified, reads from stdin",
 }
 
-var cmdDump = &cmdline.Command{
-	Run:   runHelper(runDump),
-	Name:  "dump",
-	Short: "Dump data encoded in the vom format into formatted output",
+var cmdDump = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runDump),
+	Name:   "dump",
+	Short:  "Dump data encoded in the vom format into formatted output",
 	Long: `
 Dump dumps data encoded in the vom format, generating formatted output
 describing each portion of the encoding.  If no arguments are provided, dump
@@ -102,12 +91,12 @@
 		"Data representation, one of "+fmt.Sprint(dataRepAll))
 }
 
-func runDecode(cmd *cmdline.Command, args []string) error {
+func runDecode(env *cmdline2.Env, args []string) error {
 	// Convert all inputs into a reader over binary bytes.
 	var data string
 	switch {
 	case len(args) > 1:
-		return cmd.UsageErrorf("too many args")
+		return env.UsageErrorf("too many args")
 	case len(args) == 1:
 		data = args[0]
 	default:
@@ -129,29 +118,29 @@
 	if err := decoder.Decode(&result); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), result)
+	fmt.Fprintln(env.Stdout, result)
 	if reader.Len() != 0 {
 		return fmt.Errorf("%d leftover bytes: % x", reader.Len(), reader.String())
 	}
 	return nil
 }
 
-func runDump(cmd *cmdline.Command, args []string) error {
+func runDump(env *cmdline2.Env, args []string) error {
 	// Handle non-streaming cases.
 	switch {
 	case len(args) > 1:
-		return cmd.UsageErrorf("too many args")
+		return env.UsageErrorf("too many args")
 	case len(args) == 1:
 		binbytes, err := dataToBinaryBytes(args[0])
 		if err != nil {
 			return err
 		}
-		fmt.Fprintln(cmd.Stdout(), vom.Dump(binbytes))
+		fmt.Fprintln(env.Stdout, vom.Dump(binbytes))
 		return nil
 	}
 	// Handle streaming from stdin.
 	// TODO(toddw): Add a flag to configure stdout/stderr dumping.
-	dumper := vom.NewDumper(dumpWriter{cmd.Stdout(), cmd.Stdout()})
+	dumper := vom.NewDumper(dumpWriter{env.Stdout, env.Stdout})
 	defer dumper.Close()
 	// Handle simple non-hex cases.
 	switch flagDataRep {
diff --git a/cmd/vomtestgen/generate.go b/cmd/vomtestgen/generate.go
index e6382b3..34420c2 100644
--- a/cmd/vomtestgen/generate.go
+++ b/cmd/vomtestgen/generate.go
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
 package main
 
 import (
@@ -13,10 +16,9 @@
 	"path/filepath"
 	"strings"
 
-	"v.io/v23"
 	"v.io/v23/vdl"
 	"v.io/v23/vom"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/ref/lib/vdl/build"
 	"v.io/x/ref/lib/vdl/codegen"
 	"v.io/x/ref/lib/vdl/codegen/vdlgen"
@@ -30,10 +32,10 @@
 	vomdataConfig    = "vomdata.vdl.config"
 )
 
-var cmdGenerate = &cmdline.Command{
-	Run:   runGenerate,
-	Name:  "vomtestgen",
-	Short: "generates test data for the vom wire protocol implementation",
+var cmdGenerate = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runGenerate),
+	Name:   "vomtestgen",
+	Short:  "generates test data for the vom wire protocol implementation",
 	Long: `
 Command vomtestgen generates test data for the vom wire protocol implementation.
 It takes as input a vdl config file, and outputs a vdl package with test cases.
@@ -62,38 +64,38 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept()
 	cmdGenerate.Flags.IntVar(&optGenMaxErrors, "max-errors", -1, "Stop processing after this many errors, or -1 for unlimited.")
 	cmdGenerate.Flags.StringVar(&optGenExts, "exts", ".vdl", "Comma-separated list of valid VDL file name extensions.")
 }
 
-func runGenerate(cmd *cmdline.Command, args []string) error {
-	_, shutdown := v23.Init()
-	defer shutdown()
+func main() {
+	cmdline2.Main(cmdGenerate)
+}
 
+func runGenerate(env *cmdline2.Env, args []string) error {
 	debug := new(bytes.Buffer)
-	defer dumpDebug(cmd.Stderr(), debug)
-	env := compile.NewEnv(optGenMaxErrors)
+	defer dumpDebug(env.Stderr, debug)
+	compileEnv := compile.NewEnv(optGenMaxErrors)
 	// Get the input datafile path.
 	var path string
 	switch len(args) {
 	case 0:
-		srcDirs := build.SrcDirs(env.Errors)
-		if err := env.Errors.ToError(); err != nil {
-			return cmd.UsageErrorf("%v", err)
+		srcDirs := build.SrcDirs(compileEnv.Errors)
+		if err := compileEnv.Errors.ToError(); err != nil {
+			return env.UsageErrorf("%v", err)
 		}
 		path = guessDataFilePath(debug, srcDirs)
 		if path == "" {
-			return cmd.UsageErrorf("couldn't find vomdata file in src dirs: %v", srcDirs)
+			return env.UsageErrorf("couldn't find vomdata file in src dirs: %v", srcDirs)
 		}
 	case 1:
 		path = args[0]
 	default:
-		return cmd.UsageErrorf("too many args (expecting exactly 1 vomdata file)")
+		return env.UsageErrorf("too many args (expecting exactly 1 vomdata file)")
 	}
 	inName := filepath.Clean(path)
 	if !strings.HasSuffix(inName, ".vdl.config") {
-		return cmd.UsageErrorf(`vomdata file doesn't end in ".vdl.config": %s`, inName)
+		return env.UsageErrorf(`vomdata file doesn't end in ".vdl.config": %s`, inName)
 	}
 	outName := inName[:len(inName)-len(".config")]
 	// Remove the generated file, so that it doesn't interfere with compiling the
@@ -101,7 +103,7 @@
 	if err := os.Remove(outName); err == nil {
 		fmt.Fprintf(debug, "Removed output file %v\n", outName)
 	}
-	config, err := compileConfig(debug, inName, env)
+	config, err := compileConfig(debug, inName, compileEnv)
 	if err != nil {
 		return err
 	}
@@ -113,7 +115,7 @@
 		return err
 	}
 	debug.Reset() // Don't dump debugging information on success
-	fmt.Fprintf(cmd.Stdout(), "Wrote output file %v\n", outName)
+	fmt.Fprintf(env.Stdout, "Wrote output file %v\n", outName)
 	return nil
 }
 
diff --git a/cmd/vomtestgen/main.go b/cmd/vomtestgen/main.go
deleted file mode 100644
index 6305464..0000000
--- a/cmd/vomtestgen/main.go
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// The following enables go generate to generate the doc.go file.
-//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
-
-package main
-
-import (
-	"os"
-)
-
-func main() {
-	os.Exit(cmdGenerate.Main())
-}
diff --git a/cmd/vrun/vrun.go b/cmd/vrun/vrun.go
index 390d0f1..dbcc8df 100644
--- a/cmd/vrun/vrun.go
+++ b/cmd/vrun/vrun.go
@@ -17,9 +17,10 @@
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/security"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
 	"v.io/x/ref/envvar"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/services/agent/agentlib"
 	"v.io/x/ref/services/agent/keymgr"
 	"v.io/x/ref/services/role"
@@ -33,8 +34,8 @@
 	roleFlag     string
 )
 
-var cmdVrun = &cmdline.Command{
-	Run:      vrun,
+var cmdVrun = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(vrun),
 	Name:     "vrun",
 	Short:    "executes commands with a derived Vanadium principal",
 	Long:     "Command vrun executes commands with a derived Vanadium principal.",
@@ -42,7 +43,7 @@
 }
 
 func main() {
-	cmdline.HideGlobalFlagsExcept()
+	cmdline2.HideGlobalFlagsExcept()
 	syscall.CloseOnExec(3)
 	syscall.CloseOnExec(4)
 
@@ -50,19 +51,16 @@
 	cmdVrun.Flags.StringVar(&nameFlag, "name", "", "Name to use for the blessing. Uses the command name if unset.")
 	cmdVrun.Flags.StringVar(&roleFlag, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
 
-	os.Exit(cmdVrun.Main())
+	cmdline2.Main(cmdVrun)
 }
 
-func vrun(cmd *cmdline.Command, args []string) error {
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
+func vrun(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
 		args = []string{"bash", "--norc"}
 	}
 	principal, conn, err := createPrincipal(ctx)
 	if err != nil {
-		return err
+		return env.UsageErrorf("%v", err)
 	}
 	if len(roleFlag) == 0 {
 		if len(nameFlag) == 0 {
diff --git a/profiles/internal/rpc/stress/mtstress/doc.go b/profiles/internal/rpc/stress/mtstress/doc.go
index 82fe778..f3ade96 100644
--- a/profiles/internal/rpc/stress/mtstress/doc.go
+++ b/profiles/internal/rpc/stress/mtstress/doc.go
@@ -93,11 +93,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    mtstress help [flags] [command/topic ...]
 
@@ -110,5 +105,9 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 */
 package main
diff --git a/profiles/internal/rpc/stress/mtstress/main.go b/profiles/internal/rpc/stress/mtstress/main.go
index 69e82cc..24ebf44 100644
--- a/profiles/internal/rpc/stress/mtstress/main.go
+++ b/profiles/internal/rpc/stress/mtstress/main.go
@@ -10,7 +10,6 @@
 import (
 	"flag"
 	"net"
-	"os"
 	"regexp"
 	"time"
 
@@ -19,22 +18,21 @@
 	"v.io/v23/naming"
 	"v.io/v23/options"
 	"v.io/v23/verror"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((rate)|(duration)|(reauthenticate))$`))
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^((rate)|(duration)|(reauthenticate))$`))
 }
 
 var (
-	gctx *context.T
-
 	rate     = flag.Float64("rate", 1, "Rate, in RPCs per second, to send to the test server")
 	duration = flag.Duration("duration", 10*time.Second, "Duration for sending test traffic and measuring latency")
 	reauth   = flag.Bool("reauthenticate", false, "If true, establish a new authenticated connection for each RPC, simulating load from a distinct process")
 
-	cmdMount = &cmdline.Command{
+	cmdMount = &cmdline2.Command{
 		Name:  "mount",
 		Short: "Measure latency of the Mount RPC at a fixed request rate",
 		Long: `
@@ -48,14 +46,14 @@
 seconds, 1m for 1 minute etc.
 Valid time units are "ms", "s", "m", "h".
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if got, want := len(args), 2; got != want {
-				return cmd.UsageErrorf("mount: got %d arguments, want %d", got, want)
+				return env.UsageErrorf("mount: got %d arguments, want %d", got, want)
 			}
 			mountpoint := args[0]
 			ttl, err := time.ParseDuration(args[1])
 			if err != nil {
-				return cmd.UsageErrorf("invalid TTL: %v", err)
+				return env.UsageErrorf("invalid TTL: %v", err)
 			}
 			// Make up a random server to mount
 			ep := naming.FormatEndpoint("tcp", "127.0.0.1:14141")
@@ -69,15 +67,15 @@
 				}
 				return time.Since(start), nil
 			}
-			p, err := paramsFromFlags(mountpoint)
+			p, err := paramsFromFlags(ctx, mountpoint)
 			if err != nil {
 				return err
 			}
 			return run(mount, p)
-		},
+		}),
 	}
 
-	cmdResolve = &cmdline.Command{
+	cmdResolve = &cmdline2.Command{
 		Name:  "resolve",
 		Short: "Measure latency of the Resolve RPC at a fixed request rate",
 		Long: `
@@ -87,9 +85,9 @@
 		ArgsLong: `
 <name> the object name to resolve
 `,
-		Run: func(cmd *cmdline.Command, args []string) error {
+		Runner: v23cmd.RunnerFunc(func(ctx *context.T, env *cmdline2.Env, args []string) error {
 			if got, want := len(args), 1; got != want {
-				return cmd.UsageErrorf("resolve: got %d arguments, want %d", got, want)
+				return env.UsageErrorf("resolve: got %d arguments, want %d", got, want)
 			}
 			name := args[0]
 			resolve := func(ctx *context.T) (time.Duration, error) {
@@ -103,21 +101,21 @@
 				}
 				return time.Since(start), nil
 			}
-			p, err := paramsFromFlags(name)
+			p, err := paramsFromFlags(ctx, name)
 			if err != nil {
 				return err
 			}
 			return run(resolve, p)
-		},
+		}),
 	}
 )
 
-func paramsFromFlags(objectName string) (params, error) {
+func paramsFromFlags(ctx *context.T, objectName string) (params, error) {
 	// Measure network distance to objectName
 	const iters = 5
 	addr, _ := naming.SplitAddressName(objectName)
 	if len(addr) == 0 {
-		addr, _ = naming.SplitAddressName(v23.GetNamespace(gctx).Roots()[0])
+		addr, _ = naming.SplitAddressName(v23.GetNamespace(ctx).Roots()[0])
 	}
 	ep, err := v23.NewEndpoint(addr)
 	if err != nil {
@@ -138,19 +136,16 @@
 		Rate:            *rate,
 		Duration:        *duration,
 		NetworkDistance: time.Duration(total.Nanoseconds() / iters),
-		Context:         gctx,
+		Context:         ctx,
 		Reauthenticate:  *reauth,
 	}, nil
 }
 
 func main() {
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-	gctx = ctx
-	root := &cmdline.Command{
+	root := &cmdline2.Command{
 		Name:     "mtstress",
 		Short:    "Tool to stress test a mounttable service by issuing a fixed rate of requests per second and measuring latency",
-		Children: []*cmdline.Command{cmdMount, cmdResolve},
+		Children: []*cmdline2.Command{cmdMount, cmdResolve},
 	}
-	os.Exit(root.Main())
+	cmdline2.Main(root)
 }
diff --git a/profiles/internal/rpc/stress/stress/load.go b/profiles/internal/rpc/stress/stress/load.go
index 9c9ceeb..e041349 100644
--- a/profiles/internal/rpc/stress/stress/load.go
+++ b/profiles/internal/rpc/stress/stress/load.go
@@ -11,9 +11,9 @@
 	"runtime"
 	"time"
 
-	"v.io/v23"
-
-	"v.io/x/lib/cmdline"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/profiles/internal/rpc/stress/internal"
 )
 
@@ -36,8 +36,8 @@
 	QpsPerCore float64
 }
 
-var cmdLoadTest = &cmdline.Command{
-	Run:      runLoadTest,
+var cmdLoadTest = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runLoadTest),
 	Name:     "load",
 	Short:    "Run load test",
 	Long:     "Run load test",
@@ -45,12 +45,12 @@
 	ArgsLong: "<server> ... A list of servers to connect to.",
 }
 
-func runLoadTest(cmd *cmdline.Command, args []string) error {
+func runLoadTest(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		return cmd.UsageErrorf("no server specified")
+		return env.UsageErrorf("no server specified")
 	}
 	if outFormat != "text" && outFormat != "json" {
-		return cmd.UsageErrorf("invalid output format: %s\n", outFormat)
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
 	}
 
 	cores := cpus
@@ -59,11 +59,8 @@
 	}
 	runtime.GOMAXPROCS(cores)
 
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
-	fmt.Fprintf(cmd.Stdout(), "starting load test against %d server(s) using %d core(s)...\n", len(args), cores)
-	fmt.Fprintf(cmd.Stdout(), "payloadSize: %d, duration: %v\n", payloadSize, duration)
+	fmt.Fprintf(env.Stdout, "starting load test against %d server(s) using %d core(s)...\n", len(args), cores)
+	fmt.Fprintf(env.Stdout, "payloadSize: %d, duration: %v\n", payloadSize, duration)
 
 	start := time.Now()
 	done := make(chan loadStats)
@@ -91,7 +88,7 @@
 	merged.QpsPerCore = merged.Qps / float64(cores)
 	elapsed := time.Since(start)
 	fmt.Printf("done after %v\n", elapsed)
-	return outLoadStats(cmd.Stdout(), outFormat, "load stats:", &merged)
+	return outLoadStats(env.Stdout, outFormat, "load stats:", &merged)
 }
 
 func outLoadStats(w io.Writer, format, title string, stats *loadStats) error {
diff --git a/profiles/internal/rpc/stress/stress/main.go b/profiles/internal/rpc/stress/stress/main.go
index 561cf60..d269d84 100644
--- a/profiles/internal/rpc/stress/stress/main.go
+++ b/profiles/internal/rpc/stress/stress/main.go
@@ -5,17 +5,15 @@
 package main
 
 import (
-	"os"
-
-	"v.io/v23"
-
-	"v.io/x/lib/cmdline"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/profiles/internal/rpc/stress"
 	_ "v.io/x/ref/profiles/static"
 )
 
-var cmdStopServers = &cmdline.Command{
-	Run:      runStopServers,
+var cmdStopServers = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStopServers),
 	Name:     "stop",
 	Short:    "Stop servers",
 	Long:     "Stop servers",
@@ -23,14 +21,10 @@
 	ArgsLong: "<server> ... A list of servers to stop.",
 }
 
-func runStopServers(cmd *cmdline.Command, args []string) error {
+func runStopServers(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		return cmd.UsageErrorf("no server specified")
+		return env.UsageErrorf("no server specified")
 	}
-
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
 	for _, server := range args {
 		if err := stress.StressClient(server).Stop(ctx); err != nil {
 			return err
@@ -40,16 +34,16 @@
 }
 
 func main() {
-	cmdRoot := &cmdline.Command{
+	cmdRoot := &cmdline2.Command{
 		Name:  "stress",
 		Short: "Tool to stress/load test RPC",
 		Long:  "Tool to stress/load test RPC by issuing randomly generated requests",
-		Children: []*cmdline.Command{
+		Children: []*cmdline2.Command{
 			cmdStressTest,
 			cmdStressStats,
 			cmdLoadTest,
 			cmdStopServers,
 		},
 	}
-	os.Exit(cmdRoot.Main())
+	cmdline2.Main(cmdRoot)
 }
diff --git a/profiles/internal/rpc/stress/stress/stress.go b/profiles/internal/rpc/stress/stress/stress.go
index 3d363c0..db1306a 100644
--- a/profiles/internal/rpc/stress/stress/stress.go
+++ b/profiles/internal/rpc/stress/stress/stress.go
@@ -12,9 +12,9 @@
 	"runtime"
 	"time"
 
-	"v.io/v23"
-
-	"v.io/x/lib/cmdline"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/profiles/internal/rpc/stress"
 	"v.io/x/ref/profiles/internal/rpc/stress/internal"
 )
@@ -39,8 +39,8 @@
 	cmdStressStats.Flags.StringVar(&outFormat, "format", "text", "Stats output format; either text or json")
 }
 
-var cmdStressTest = &cmdline.Command{
-	Run:      runStressTest,
+var cmdStressTest = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStressTest),
 	Name:     "stress",
 	Short:    "Run stress test",
 	Long:     "Run stress test",
@@ -48,23 +48,19 @@
 	ArgsLong: "<server> ... A list of servers to connect to.",
 }
 
-func runStressTest(cmd *cmdline.Command, args []string) error {
+func runStressTest(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		return cmd.UsageErrorf("no server specified")
+		return env.UsageErrorf("no server specified")
 	}
 	if outFormat != "text" && outFormat != "json" {
-		return cmd.UsageErrorf("invalid output format: %s\n", outFormat)
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
 	}
 
 	cores := runtime.NumCPU()
 	runtime.GOMAXPROCS(cores)
-
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
 	rand.Seed(time.Now().UnixNano())
-	fmt.Fprintf(cmd.Stdout(), "starting stress test against %d server(s) using %d core(s)...\n", len(args), cores)
-	fmt.Fprintf(cmd.Stdout(), "workers: %d, maxChunkCnt: %d, maxPayloadSize: %d, duration: %v\n", workers, maxChunkCnt, maxPayloadSize, duration)
+	fmt.Fprintf(env.Stdout, "starting stress test against %d server(s) using %d core(s)...\n", len(args), cores)
+	fmt.Fprintf(env.Stdout, "workers: %d, maxChunkCnt: %d, maxPayloadSize: %d, duration: %v\n", workers, maxChunkCnt, maxPayloadSize, duration)
 
 	start := time.Now()
 	done := make(chan stress.SumStats)
@@ -100,11 +96,11 @@
 	}
 	elapsed := time.Since(start)
 	fmt.Printf("done after %v\n", elapsed)
-	return outSumStats(cmd.Stdout(), outFormat, "client stats:", &merged)
+	return outSumStats(env.Stdout, outFormat, "client stats:", &merged)
 }
 
-var cmdStressStats = &cmdline.Command{
-	Run:      runStressStats,
+var cmdStressStats = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStressStats),
 	Name:     "stats",
 	Short:    "Print out stress stats of servers",
 	Long:     "Print out stress stats of servers",
@@ -112,24 +108,20 @@
 	ArgsLong: "<server> ... A list of servers to connect to.",
 }
 
-func runStressStats(cmd *cmdline.Command, args []string) error {
+func runStressStats(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
-		return cmd.UsageErrorf("no server specified")
+		return env.UsageErrorf("no server specified")
 	}
 	if outFormat != "text" && outFormat != "json" {
-		return cmd.UsageErrorf("invalid output format: %s\n", outFormat)
+		return env.UsageErrorf("invalid output format: %s\n", outFormat)
 	}
-
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
 	for _, server := range args {
 		stats, err := stress.StressClient(server).GetSumStats(ctx)
 		if err != nil {
 			return err
 		}
 		title := fmt.Sprintf("server stats(%s):", server)
-		if err := outSumStats(cmd.Stdout(), outFormat, title, &stats); err != nil {
+		if err := outSumStats(env.Stdout, outFormat, title, &stats); err != nil {
 			return err
 		}
 	}
diff --git a/profiles/internal/util.go b/profiles/internal/util.go
index a530087..58894d7 100644
--- a/profiles/internal/util.go
+++ b/profiles/internal/util.go
@@ -39,8 +39,7 @@
 	if handle != nil {
 		config = handle.Config.Dump()
 	}
-	f.Parse(os.Args[1:], config)
-	return nil
+	return f.Parse(os.Args[1:], config)
 }
 
 // IPAddressChooser returns the preferred IP address, which is,
diff --git a/services/agent/vbecome/vbecome.go b/services/agent/vbecome/vbecome.go
index 929d6d1..d4c224b 100644
--- a/services/agent/vbecome/vbecome.go
+++ b/services/agent/vbecome/vbecome.go
@@ -21,10 +21,11 @@
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/security"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
 	"v.io/x/ref/envvar"
 	vsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/services/agent/internal/server"
 	"v.io/x/ref/services/role"
 
@@ -37,8 +38,8 @@
 	roleFlag     string
 )
 
-var cmdVbecome = &cmdline.Command{
-	Run:      vbecome,
+var cmdVbecome = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(vbecome),
 	Name:     "vbecome",
 	Short:    "executes commands with a derived Vanadium principal",
 	Long:     "Command vbecome executes commands with a derived Vanadium principal.",
@@ -49,7 +50,7 @@
 const keyServerFd = 4
 
 func main() {
-	cmdline.HideGlobalFlagsExcept()
+	cmdline2.HideGlobalFlagsExcept()
 	syscall.CloseOnExec(childAgentFd)
 	syscall.CloseOnExec(keyServerFd)
 
@@ -57,13 +58,10 @@
 	cmdVbecome.Flags.StringVar(&nameFlag, "name", "", "Name to use for the blessing.")
 	cmdVbecome.Flags.StringVar(&roleFlag, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
 
-	os.Exit(cmdVbecome.Main())
+	cmdline2.Main(cmdVbecome)
 }
 
-func vbecome(cmd *cmdline.Command, args []string) error {
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
+func vbecome(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) == 0 {
 		if shell := os.Getenv("SHELL"); shell != "" {
 			args = []string{shell}
diff --git a/services/device/deviced/commands.go b/services/device/deviced/commands.go
index a8bc375..a2eccc2 100644
--- a/services/device/deviced/commands.go
+++ b/services/device/deviced/commands.go
@@ -8,7 +8,7 @@
 	"fmt"
 	"os"
 
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 
 	"v.io/v23"
 	"v.io/x/lib/vlog"
@@ -41,8 +41,8 @@
 	}
 }
 
-var cmdInstall = &cmdline.Command{
-	Run:      runInstall,
+var cmdInstall = &cmdline2.Command{
+	Runner:   cmdline2.RunnerFunc(runInstall),
 	Name:     "install",
 	Short:    "Install the device manager.",
 	Long:     fmt.Sprintf("Performs installation of device manager into %s (if the env var set), or into the current dir otherwise", deviceDirEnv),
@@ -63,7 +63,7 @@
 	cmdInstall.Flags.BoolVar(&initMode, "init_mode", false, "if set, installs the device manager with the system init service manager")
 }
 
-func runInstall(cmd *cmdline.Command, args []string) error {
+func runInstall(env *cmdline2.Env, args []string) error {
 	if installFrom != "" {
 		// TODO(caprita): Also pass args into InstallFrom.
 		if err := impl.InstallFrom(installFrom); err != nil {
@@ -73,94 +73,94 @@
 		return nil
 	}
 	if suidHelper == "" {
-		return cmd.UsageErrorf("--suid_helper must be set")
+		return env.UsageErrorf("--suid_helper must be set")
 	}
 	if agent == "" {
-		return cmd.UsageErrorf("--agent must be set")
+		return env.UsageErrorf("--agent must be set")
 	}
 	if initMode && initHelper == "" {
-		return cmd.UsageErrorf("--init_helper must be set")
+		return env.UsageErrorf("--init_helper must be set")
 	}
-	if err := impl.SelfInstall(installationDir(), suidHelper, agent, initHelper, origin, singleUser, sessionMode, initMode, args, os.Environ(), cmd.Stderr(), cmd.Stdout()); err != nil {
+	if err := impl.SelfInstall(installationDir(), suidHelper, agent, initHelper, origin, singleUser, sessionMode, initMode, args, os.Environ(), env.Stderr, env.Stdout); err != nil {
 		vlog.Errorf("SelfInstall failed: %v", err)
 		return err
 	}
 	return nil
 }
 
-var cmdUninstall = &cmdline.Command{
-	Run:   runUninstall,
-	Name:  "uninstall",
-	Short: "Uninstall the device manager.",
-	Long:  fmt.Sprintf("Removes the device manager installation from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+var cmdUninstall = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runUninstall),
+	Name:   "uninstall",
+	Short:  "Uninstall the device manager.",
+	Long:   fmt.Sprintf("Removes the device manager installation from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
 func init() {
 	cmdUninstall.Flags.StringVar(&suidHelper, "suid_helper", "", "path to suid helper")
 }
 
-func runUninstall(cmd *cmdline.Command, _ []string) error {
+func runUninstall(env *cmdline2.Env, _ []string) error {
 	if suidHelper == "" {
-		return cmd.UsageErrorf("--suid_helper must be set")
+		return env.UsageErrorf("--suid_helper must be set")
 	}
-	if err := impl.Uninstall(installationDir(), suidHelper, cmd.Stderr(), cmd.Stdout()); err != nil {
+	if err := impl.Uninstall(installationDir(), suidHelper, env.Stderr, env.Stdout); err != nil {
 		vlog.Errorf("Uninstall failed: %v", err)
 		return err
 	}
 	return nil
 }
 
-var cmdStart = &cmdline.Command{
-	Run:   runStart,
-	Name:  "start",
-	Short: "Start the device manager.",
-	Long:  fmt.Sprintf("Starts the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+var cmdStart = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runStart),
+	Name:   "start",
+	Short:  "Start the device manager.",
+	Long:   fmt.Sprintf("Starts the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
-func runStart(cmd *cmdline.Command, _ []string) error {
-	if err := impl.Start(installationDir(), cmd.Stderr(), cmd.Stdout()); err != nil {
+func runStart(env *cmdline2.Env, _ []string) error {
+	if err := impl.Start(installationDir(), env.Stderr, env.Stdout); err != nil {
 		vlog.Errorf("Start failed: %v", err)
 		return err
 	}
 	return nil
 }
 
-var cmdStop = &cmdline.Command{
-	Run:   runStop,
-	Name:  "stop",
-	Short: "Stop the device manager.",
-	Long:  fmt.Sprintf("Stops the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
+var cmdStop = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runStop),
+	Name:   "stop",
+	Short:  "Stop the device manager.",
+	Long:   fmt.Sprintf("Stops the device manager installed under from %s (if the env var set), or the current dir otherwise", deviceDirEnv),
 }
 
-func runStop(cmd *cmdline.Command, _ []string) error {
+func runStop(env *cmdline2.Env, _ []string) error {
 	ctx, shutdown := v23.Init()
 	defer shutdown()
-	if err := impl.Stop(ctx, installationDir(), cmd.Stderr(), cmd.Stdout()); err != nil {
+	if err := impl.Stop(ctx, installationDir(), env.Stderr, env.Stdout); err != nil {
 		vlog.Errorf("Stop failed: %v", err)
 		return err
 	}
 	return nil
 }
 
-var cmdProfile = &cmdline.Command{
-	Run:   runProfile,
-	Name:  "profile",
-	Short: "Dumps profile for the device manager.",
-	Long:  "Prints the internal profile description for the device manager.",
+var cmdProfile = &cmdline2.Command{
+	Runner: cmdline2.RunnerFunc(runProfile),
+	Name:   "profile",
+	Short:  "Dumps profile for the device manager.",
+	Long:   "Prints the internal profile description for the device manager.",
 }
 
-func runProfile(cmd *cmdline.Command, _ []string) error {
+func runProfile(env *cmdline2.Env, _ []string) error {
 	spec, err := impl.ComputeDeviceProfile()
 	if err != nil {
 		vlog.Errorf("ComputeDeviceProfile failed: %v", err)
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Profile: %#v\n", spec)
+	fmt.Fprintf(env.Stdout, "Profile: %#v\n", spec)
 	desc, err := impl.Describe()
 	if err != nil {
 		vlog.Errorf("Describe failed: %v", err)
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Description: %#v\n", desc)
+	fmt.Fprintf(env.Stdout, "Description: %#v\n", desc)
 	return nil
 }
diff --git a/services/device/deviced/doc.go b/services/device/deviced/doc.go
index cc1c5bb..f9000bc 100644
--- a/services/device/deviced/doc.go
+++ b/services/device/deviced/doc.go
@@ -10,8 +10,8 @@
 which implements the v.io/v23/services/device interfaces.
 
 Usage:
-   deviced <command>
    deviced
+   deviced <command>
 
 The deviced commands are:
    install     Install the device manager.
@@ -184,11 +184,6 @@
 
 "help ..." recursively displays help for all commands and topics.
 
-Output is formatted to a target width in runes, determined by checking the
-CMDLINE_WIDTH environment variable, falling back on the terminal width, falling
-back on 80 chars.  By setting CMDLINE_WIDTH=x, if x > 0 the width is x, if x < 0
-the width is unlimited, and if x == 0 or is unset one of the fallbacks is used.
-
 Usage:
    deviced help [flags] [command/topic ...]
 
@@ -201,5 +196,9 @@
       full    - Good for cmdline output, shows all global flags.
       godoc   - Good for godoc processing.
    Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+   Format output to this target width in runes, or unlimited if width < 0.
+   Defaults to the terminal width if available.  Override the default by setting
+   the CMDLINE_WIDTH environment variable.
 */
 package main
diff --git a/services/device/deviced/main.go b/services/device/deviced/main.go
index 2e824b2..8da9320 100644
--- a/services/device/deviced/main.go
+++ b/services/device/deviced/main.go
@@ -11,7 +11,8 @@
 	"os"
 	"runtime"
 
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 )
 
 func main() {
@@ -21,15 +22,15 @@
 		runtime.GOMAXPROCS(runtime.NumCPU())
 	}
 
-	rootCmd := cmdline.Command{
+	rootCmd := &cmdline2.Command{
 		Name:  "deviced",
 		Short: "launch, configure and manage the deviced daemon",
 		Long: `
 Command deviced is used to launch, configure and manage the deviced daemon,
 which implements the v.io/v23/services/device interfaces.
 `,
-		Children: []*cmdline.Command{cmdInstall, cmdUninstall, cmdStart, cmdStop, cmdProfile},
-		Run:      runServer,
+		Children: []*cmdline2.Command{cmdInstall, cmdUninstall, cmdStart, cmdStop, cmdProfile},
+		Runner:   v23cmd.RunnerFunc(runServer),
 	}
-	os.Exit(rootCmd.Main())
+	cmdline2.Main(rootCmd)
 }
diff --git a/services/device/deviced/server.go b/services/device/deviced/server.go
index 01c378e..d90ae68 100644
--- a/services/device/deviced/server.go
+++ b/services/device/deviced/server.go
@@ -19,7 +19,7 @@
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/verror"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/lib/mgmt"
@@ -47,13 +47,10 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((name)|(restart-exit-code)|(neighborhood-name)|(deviced-port)|(proxy-port)|(use-pairing-token))$`))
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^((name)|(restart-exit-code)|(neighborhood-name)|(deviced-port)|(proxy-port)|(use-pairing-token))$`))
 }
 
-func runServer(*cmdline.Command, []string) error {
-	ctx, shutdown := v23.Init()
-	defer shutdown()
-
+func runServer(ctx *context.T, _ *cmdline2.Env, _ []string) error {
 	var testMode bool
 	// If this device manager was started by another device manager, it must
 	// be part of a self update to test that this binary works. In that
@@ -104,7 +101,7 @@
 	dev := starter.DeviceArgs{
 		ConfigState:     configState,
 		TestMode:        testMode,
-		RestartCallback: func() { exitErr = cmdline.ErrExitCode(*restartExitCode) },
+		RestartCallback: func() { exitErr = cmdline2.ErrExitCode(*restartExitCode) },
 		PairingToken:    pairingToken,
 	}
 	if testMode {