ref: Add x/lib/cmdline2 and x/ref/lib/v23cmd.

The cmdline2 package is very similar to the existing cmdline
package.  The main differences:
* Uses Runner interface, rather than Run func.
* Exposes Parse and ParseAndRun, rather than Init/Execute
* Passes *Env to runners, rather than *Command.

The purpose of these changes is to make it possible to get the
ordering of initialization right in our command line tools that
rely on v23.Init.  In particular, the previous Init/Execute API
was bad, since Execute perfomed both arg parsing and command
running.  That made it hard to inject special-purpose code,
e.g. for intializing the v23 context in tests.

The v23cmd package is a very small utility package that builds on
top of cmdline2 to make it really easy to add commands that need
a v23 context to be initialized.

The combination of these two packages lets us get rid of the
global gctx variable used in many of our cmdline tools.  I've
updated most tools that used gctx, to make sure the new mechanism
is really good enough for our needs.

I'll make another sweep in separate CLs to convert all other
tools over to cmdline2, and then have mechanical CLs to replace
cmdline with cmdline2.

Addresses some of vanadium/issues#407

Change-Id: Ie32bc4c45453442dcccd9d8d68bb4713dbdeac89
diff --git a/cmd/mounttable/doc.go b/cmd/mounttable/doc.go
index 7763255..0dd113d 100644
--- a/cmd/mounttable/doc.go
+++ b/cmd/mounttable/doc.go
@@ -116,11 +116,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:
    mounttable help [flags] [command/topic ...]
 
@@ -133,5 +128,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/mounttable/impl.go b/cmd/mounttable/impl.go
index 8be675e..04fcc9e 100644
--- a/cmd/mounttable/impl.go
+++ b/cmd/mounttable/impl.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 .
+
 package main
 
 import (
@@ -16,16 +19,19 @@
 	"v.io/v23/options"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 )
 
-func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+func main() {
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline2.Main(cmdRoot)
 }
 
-var cmdGlob = &cmdline.Command{
-	Run:      runGlob,
+var cmdGlob = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
 	Name:     "glob",
 	Short:    "returns all matching entries in the mount table",
 	Long:     "returns all matching entries in the mount table",
@@ -37,8 +43,8 @@
 `,
 }
 
-func runGlob(cmd *cmdline.Command, args []string) error {
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+func runGlob(ctx *context.T, env *cmdline2.Env, args []string) error {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	if len(args) == 1 {
@@ -49,7 +55,7 @@
 		args = append([]string{roots[0]}, args...)
 	}
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 
 	name, pattern := args[0], args[1]
@@ -65,11 +71,11 @@
 		}
 		switch v := gr.(type) {
 		case naming.GlobReplyEntry:
-			fmt.Fprint(cmd.Stdout(), v.Value.Name)
+			fmt.Fprint(env.Stdout, v.Value.Name)
 			for _, s := range v.Value.Servers {
-				fmt.Fprintf(cmd.Stdout(), " %s (Deadline %s)", s.Server, s.Deadline.Time)
+				fmt.Fprintf(env.Stdout, " %s (Deadline %s)", s.Server, s.Deadline.Time)
 			}
-			fmt.Fprintln(cmd.Stdout())
+			fmt.Fprintln(env.Stdout)
 		}
 	}
 	if err := call.Finish(); err != nil {
@@ -78,8 +84,8 @@
 	return nil
 }
 
-var cmdMount = &cmdline.Command{
-	Run:      runMount,
+var cmdMount = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runMount),
 	Name:     "mount",
 	Short:    "Mounts a server <name> onto a mount table",
 	Long:     "Mounts a server <name> onto a mount table",
@@ -97,10 +103,10 @@
 `,
 }
 
-func runMount(cmd *cmdline.Command, args []string) error {
+func runMount(ctx *context.T, env *cmdline2.Env, args []string) error {
 	got := len(args)
 	if got < 2 || got > 4 {
-		return cmd.UsageErrorf("mount: incorrect number of arguments, expected 2, 3, or 4, got %d", got)
+		return env.UsageErrorf("mount: incorrect number of arguments, expected 2, 3, or 4, got %d", got)
 	}
 	name := args[0]
 	server := args[1]
@@ -124,18 +130,18 @@
 			}
 		}
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	client := v23.GetClient(ctx)
 	if err := client.Call(ctx, name, "Mount", []interface{}{server, seconds, flags}, nil, options.NoResolve{}); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Name mounted successfully.")
+	fmt.Fprintln(env.Stdout, "Name mounted successfully.")
 	return nil
 }
 
-var cmdUnmount = &cmdline.Command{
-	Run:      runUnmount,
+var cmdUnmount = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runUnmount),
 	Name:     "unmount",
 	Short:    "removes server <name> from the mount table",
 	Long:     "removes server <name> from the mount table",
@@ -146,22 +152,22 @@
 `,
 }
 
-func runUnmount(cmd *cmdline.Command, args []string) error {
+func runUnmount(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	client := v23.GetClient(ctx)
 	if err := client.Call(ctx, args[0], "Unmount", []interface{}{args[1]}, nil, options.NoResolve{}); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Unmount successful or name not mounted.")
+	fmt.Fprintln(env.Stdout, "Unmount successful or name not mounted.")
 	return nil
 }
 
-var cmdResolveStep = &cmdline.Command{
-	Run:      runResolveStep,
+var cmdResolveStep = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runResolveStep),
 	Name:     "resolvestep",
 	Short:    "takes the next step in resolving a name.",
 	Long:     "takes the next step in resolving a name.",
@@ -171,30 +177,28 @@
 `,
 }
 
-func runResolveStep(cmd *cmdline.Command, args []string) error {
+func runResolveStep(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	client := v23.GetClient(ctx)
 	var entry naming.MountEntry
 	if err := client.Call(ctx, args[0], "ResolveStep", nil, []interface{}{&entry}, options.NoResolve{}); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Servers: %v Suffix: %q MT: %v\n", entry.Servers, entry.Name, entry.ServesMountTable)
+	fmt.Fprintf(env.Stdout, "Servers: %v Suffix: %q MT: %v\n", entry.Servers, entry.Name, entry.ServesMountTable)
 	return nil
 }
 
-func root() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "mounttable",
-		Short: "sends commands to Vanadium mounttable services",
-		Long: `
+var cmdRoot = &cmdline2.Command{
+	Name:  "mounttable",
+	Short: "sends commands to Vanadium mounttable services",
+	Long: `
 Command mounttable sends commands to Vanadium mounttable services.
 `,
-		Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolveStep},
-	}
+	Children: []*cmdline2.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolveStep},
 }
 
 func blessingPatternsFromServer(ctx *context.T, server string) ([]security.BlessingPattern, error) {
diff --git a/cmd/mounttable/impl_test.go b/cmd/mounttable/impl_test.go
index 84de715..66495a9 100644
--- a/cmd/mounttable/impl_test.go
+++ b/cmd/mounttable/impl_test.go
@@ -19,8 +19,9 @@
 	"v.io/v23/security/access"
 	"v.io/v23/services/mounttable"
 	vdltime "v.io/v23/vdlroot/time"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
-
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/test"
 )
@@ -112,11 +113,10 @@
 }
 
 func TestMountTableClient(t *testing.T) {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
-	server, endpoint, err := startServer(t, gctx)
+	server, endpoint, err := startServer(t, ctx)
 	if err != nil {
 		return
 	}
@@ -124,15 +124,14 @@
 
 	// Make sure to use our newly created mounttable rather than the
 	// default.
-	v23.GetNamespace(gctx).SetRoots(endpoint.Name())
+	v23.GetNamespace(ctx).SetRoots(endpoint.Name())
 
 	// Setup the command-line.
-	cmd := root()
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 
 	// Test the 'glob' command.
-	if err := cmd.Execute([]string{"glob", naming.JoinAddressName(endpoint.String(), ""), "*"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"glob", naming.JoinAddressName(endpoint.String(), ""), "*"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	const deadRE = `\(Deadline ([^)]+)\)`
@@ -142,7 +141,7 @@
 	stdout.Reset()
 
 	// Test the 'mount' command.
-	if err := cmd.Execute([]string{"mount", "server", endpoint.Name(), "123s"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"mount", "server", endpoint.Name(), "123s"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, want := strings.TrimSpace(stdout.String()), "Name mounted successfully."; got != want {
@@ -151,7 +150,7 @@
 	stdout.Reset()
 
 	// Test the 'unmount' command.
-	if err := cmd.Execute([]string{"unmount", "server", endpoint.Name()}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"unmount", "server", endpoint.Name()}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, want := strings.TrimSpace(stdout.String()), "Unmount successful or name not mounted."; got != want {
@@ -161,7 +160,7 @@
 
 	// Test the 'resolvestep' command.
 	vlog.Infof("resovestep %s", naming.JoinAddressName(endpoint.String(), "name"))
-	if err := cmd.Execute([]string{"resolvestep", naming.JoinAddressName(endpoint.String(), "name")}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"resolvestep", naming.JoinAddressName(endpoint.String(), "name")}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, wantRE := strings.TrimSpace(stdout.String()), regexp.MustCompile(`Servers: \[\{server1 [^}]+\}\] Suffix: "name" MT: false`); !wantRE.MatchString(got) {
diff --git a/cmd/mounttable/main.go b/cmd/mounttable/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/cmd/mounttable/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/cmd/namespace/doc.go b/cmd/namespace/doc.go
index b6e2957..d873fb4 100644
--- a/cmd/namespace/doc.go
+++ b/cmd/namespace/doc.go
@@ -193,11 +193,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:
    namespace help [flags] [command/topic ...]
 
@@ -210,5 +205,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/namespace/impl.go b/cmd/namespace/impl.go
index 9abaee2..5e4f1b6 100644
--- a/cmd/namespace/impl.go
+++ b/cmd/namespace/impl.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 .
+
 package main
 
 import (
@@ -18,12 +21,15 @@
 	"v.io/v23/options"
 	"v.io/v23/security/access"
 	"v.io/v23/verror"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 )
 
-func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+func main() {
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline2.Main(cmdRoot)
 }
 
 var (
@@ -33,8 +39,15 @@
 	flagDeleteSubtree       bool
 )
 
-var cmdGlob = &cmdline.Command{
-	Run:      runGlob,
+func init() {
+	cmdGlob.Flags.BoolVar(&flagLongGlob, "l", false, "Long listing format.")
+	cmdResolve.Flags.BoolVar(&flagInsecureResolve, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
+	cmdResolveToMT.Flags.BoolVar(&flagInsecureResolveToMT, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
+	cmdDelete.Flags.BoolVar(&flagDeleteSubtree, "r", false, "Delete all children of the name in addition to the name itself.")
+}
+
+var cmdGlob = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
 	Name:     "glob",
 	Short:    "Returns all matching entries from the namespace",
 	Long:     "Returns all matching entries from the namespace.",
@@ -45,13 +58,13 @@
 `,
 }
 
-func runGlob(cmd *cmdline.Command, args []string) error {
+func runGlob(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	pattern := args[0]
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	ns := v23.GetNamespace(ctx)
@@ -66,14 +79,14 @@
 		for res := range c {
 			switch v := res.(type) {
 			case *naming.GlobReplyEntry:
-				fmt.Fprint(cmd.Stdout(), v.Value.Name)
+				fmt.Fprint(env.Stdout, v.Value.Name)
 				for _, s := range v.Value.Servers {
 					delta := s.Deadline.Time.Sub(time.Now())
-					fmt.Fprintf(cmd.Stdout(), " %s (Expires in %d sec)", s.Server, int(delta.Seconds()))
+					fmt.Fprintf(env.Stdout, " %s (Expires in %d sec)", s.Server, int(delta.Seconds()))
 				}
-				fmt.Fprintln(cmd.Stdout())
+				fmt.Fprintln(env.Stdout)
 			case *naming.GlobReplyError:
-				fmt.Fprintf(cmd.Stderr(), "Error: %s: %v\n", v.Value.Name, v.Value.Error)
+				fmt.Fprintf(env.Stderr, "Error: %s: %v\n", v.Value.Name, v.Value.Error)
 			}
 		}
 		return nil
@@ -97,16 +110,16 @@
 	}
 	sort.Strings(results)
 	for _, result := range results {
-		fmt.Fprintln(cmd.Stdout(), result)
+		fmt.Fprintln(env.Stdout, result)
 	}
 	for _, err := range errors {
-		fmt.Fprintf(cmd.Stderr(), "Error: %s: %v\n", err.Name, err.Error)
+		fmt.Fprintf(env.Stderr, "Error: %s: %v\n", err.Name, err.Error)
 	}
 	return nil
 }
 
-var cmdMount = &cmdline.Command{
-	Run:      runMount,
+var cmdMount = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runMount),
 	Name:     "mount",
 	Short:    "Adds a server to the namespace",
 	Long:     "Adds server <server> to the namespace with name <name>.",
@@ -119,9 +132,9 @@
 `,
 }
 
-func runMount(cmd *cmdline.Command, args []string) error {
+func runMount(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 3, len(args); expected != got {
-		return cmd.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	server := args[1]
@@ -132,7 +145,7 @@
 		return fmt.Errorf("TTL parse error: %v", err)
 	}
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	ns := v23.GetNamespace(ctx)
@@ -140,12 +153,12 @@
 		vlog.Infof("ns.Mount(%q, %q, %s) failed: %v", name, server, ttl, err)
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Server mounted successfully.")
+	fmt.Fprintln(env.Stdout, "Server mounted successfully.")
 	return nil
 }
 
-var cmdUnmount = &cmdline.Command{
-	Run:      runUnmount,
+var cmdUnmount = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runUnmount),
 	Name:     "unmount",
 	Short:    "Removes a server from the namespace",
 	Long:     "Removes server <server> with name <name> from the namespace.",
@@ -156,14 +169,14 @@
 `,
 }
 
-func runUnmount(cmd *cmdline.Command, args []string) error {
+func runUnmount(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	server := args[1]
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	ns := v23.GetNamespace(ctx)
@@ -172,12 +185,12 @@
 		vlog.Infof("ns.Unmount(%q, %q) failed: %v", name, server, err)
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Server unmounted successfully.")
+	fmt.Fprintln(env.Stdout, "Server unmounted successfully.")
 	return nil
 }
 
-var cmdResolve = &cmdline.Command{
-	Run:      runResolve,
+var cmdResolve = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runResolve),
 	Name:     "resolve",
 	Short:    "Translates a object name to its object address(es)",
 	Long:     "Translates a object name to its object address(es).",
@@ -185,13 +198,13 @@
 	ArgsLong: "<name> is the name to resolve.",
 }
 
-func runResolve(cmd *cmdline.Command, args []string) error {
+func runResolve(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	ns := v23.GetNamespace(ctx)
@@ -206,13 +219,13 @@
 		return err
 	}
 	for _, n := range me.Names() {
-		fmt.Fprintln(cmd.Stdout(), n)
+		fmt.Fprintln(env.Stdout, n)
 	}
 	return nil
 }
 
-var cmdResolveToMT = &cmdline.Command{
-	Run:      runResolveToMT,
+var cmdResolveToMT = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runResolveToMT),
 	Name:     "resolvetomt",
 	Short:    "Finds the address of the mounttable that holds an object name",
 	Long:     "Finds the address of the mounttable that holds an object name.",
@@ -220,13 +233,13 @@
 	ArgsLong: "<name> is the name to resolve.",
 }
 
-func runResolveToMT(cmd *cmdline.Command, args []string) error {
+func runResolveToMT(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	ns := v23.GetNamespace(ctx)
@@ -240,12 +253,12 @@
 		return err
 	}
 	for _, s := range e.Servers {
-		fmt.Fprintln(cmd.Stdout(), naming.JoinAddressName(s.Server, e.Name))
+		fmt.Fprintln(env.Stdout, naming.JoinAddressName(s.Server, e.Name))
 	}
 	return nil
 }
 
-var cmdPermissions = &cmdline.Command{
+var cmdPermissions = &cmdline2.Command{
 	Name:  "permissions",
 	Short: "Manipulates permissions on an entry in the namespace",
 	Long: `
@@ -255,13 +268,13 @@
 The permissions are provided as an JSON-encoded version of the Permissions type
 defined in v.io/v23/security/access/types.vdl.
 `,
-	Children: []*cmdline.Command{cmdPermissionsGet, cmdPermissionsSet},
+	Children: []*cmdline2.Command{cmdPermissionsGet, cmdPermissionsSet},
 }
 
-var cmdPermissionsSet = &cmdline.Command{
-	Run:   runPermissionsSet,
-	Name:  "set",
-	Short: "Sets permissions on a mount name",
+var cmdPermissionsSet = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runPermissionsSet),
+	Name:   "set",
+	Short:  "Sets permissions on a mount name",
 	Long: `
 Set replaces the permissions controlling usage of a mount name.
 `,
@@ -274,9 +287,9 @@
 `,
 }
 
-func runPermissionsSet(cmd *cmdline.Command, args []string) error {
+func runPermissionsSet(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("set: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("set: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	var perms access.Permissions
@@ -291,7 +304,7 @@
 	if err := json.NewDecoder(file).Decode(&perms); err != nil {
 		return err
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	ns := v23.GetNamespace(ctx)
 	for {
@@ -307,8 +320,8 @@
 	}
 }
 
-var cmdPermissionsGet = &cmdline.Command{
-	Run:      runPermissionsGet,
+var cmdPermissionsGet = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runPermissionsGet),
 	Name:     "get",
 	Short:    "Gets permissions on a mount name",
 	ArgsName: "<name>",
@@ -323,23 +336,23 @@
 `,
 }
 
-func runPermissionsGet(cmd *cmdline.Command, args []string) error {
+func runPermissionsGet(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	perms, _, err := v23.GetNamespace(ctx).GetPermissions(ctx, name)
 	if err != nil {
 		return err
 	}
-	return json.NewEncoder(cmd.Stdout()).Encode(perms)
+	return json.NewEncoder(env.Stdout).Encode(perms)
 }
 
-var cmdDelete = &cmdline.Command{
-	Run:      runDelete,
+var cmdDelete = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
 	Name:     "delete",
 	Short:    "Deletes a name from the namespace",
 	ArgsName: "<name>",
@@ -347,26 +360,21 @@
 	Long:     "Deletes a name from the namespace.",
 }
 
-func runDelete(cmd *cmdline.Command, args []string) error {
+func runDelete(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 
 	return v23.GetNamespace(ctx).Delete(ctx, name, flagDeleteSubtree)
 }
 
-func root() *cmdline.Command {
-	cmdGlob.Flags.BoolVar(&flagLongGlob, "l", false, "Long listing format.")
-	cmdResolve.Flags.BoolVar(&flagInsecureResolve, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
-	cmdResolveToMT.Flags.BoolVar(&flagInsecureResolveToMT, "insecure", false, "Insecure mode: May return results from untrusted servers and invoke Resolve on untrusted mounttables")
-	cmdDelete.Flags.BoolVar(&flagDeleteSubtree, "r", false, "Delete all children of the name in addition to the name itself.")
-	return &cmdline.Command{
-		Name:  "namespace",
-		Short: "resolves and manages names in the Vanadium namespace",
-		Long: `
+var cmdRoot = &cmdline2.Command{
+	Name:  "namespace",
+	Short: "resolves and manages names in the Vanadium namespace",
+	Long: `
 Command namespace resolves and manages names in the Vanadium namespace.
 
 The namespace roots are set from the command line via --v23.namespace.root
@@ -374,6 +382,5 @@
 with V23_NAMESPACE, e.g.  V23_NAMESPACE, V23_NAMESPACE_2, V23_NAMESPACE_GOOGLE,
 etc.  The command line options override the environment.
 `,
-		Children: []*cmdline.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolve, cmdResolveToMT, cmdPermissions, cmdDelete},
-	}
+	Children: []*cmdline2.Command{cmdGlob, cmdMount, cmdUnmount, cmdResolve, cmdResolveToMT, cmdPermissions, cmdDelete},
 }
diff --git a/cmd/namespace/main.go b/cmd/namespace/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/cmd/namespace/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/cmd/vrpc/doc.go b/cmd/vrpc/doc.go
index 0311b7a..fc183f9 100644
--- a/cmd/vrpc/doc.go
+++ b/cmd/vrpc/doc.go
@@ -139,11 +139,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:
    vrpc help [flags] [command/topic ...]
 
@@ -156,5 +151,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/vrpc/vrpc.go b/cmd/vrpc/vrpc.go
index e540707..0e25698 100644
--- a/cmd/vrpc/vrpc.go
+++ b/cmd/vrpc/vrpc.go
@@ -10,7 +10,6 @@
 import (
 	"fmt"
 	"io"
-	"os"
 	"regexp"
 	"strings"
 	"time"
@@ -23,7 +22,8 @@
 	"v.io/v23/rpc/reserved"
 	"v.io/v23/vdl"
 	"v.io/v23/vdlroot/signature"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/lib/vdl/build"
 	"v.io/x/ref/lib/vdl/codegen/vdlgen"
 	"v.io/x/ref/lib/vdl/compile"
@@ -32,18 +32,13 @@
 )
 
 var (
-	gctx             *context.T
 	flagInsecure     bool
 	flagShowReserved bool
 )
 
 func main() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := cmdVRPC.Main()
-	shutdown()
-	os.Exit(exitCode)
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^v23\.namespace\.root$`))
+	cmdline2.Main(cmdVRPC)
 }
 
 func init() {
@@ -58,7 +53,7 @@
 	cmdSignature.Flags.BoolVar(&flagShowReserved, "show-reserved", false, "if true, also show the signatures of reserved methods")
 }
 
-var cmdVRPC = &cmdline.Command{
+var cmdVRPC = &cmdline2.Command{
 	Name:  "vrpc",
 	Short: "sends and receives Vanadium remote procedure calls",
 	Long: `
@@ -68,7 +63,7 @@
 	// TODO(toddw): Add cmdServe, which will take an interface as input, and set
 	// up a server capable of handling the given methods.  When a request is
 	// received, it'll allow the user to respond via stdin.
-	Children: []*cmdline.Command{cmdSignature, cmdCall, cmdIdentify},
+	Children: []*cmdline2.Command{cmdSignature, cmdCall, cmdIdentify},
 }
 
 const serverDesc = `
@@ -76,10 +71,10 @@
 the server, or an object name that will be resolved to an end-point.
 `
 
-var cmdSignature = &cmdline.Command{
-	Run:   runSignature,
-	Name:  "signature",
-	Short: "Describe the interfaces of a Vanadium server",
+var cmdSignature = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runSignature),
+	Name:   "signature",
+	Short:  "Describe the interfaces of a Vanadium server",
 	Long: `
 Signature connects to the Vanadium server identified by <server>.
 
@@ -93,10 +88,10 @@
 `,
 }
 
-var cmdCall = &cmdline.Command{
-	Run:   runCall,
-	Name:  "call",
-	Short: "Call a method of a Vanadium server",
+var cmdCall = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runCall),
+	Name:   "call",
+	Short:  "Call a method of a Vanadium server",
 	Long: `
 Call connects to the Vanadium server identified by <server> and calls the
 <method> with the given positional [args...], returning results on stdout.
@@ -124,10 +119,10 @@
 `,
 }
 
-var cmdIdentify = &cmdline.Command{
-	Run:   runIdentify,
-	Name:  "identify",
-	Short: "Reveal blessings presented by a Vanadium server",
+var cmdIdentify = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runIdentify),
+	Name:   "identify",
+	Short:  "Reveal blessings presented by a Vanadium server",
 	Long: `
 Identify connects to the Vanadium server identified by <server> and dumps out
 the blessings presented by that server (and the subset of those that are
@@ -137,7 +132,7 @@
 	ArgsLong: serverDesc,
 }
 
-func runSignature(cmd *cmdline.Command, args []string) error {
+func runSignature(ctx *context.T, env *cmdline2.Env, args []string) error {
 	// Error-check args.
 	var server, method string
 	switch len(args) {
@@ -146,11 +141,11 @@
 	case 2:
 		server, method = args[0], args[1]
 	default:
-		return cmd.UsageErrorf("wrong number of arguments")
+		return env.UsageErrorf("wrong number of arguments")
 	}
 	// Get the interface or method signature, and pretty-print.  We print the
 	// named types after the signatures, to aid in readability.
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	var types vdlgen.NamedTypes
 	var opts []rpc.CallOpt
@@ -162,9 +157,9 @@
 		if err != nil {
 			return fmt.Errorf("MethodSignature failed: %v", err)
 		}
-		vdlgen.PrintMethod(cmd.Stdout(), methodSig, &types)
-		fmt.Fprintln(cmd.Stdout())
-		types.Print(cmd.Stdout())
+		vdlgen.PrintMethod(env.Stdout, methodSig, &types)
+		fmt.Fprintln(env.Stdout)
+		types.Print(env.Stdout)
 		return nil
 	}
 	ifacesSig, err := reserved.Signature(ctx, server, opts...)
@@ -176,22 +171,22 @@
 			continue
 		}
 		if i > 0 {
-			fmt.Fprintln(cmd.Stdout())
+			fmt.Fprintln(env.Stdout)
 		}
-		vdlgen.PrintInterface(cmd.Stdout(), iface, &types)
-		fmt.Fprintln(cmd.Stdout())
+		vdlgen.PrintInterface(env.Stdout, iface, &types)
+		fmt.Fprintln(env.Stdout)
 	}
-	types.Print(cmd.Stdout())
+	types.Print(env.Stdout)
 	return nil
 }
 
-func runCall(cmd *cmdline.Command, args []string) error {
+func runCall(ctx *context.T, env *cmdline2.Env, args []string) error {
 	// Error-check args, and set up argsdata with a comma-separated list of
 	// arguments, allowing each individual arg to already be comma-separated.
 	//
 	// TODO(toddw): Should we just space-separate the args instead?
 	if len(args) < 2 {
-		return cmd.UsageErrorf("must specify <server> and <method>")
+		return env.UsageErrorf("must specify <server> and <method>")
 	}
 	server, method := args[0], args[1]
 	var argsdata string
@@ -204,7 +199,7 @@
 		}
 	}
 	// Get the method signature and parse args.
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	methodSig, err := reserved.MethodSignature(ctx, server, method)
 	if err != nil {
@@ -231,7 +226,7 @@
 		case err != nil:
 			return fmt.Errorf("call.Recv failed: %v", err)
 		}
-		fmt.Fprintf(cmd.Stdout(), "<< %v\n", vdlgen.TypedConst(item, "", nil))
+		fmt.Fprintf(env.Stdout, "<< %v\n", vdlgen.TypedConst(item, "", nil))
 	}
 	// Finish the method call.
 	outargs := make([]*vdl.Value, len(methodSig.OutArgs))
@@ -245,11 +240,11 @@
 	// Pretty-print results.
 	for i, arg := range outargs {
 		if i > 0 {
-			fmt.Fprint(cmd.Stdout(), " ")
+			fmt.Fprint(env.Stdout, " ")
 		}
-		fmt.Fprint(cmd.Stdout(), vdlgen.TypedConst(arg, "", nil))
+		fmt.Fprint(env.Stdout, vdlgen.TypedConst(arg, "", nil))
 	}
-	fmt.Fprintln(cmd.Stdout())
+	fmt.Fprintln(env.Stdout)
 	return nil
 }
 
@@ -277,12 +272,12 @@
 	return ret, nil
 }
 
-func runIdentify(cmd *cmdline.Command, args []string) error {
+func runIdentify(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if len(args) != 1 {
-		return cmd.UsageErrorf("wrong number of arguments")
+		return env.UsageErrorf("wrong number of arguments")
 	}
 	server := args[0]
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	var opts []rpc.CallOpt
 	if flagInsecure {
@@ -295,6 +290,6 @@
 		return fmt.Errorf(`client.StartCall(%q, "", nil) failed with %v`, server, err)
 	}
 	valid, presented := call.RemoteBlessings()
-	fmt.Fprintf(cmd.Stdout(), "PRESENTED: %v\nVALID:     %v\nPUBLICKEY: %v\n", presented, valid, presented.PublicKey())
+	fmt.Fprintf(env.Stdout, "PRESENTED: %v\nVALID:     %v\nPUBLICKEY: %v\n", presented, valid, presented.PublicKey())
 	return nil
 }
diff --git a/cmd/vrpc/vrpc_test.go b/cmd/vrpc/vrpc_test.go
index d6aebaa..d8ef7dc 100644
--- a/cmd/vrpc/vrpc_test.go
+++ b/cmd/vrpc/vrpc_test.go
@@ -6,15 +6,17 @@
 
 import (
 	"bytes"
+	"fmt"
 	"strings"
 	"testing"
 
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
-
 	"v.io/x/ref/cmd/vrpc/internal"
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/test"
 )
@@ -114,16 +116,15 @@
 	return nil
 }
 
-func initTest(t *testing.T) (name string, shutdown v23.Shutdown) {
-	// The gctx initialized here is the global context defined in vrpc.go.
-	gctx, shutdown = test.InitForTest()
+func initTest(t *testing.T) (ctx *context.T, name string, shutdown v23.Shutdown) {
+	ctx, shutdown = test.InitForTest()
 
-	rpcServer, err := v23.NewServer(gctx)
+	rpcServer, err := v23.NewServer(ctx)
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
 		return
 	}
-	endpoints, err := rpcServer.Listen(v23.GetListenSpec(gctx))
+	endpoints, err := rpcServer.Listen(v23.GetListenSpec(ctx))
 	if err != nil {
 		t.Fatalf("Listen failed: %v", err)
 		return
@@ -132,22 +133,18 @@
 	obj := internal.TypeTesterServer(&server{})
 	if err := rpcServer.Serve("", obj, nil); err != nil {
 		t.Fatalf("Serve failed: %v", err)
-		return name, shutdown
+		return
 	}
-	return name, shutdown
+	return
 }
 
 func testSignature(t *testing.T, showReserved bool, wantSig string) {
-	name, shutdown := initTest(t)
+	ctx, name, shutdown := initTest(t)
 	defer shutdown()
 	var stdout, stderr bytes.Buffer
-	cmdVRPC.Init(nil, &stdout, &stderr)
-
-	args := []string{"signature", name}
-	// The cmdline package won't reparse the flags sent to Execute, so
-	// instead, set the flag variable directly from here.
-	flagShowReserved = showReserved
-	if err := cmdVRPC.Execute(args); err != nil {
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
+	args := []string{"signature", fmt.Sprintf("-show-reserved=%v", showReserved), name}
+	if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, args); err != nil {
 		t.Fatalf("%s: %v", args, err)
 	}
 
@@ -274,7 +271,7 @@
 }
 
 func TestMethodSignature(t *testing.T) {
-	name, shutdown := initTest(t)
+	ctx, name, shutdown := initTest(t)
 	defer shutdown()
 
 	tests := []struct {
@@ -294,8 +291,8 @@
 	}
 	for _, test := range tests {
 		var stdout, stderr bytes.Buffer
-		cmdVRPC.Init(nil, &stdout, &stderr)
-		if err := cmdVRPC.Execute([]string{"signature", name, test.Method}); err != nil {
+		env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, []string{"signature", name, test.Method}); err != nil {
 			t.Errorf("%q failed: %v", test.Method, err)
 			continue
 		}
@@ -309,7 +306,7 @@
 }
 
 func TestCall(t *testing.T) {
-	name, shutdown := initTest(t)
+	ctx, name, shutdown := initTest(t)
 	defer shutdown()
 
 	tests := []struct {
@@ -337,8 +334,8 @@
 	}
 	for _, test := range tests {
 		var stdout, stderr bytes.Buffer
-		cmdVRPC.Init(nil, &stdout, &stderr)
-		if err := cmdVRPC.Execute([]string{"call", name, test.Method, test.InArgs}); err != nil {
+		env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
+		if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, []string{"call", name, test.Method, test.InArgs}); err != nil {
 			t.Errorf("%q(%s) failed: %v", test.Method, test.InArgs, err)
 			continue
 		}
diff --git a/lib/v23cmd/v23cmd.go b/lib/v23cmd/v23cmd.go
new file mode 100644
index 0000000..cdca697
--- /dev/null
+++ b/lib/v23cmd/v23cmd.go
@@ -0,0 +1,90 @@
+// 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.
+
+// Package v23cmd implements utilities for running v23 cmdline programs.
+//
+// The cmdline package requires a Runner that implements Run(env, args), but for
+// v23 programs we'd like to implement Run(ctx, env, args) instead.  The
+// initialization of the ctx is also tricky, since v23.Init() calls flag.Parse,
+// but the cmdline package needs to call flag.Parse first.
+//
+// The RunnerFunc package-level function allows us to write run functions of the
+// form Run(ctx, env, args), retaining static type-safety, and also getting the
+// flag.Parse ordering right.  In addition the Run and ParseAndRun functions may
+// be called in tests, to pass your own ctx into your command runners.
+package v23cmd
+
+import (
+	"errors"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline2"
+)
+
+var (
+	ErrUnknownRunner = errors.New("v23cmd: unknown runner")
+
+	// The strategy behind this package is this slice, which only grows and never
+	// shrinks.  Here we maintain a mapping between an index number and the
+	// originally registered function, so that we can look the function up in a
+	// typesafe manner.
+	funcs  []func(*context.T, *cmdline2.Env, []string) error
+	initFn = v23.Init
+)
+
+// indexRunner implements cmdline.Runner by looking up the index in the funcs
+// slice to retrieve the originally registered function.
+type indexRunner uint
+
+func (ix indexRunner) Run(env *cmdline2.Env, args []string) error {
+	if int(ix) < len(funcs) {
+		ctx, shutdown := initFn()
+		err := funcs[ix](ctx, env, args)
+		shutdown()
+		return err
+	}
+	return ErrUnknownRunner
+}
+
+// RunnerFunc behaves similarly to cmdline.RunnerFunc, but takes a run function
+// fn that includes a context as the first arg.  The context is created via
+// v23.Init when Run is called on the returned Runner.
+func RunnerFunc(fn func(*context.T, *cmdline2.Env, []string) error) cmdline2.Runner {
+	ix := indexRunner(len(funcs))
+	funcs = append(funcs, fn)
+	return ix
+}
+
+// Lookup returns the function registered via RunnerFunc corresponding to
+// runner, or nil if it doesn't exist.
+func Lookup(runner cmdline2.Runner) func(*context.T, *cmdline2.Env, []string) error {
+	if ix, ok := runner.(indexRunner); ok && int(ix) < len(funcs) {
+		return funcs[ix]
+	}
+	return nil
+}
+
+// Run performs Lookup, and then runs the function with the given ctx, env and
+// args.
+//
+// Doesn't call v23.Init; the context initialization is up to you.
+func Run(runner cmdline2.Runner, ctx *context.T, env *cmdline2.Env, args []string) error {
+	if fn := Lookup(runner); fn != nil {
+		return fn(ctx, env, args)
+	}
+	return ErrUnknownRunner
+}
+
+// ParseAndRun parses the cmd with the given env and args, and calls Run to run
+// the returned runner with the ctx, env and args.
+//
+// Doesn't call v23.Init; the context initialization is up to you.
+func ParseAndRun(cmd *cmdline2.Command, ctx *context.T, env *cmdline2.Env, args []string) error {
+	runner, args, err := cmdline2.Parse(cmd, env, args)
+	if err != nil {
+		return err
+	}
+	return Run(runner, ctx, env, args)
+}
diff --git a/profiles/internal/rpc/stress/mtstress/main.go b/profiles/internal/rpc/stress/mtstress/main.go
index 5c0a8b9..69e82cc 100644
--- a/profiles/internal/rpc/stress/mtstress/main.go
+++ b/profiles/internal/rpc/stress/mtstress/main.go
@@ -24,7 +24,7 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^(rate)|(duration)|(reauthenticate)$`))
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((rate)|(duration)|(reauthenticate))$`))
 }
 
 var (
diff --git a/services/application/application/doc.go b/services/application/application/doc.go
index 9ec0e14..2f6d774 100644
--- a/services/application/application/doc.go
+++ b/services/application/application/doc.go
@@ -109,11 +109,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:
    application help [flags] [command/topic ...]
 
@@ -126,5 +121,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/application/application/impl.go b/services/application/application/impl.go
index 448661e..605eb6b 100644
--- a/services/application/application/impl.go
+++ b/services/application/application/impl.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 .
+
 package main
 
 import (
@@ -17,16 +20,19 @@
 
 	"v.io/v23/context"
 	"v.io/v23/services/application"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/repository"
 )
 
-func init() {
-	cmdline.HideGlobalFlagsExcept()
+func main() {
+	cmdline2.HideGlobalFlagsExcept()
+	cmdline2.Main(cmdRoot)
 }
 
-func getEnvelopeJSON(app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+func getEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string) ([]byte, error) {
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	env, err := app.Match(ctx, strings.Split(profiles, ","))
 	if err != nil {
@@ -39,12 +45,12 @@
 	return j, nil
 }
 
-func putEnvelopeJSON(app repository.ApplicationClientMethods, profiles string, j []byte) error {
+func putEnvelopeJSON(ctx *context.T, app repository.ApplicationClientMethods, profiles string, j []byte) error {
 	var env application.Envelope
 	if err := json.Unmarshal(j, &env); err != nil {
 		return fmt.Errorf("Unmarshal(%v) failed: %v", string(j), err)
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	if err := app.Put(ctx, strings.Split(profiles, ","), env); err != nil {
 		return err
@@ -52,8 +58,8 @@
 	return nil
 }
 
-func promptUser(cmd *cmdline.Command, msg string) string {
-	fmt.Fprint(cmd.Stdout(), msg)
+func promptUser(env *cmdline2.Env, msg string) string {
+	fmt.Fprint(env.Stdout, msg)
 	var answer string
 	if _, err := fmt.Scanf("%s", &answer); err != nil {
 		return ""
@@ -61,8 +67,8 @@
 	return answer
 }
 
-var cmdMatch = &cmdline.Command{
-	Run:      runMatch,
+var cmdMatch = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runMatch),
 	Name:     "match",
 	Short:    "Shows the first matching envelope that matches the given profiles.",
 	Long:     "Shows the first matching envelope that matches the given profiles.",
@@ -72,22 +78,22 @@
 <profiles> is a comma-separated list of profiles.`,
 }
 
-func runMatch(cmd *cmdline.Command, args []string) error {
+func runMatch(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	j, err := getEnvelopeJSON(app, profiles)
+	j, err := getEnvelopeJSON(ctx, app, profiles)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), string(j))
+	fmt.Fprintln(env.Stdout, string(j))
 	return nil
 }
 
-var cmdPut = &cmdline.Command{
-	Run:      runPut,
+var cmdPut = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runPut),
 	Name:     "put",
 	Short:    "Add the given envelope to the application for the given profiles.",
 	Long:     "Add the given envelope to the application for the given profiles.",
@@ -99,9 +105,9 @@
 not provided, the user will be prompted to enter the data manually.`,
 }
 
-func runPut(cmd *cmdline.Command, args []string) error {
+func runPut(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if got := len(args); got != 2 && got != 3 {
-		return cmd.UsageErrorf("put: incorrect number of arguments, expected 2 or 3, got %d", got)
+		return env.UsageErrorf("put: incorrect number of arguments, expected 2 or 3, got %d", got)
 	}
 	name, profiles := args[0], args[1]
 	app := repository.ApplicationClient(name)
@@ -111,25 +117,25 @@
 		if err != nil {
 			return fmt.Errorf("ReadFile(%v): %v", envelope, err)
 		}
-		if err = putEnvelopeJSON(app, profiles, j); err != nil {
+		if err = putEnvelopeJSON(ctx, app, profiles, j); err != nil {
 			return err
 		}
-		fmt.Fprintln(cmd.Stdout(), "Application envelope added successfully.")
+		fmt.Fprintln(env.Stdout, "Application envelope added successfully.")
 		return nil
 	}
-	env := application.Envelope{Args: []string{}, Env: []string{}, Packages: application.Packages{}}
-	j, err := json.MarshalIndent(env, "", "  ")
+	envelope := application.Envelope{Args: []string{}, Env: []string{}, Packages: application.Packages{}}
+	j, err := json.MarshalIndent(envelope, "", "  ")
 	if err != nil {
 		return fmt.Errorf("MarshalIndent() failed: %v", err)
 	}
-	if err := editAndPutEnvelopeJSON(cmd, app, profiles, j); err != nil {
+	if err := editAndPutEnvelopeJSON(ctx, env, app, profiles, j); err != nil {
 		return err
 	}
 	return nil
 }
 
-var cmdRemove = &cmdline.Command{
-	Run:      runRemove,
+var cmdRemove = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
 	Name:     "remove",
 	Short:    "removes the application envelope for the given profile.",
 	Long:     "removes the application envelope for the given profile.",
@@ -139,23 +145,23 @@
 <profile> is a profile.`,
 }
 
-func runRemove(cmd *cmdline.Command, args []string) error {
+func runRemove(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profile := args[0], args[1]
 	app := repository.ApplicationClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	if err := app.Remove(ctx, profile); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Application envelope removed successfully.")
+	fmt.Fprintln(env.Stdout, "Application envelope removed successfully.")
 	return nil
 }
 
-var cmdEdit = &cmdline.Command{
-	Run:      runEdit,
+var cmdEdit = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runEdit),
 	Name:     "edit",
 	Short:    "edits the application envelope for the given profile.",
 	Long:     "edits the application envelope for the given profile.",
@@ -165,24 +171,24 @@
 <profile> is a profile.`,
 }
 
-func runEdit(cmd *cmdline.Command, args []string) error {
+func runEdit(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("edit: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("edit: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profile := args[0], args[1]
 	app := repository.ApplicationClient(name)
 
-	envData, err := getEnvelopeJSON(app, profile)
+	envData, err := getEnvelopeJSON(ctx, app, profile)
 	if err != nil {
 		return err
 	}
-	if err := editAndPutEnvelopeJSON(cmd, app, profile, envData); err != nil {
+	if err := editAndPutEnvelopeJSON(ctx, env, app, profile, envData); err != nil {
 		return err
 	}
 	return nil
 }
 
-func editAndPutEnvelopeJSON(cmd *cmdline.Command, app repository.ApplicationClientMethods, profile string, envData []byte) error {
+func editAndPutEnvelopeJSON(ctx *context.T, env *cmdline2.Env, app repository.ApplicationClientMethods, profile string, envData []byte) error {
 	f, err := ioutil.TempFile("", "application-edit-")
 	if err != nil {
 		return fmt.Errorf("TempFile() failed: %v", err)
@@ -207,36 +213,34 @@
 		}
 		newData, err := ioutil.ReadFile(fileName)
 		if err != nil {
-			fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err)
-			if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
+			fmt.Fprintf(env.Stdout, "Error: %v\n", err)
+			if ans := promptUser(env, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
 				continue
 			}
 			return errors.New("aborted")
 		}
 		if bytes.Compare(envData, newData) == 0 {
-			fmt.Fprintln(cmd.Stdout(), "Nothing changed")
+			fmt.Fprintln(env.Stdout, "Nothing changed")
 			return nil
 		}
-		if err = putEnvelopeJSON(app, profile, newData); err != nil {
-			fmt.Fprintf(cmd.Stdout(), "Error: %v\n", err)
-			if ans := promptUser(cmd, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
+		if err = putEnvelopeJSON(ctx, app, profile, newData); err != nil {
+			fmt.Fprintf(env.Stdout, "Error: %v\n", err)
+			if ans := promptUser(env, "Try again? [y/N] "); strings.ToUpper(ans) == "Y" {
 				continue
 			}
 			return errors.New("aborted")
 		}
 		break
 	}
-	fmt.Fprintln(cmd.Stdout(), "Application envelope updated successfully.")
+	fmt.Fprintln(env.Stdout, "Application envelope updated successfully.")
 	return nil
 }
 
-func root() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "application",
-		Short: "manages the Vanadium application repository",
-		Long: `
+var cmdRoot = &cmdline2.Command{
+	Name:  "application",
+	Short: "manages the Vanadium application repository",
+	Long: `
 Command application manages the Vanadium application repository.
 `,
-		Children: []*cmdline.Command{cmdMatch, cmdPut, cmdRemove, cmdEdit},
-	}
+	Children: []*cmdline2.Command{cmdMatch, cmdPut, cmdRemove, cmdEdit},
 }
diff --git a/services/application/application/impl_test.go b/services/application/application/impl_test.go
index 8c8f0fe..7683569 100644
--- a/services/application/application/impl_test.go
+++ b/services/application/application/impl_test.go
@@ -18,8 +18,9 @@
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/services/application"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
-
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/repository"
 	"v.io/x/ref/test"
@@ -141,24 +142,22 @@
 }
 
 func TestApplicationClient(t *testing.T) {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
-	server, endpoint, err := startServer(t, gctx)
+	server, endpoint, err := startServer(t, ctx)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 	// Setup the command-line.
-	cmd := root()
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "myapp/1")
 	profile := "myprofile"
 
 	// Test the 'Match' command.
-	if err := cmd.Execute([]string{"match", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"match", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := jsonEnv, strings.TrimSpace(stdout.String()); got != expected {
@@ -179,7 +178,7 @@
 	if err = f.Close(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := cmd.Execute([]string{"put", appName, profile, fileName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"put", appName, profile, fileName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope added successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -188,7 +187,7 @@
 	stdout.Reset()
 
 	// Test the 'remove' command.
-	if err := cmd.Execute([]string{"remove", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"remove", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -198,7 +197,7 @@
 
 	// Test the 'edit' command. (nothing changed)
 	os.Setenv("EDITOR", "true")
-	if err := cmd.Execute([]string{"edit", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Nothing changed", strings.TrimSpace(stdout.String()); got != expected {
@@ -208,7 +207,7 @@
 
 	// Test the 'edit' command.
 	os.Setenv("EDITOR", "perl -pi -e 's/arg1/arg111/'")
-	if err := cmd.Execute([]string{"edit", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope updated successfully.", strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/application/application/main.go b/services/application/application/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/services/application/application/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/services/binary/binary/doc.go b/services/binary/binary/doc.go
index 04a4f73..e182c70 100644
--- a/services/binary/binary/doc.go
+++ b/services/binary/binary/doc.go
@@ -110,11 +110,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:
    binary help [flags] [command/topic ...]
 
@@ -127,5 +122,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/binary/binary/impl.go b/services/binary/binary/impl.go
index a86ede8..5c35b8e 100644
--- a/services/binary/binary/impl.go
+++ b/services/binary/binary/impl.go
@@ -2,22 +2,29 @@
 // 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 .
+
 package main
 
 import (
 	"fmt"
 	"os"
 
-	"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"
 	"v.io/x/ref/services/internal/binarylib"
 )
 
-func init() {
-	cmdline.HideGlobalFlagsExcept()
+func main() {
+	cmdline2.HideGlobalFlagsExcept()
+	cmdline2.Main(cmdRoot)
 }
 
-var cmdDelete = &cmdline.Command{
-	Run:      runDelete,
+var cmdDelete = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
 	Name:     "delete",
 	Short:    "Delete a binary",
 	Long:     "Delete connects to the binary repository and deletes the specified binary",
@@ -25,22 +32,22 @@
 	ArgsLong: "<von> is the vanadium object name of the binary to delete",
 }
 
-func runDelete(cmd *cmdline.Command, args []string) error {
+func runDelete(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von := args[0]
-	if err := binarylib.Delete(gctx, von); err != nil {
+	if err := binarylib.Delete(ctx, von); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Binary deleted successfully\n")
+	fmt.Fprintf(env.Stdout, "Binary deleted successfully\n")
 	return nil
 }
 
-var cmdDownload = &cmdline.Command{
-	Run:   runDownload,
-	Name:  "download",
-	Short: "Download a binary",
+var cmdDownload = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runDownload),
+	Name:   "download",
+	Short:  "Download a binary",
 	Long: `
 Download connects to the binary repository, downloads the specified binary, and
 writes it to a file.
@@ -52,22 +59,22 @@
 `,
 }
 
-func runDownload(cmd *cmdline.Command, args []string) error {
+func runDownload(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von, filename := args[0], args[1]
-	if err := binarylib.DownloadToFile(gctx, von, filename); err != nil {
+	if err := binarylib.DownloadToFile(ctx, von, filename); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Binary downloaded to file %s\n", filename)
+	fmt.Fprintf(env.Stdout, "Binary downloaded to file %s\n", filename)
 	return nil
 }
 
-var cmdUpload = &cmdline.Command{
-	Run:   runUpload,
-	Name:  "upload",
-	Short: "Upload a binary or directory archive",
+var cmdUpload = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runUpload),
+	Name:   "upload",
+	Short:  "Upload a binary or directory archive",
 	Long: `
 Upload connects to the binary repository and uploads the binary of the specified
 file or archive of the specified directory. When successful, it writes the name of the new binary to stdout.
@@ -79,9 +86,9 @@
 `,
 }
 
-func runUpload(cmd *cmdline.Command, args []string) error {
+func runUpload(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von, filename := args[0], args[1]
 	fi, err := os.Stat(filename)
@@ -89,23 +96,23 @@
 		return err
 	}
 	if fi.IsDir() {
-		sig, err := binarylib.UploadFromDir(gctx, von, filename)
+		sig, err := binarylib.UploadFromDir(ctx, von, filename)
 		if err != nil {
 			return err
 		}
-		fmt.Fprintf(cmd.Stdout(), "Binary package uploaded from directory %s signature(%v)\n", filename, sig)
+		fmt.Fprintf(env.Stdout, "Binary package uploaded from directory %s signature(%v)\n", filename, sig)
 		return nil
 	}
-	sig, err := binarylib.UploadFromFile(gctx, von, filename)
+	sig, err := binarylib.UploadFromFile(ctx, von, filename)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Binary uploaded from file %s signature(%v)\n", filename, sig)
+	fmt.Fprintf(env.Stdout, "Binary uploaded from file %s signature(%v)\n", filename, sig)
 	return nil
 }
 
-var cmdURL = &cmdline.Command{
-	Run:      runURL,
+var cmdURL = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runURL),
 	Name:     "url",
 	Short:    "Fetch a download URL",
 	Long:     "Connect to the binary repository and fetch the download URL for the given vanadium object name.",
@@ -113,26 +120,24 @@
 	ArgsLong: "<von> is the vanadium object name of the binary repository",
 }
 
-func runURL(cmd *cmdline.Command, args []string) error {
+func runURL(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("rooturl: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("rooturl: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von := args[0]
-	url, _, err := binarylib.DownloadUrl(gctx, von)
+	url, _, err := binarylib.DownloadUrl(ctx, von)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "%v\n", url)
+	fmt.Fprintf(env.Stdout, "%v\n", url)
 	return nil
 }
 
-func root() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "binary",
-		Short: "manages the Vanadium binary repository",
-		Long: `
+var cmdRoot = &cmdline2.Command{
+	Name:  "binary",
+	Short: "manages the Vanadium binary repository",
+	Long: `
 Command binary manages the Vanadium binary repository.
 `,
-		Children: []*cmdline.Command{cmdDelete, cmdDownload, cmdUpload, cmdURL},
-	}
+	Children: []*cmdline2.Command{cmdDelete, cmdDownload, cmdUpload, cmdURL},
 }
diff --git a/services/binary/binary/impl_test.go b/services/binary/binary/impl_test.go
index 2b6c87d..deab3df 100644
--- a/services/binary/binary/impl_test.go
+++ b/services/binary/binary/impl_test.go
@@ -23,8 +23,9 @@
 	"v.io/v23/security/access"
 	"v.io/v23/services/binary"
 	"v.io/v23/services/repository"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
-
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/test"
 )
@@ -126,23 +127,21 @@
 }
 
 func TestBinaryClient(t *testing.T) {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
-	server, endpoint, err := startServer(t, gctx)
+	server, endpoint, err := startServer(t, ctx)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := root()
 	var out bytes.Buffer
-	cmd.Init(nil, &out, &out)
+	env := &cmdline2.Env{Stdout: &out, Stderr: &out}
 
 	// Test the 'delete' command.
-	if err := cmd.Execute([]string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "delete", err, out.String())
 	}
 	if expected, got := "Binary deleted successfully", strings.TrimSpace(out.String()); got != expected {
@@ -158,7 +157,7 @@
 	defer os.RemoveAll(dir)
 	file := path.Join(dir, "testfile")
 	defer os.Remove(file)
-	if err := cmd.Execute([]string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "download", err, out.String())
 	}
 	if expected, got := "Binary downloaded to file "+file, strings.TrimSpace(out.String()); got != expected {
@@ -174,13 +173,13 @@
 	out.Reset()
 
 	// Test the 'upload' command.
-	if err := cmd.Execute([]string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "upload", err, out.String())
 	}
 	out.Reset()
 
 	// Test the 'url' command.
-	if err := cmd.Execute([]string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "url", err, out.String())
 	}
 	if expected, got := "test-download-url", strings.TrimSpace(out.String()); got != expected {
diff --git a/services/binary/binary/main.go b/services/binary/binary/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/services/binary/binary/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/services/build/build/doc.go b/services/build/build/doc.go
index 20bda16..7e1a1c1 100644
--- a/services/build/build/doc.go
+++ b/services/build/build/doc.go
@@ -87,11 +87,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:
    build help [flags] [command/topic ...]
 
@@ -104,5 +99,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/build/build/impl.go b/services/build/build/impl.go
index f9b6e7f..8047aa4 100644
--- a/services/build/build/impl.go
+++ b/services/build/build/impl.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 .
+
 package main
 
 import (
@@ -16,40 +19,41 @@
 
 	"v.io/v23/context"
 	vbuild "v.io/v23/services/build"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 )
 
+func main() {
+	cmdline2.Main(cmdRoot)
+}
+
 var (
 	flagArch string
 	flagOS   string
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept()
+	cmdline2.HideGlobalFlagsExcept()
 	cmdBuild.Flags.StringVar(&flagArch, "arch", runtime.GOARCH, "Target architecture.  The default is the value of runtime.GOARCH.")
 	cmdBuild.Flags.Lookup("arch").DefValue = "<runtime.GOARCH>"
 	cmdBuild.Flags.StringVar(&flagOS, "os", runtime.GOOS, "Target operating system.  The default is the value of runtime.GOOS.")
 	cmdBuild.Flags.Lookup("os").DefValue = "<runtime.GOOS>"
 }
 
-var cmdRoot = &cmdline.Command{
+var cmdRoot = &cmdline2.Command{
 	Name:  "build",
 	Short: "sends commands to a Vanadium build server",
 	Long: `
 Command build sends commands to a Vanadium build server.
 `,
-	Children: []*cmdline.Command{cmdBuild},
+	Children: []*cmdline2.Command{cmdBuild},
 }
 
-// root returns a command that represents the root of the vanadium tool.
-func root() *cmdline.Command {
-	return cmdRoot
-}
-
-var cmdBuild = &cmdline.Command{
-	Run:   runBuild,
-	Name:  "build",
-	Short: "Build vanadium Go packages",
+var cmdBuild = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runBuild),
+	Name:   "build",
+	Short:  "Build vanadium Go packages",
 	Long: `
 Build vanadium Go packages using a remote build server. The command collects all
 source code files that are not part of the Go standard library that the target
@@ -222,7 +226,7 @@
 // concurrently 1) reads the source files, 2) sends them to the build
 // server and receives binaries from the build server, and 3) writes
 // the binaries out to the disk.
-func runBuild(command *cmdline.Command, args []string) error {
+func runBuild(ctx *context.T, env *cmdline2.Env, args []string) error {
 	name, paths := args[0], args[1:]
 	pkgMap := map[string]*build.Package{}
 	if err := importPackages(paths, pkgMap); err != nil {
@@ -231,7 +235,7 @@
 	errchan := make(chan error)
 	defer close(errchan)
 
-	ctx, ctxCancel := context.WithTimeout(gctx, time.Minute)
+	ctx, ctxCancel := context.WithTimeout(ctx, time.Minute)
 	defer ctxCancel()
 
 	// Start all stages of the pipeline.
diff --git a/services/build/build/impl_test.go b/services/build/build/impl_test.go
index a4a71ca..6f35178 100644
--- a/services/build/build/impl_test.go
+++ b/services/build/build/impl_test.go
@@ -16,8 +16,9 @@
 	"v.io/v23/services/binary"
 	"v.io/v23/services/build"
 	"v.io/v23/verror"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
-
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/test"
 )
@@ -69,22 +70,19 @@
 }
 
 func TestBuildClient(t *testing.T) {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
-	server, endpoint := startServer(gctx, t)
+	server, endpoint := startServer(ctx, t)
 	defer stopServer(t, server)
 
-	cmd := root()
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
-
-	// Test the 'Build' command.
-	if err := cmd.Execute([]string{"build", naming.JoinAddressName(endpoint.String(), ""), "v.io/x/ref/services/build/build"}); err != nil {
-		t.Fatalf("%v", err)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
+	args := []string{"build", naming.JoinAddressName(endpoint.String(), ""), "v.io/x/ref/services/build/build"}
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, args); err != nil {
+		t.Fatalf("Run failed: %v", err)
 	}
-	if expected, got := "", strings.TrimSpace(stdout.String()); got != expected {
-		t.Errorf("Unexpected output from build: got %q, expected %q", got, expected)
+	if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
+		t.Errorf("got %q, want %q", got, want)
 	}
 }
diff --git a/services/build/build/main.go b/services/build/build/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/services/build/build/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/services/debug/debug/doc.go b/services/debug/debug/doc.go
index ad9e01e..d291f34 100644
--- a/services/debug/debug/doc.go
+++ b/services/debug/debug/doc.go
@@ -208,11 +208,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:
    debug help [flags] [command/topic ...]
 
@@ -225,5 +220,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/debug/debug/impl.go b/services/debug/debug/impl.go
index 464faf9..b0b5793 100644
--- a/services/debug/debug/impl.go
+++ b/services/debug/debug/impl.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 .
+
 package main
 
 import (
@@ -25,12 +28,18 @@
 	"v.io/v23/uniqueid"
 	"v.io/v23/vdl"
 	"v.io/v23/vtrace"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/ref/lib/glob"
 	"v.io/x/ref/lib/signals"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/internal/pproflib"
 )
 
+func main() {
+	cmdline2.Main(cmdRoot)
+}
+
 var (
 	follow     bool
 	verbose    bool
@@ -42,7 +51,7 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept()
+	cmdline2.HideGlobalFlagsExcept()
 
 	// logs read flags
 	cmdLogsRead.Flags.BoolVar(&follow, "f", false, "When true, read will wait for new log entries when it reaches the end of the file.")
@@ -62,8 +71,8 @@
 	cmdPProfRun.Flags.StringVar(&pprofCmd, "pprofcmd", "v23 go tool pprof", "The pprof command to use.")
 }
 
-var cmdVtrace = &cmdline.Command{
-	Run:      runVtrace,
+var cmdVtrace = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runVtrace),
 	Name:     "vtrace",
 	Short:    "Returns vtrace traces.",
 	Long:     "Returns matching vtrace traces (or all stored traces if no ids are given).",
@@ -86,16 +95,16 @@
 	}
 }
 
-func runVtrace(cmd *cmdline.Command, args []string) error {
+func runVtrace(ctx *context.T, env *cmdline2.Env, args []string) error {
 	arglen := len(args)
 	if arglen == 0 {
-		return cmd.UsageErrorf("vtrace: incorrect number of arguments, got %d want >= 1", arglen)
+		return env.UsageErrorf("vtrace: incorrect number of arguments, got %d want >= 1", arglen)
 	}
 
 	name := args[0]
 	client := s_vtrace.StoreClient(name)
 	if arglen == 1 {
-		call, err := client.AllTraces(gctx)
+		call, err := client.AllTraces(ctx)
 		if err != nil {
 			return err
 		}
@@ -120,7 +129,7 @@
 		if err != nil {
 			return err
 		}
-		go doFetchTrace(gctx, &wg, client, id, traces, errors)
+		go doFetchTrace(ctx, &wg, client, id, traces, errors)
 	}
 	go func() {
 		wg.Wait()
@@ -136,8 +145,8 @@
 	return <-errors
 }
 
-var cmdGlob = &cmdline.Command{
-	Run:      runGlob,
+var cmdGlob = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runGlob),
 	Name:     "glob",
 	Short:    "Returns all matching entries from the namespace.",
 	Long:     "Returns all matching entries from the namespace.",
@@ -147,32 +156,32 @@
 `,
 }
 
-func runGlob(cmd *cmdline.Command, args []string) error {
+func runGlob(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if min, got := 1, len(args); got < min {
-		return cmd.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min)
+		return env.UsageErrorf("glob: incorrect number of arguments, got %d, want >=%d", got, min)
 	}
 	results := make(chan naming.GlobReply)
 	errors := make(chan error)
-	doGlobs(gctx, args, results, errors)
+	doGlobs(ctx, args, results, errors)
 	var lastErr error
 	for {
 		select {
 		case err := <-errors:
 			lastErr = err
-			fmt.Fprintln(cmd.Stderr(), "Error:", err)
+			fmt.Fprintln(env.Stderr, "Error:", err)
 		case me, ok := <-results:
 			if !ok {
 				return lastErr
 			}
 			switch v := me.(type) {
 			case *naming.GlobReplyEntry:
-				fmt.Fprint(cmd.Stdout(), v.Value.Name)
+				fmt.Fprint(env.Stdout, v.Value.Name)
 				for _, s := range v.Value.Servers {
-					fmt.Fprintf(cmd.Stdout(), " %s (Deadline %s)", s.Server, s.Deadline.Time)
+					fmt.Fprintf(env.Stdout, " %s (Deadline %s)", s.Server, s.Deadline.Time)
 				}
-				fmt.Fprintln(cmd.Stdout())
+				fmt.Fprintln(env.Stdout)
 			case *naming.GlobReplyError:
-				fmt.Fprintf(cmd.Stderr(), "Error: %s: %v\n", v.Value.Name, v.Value.Error)
+				fmt.Fprintf(env.Stderr, "Error: %s: %v\n", v.Value.Name, v.Value.Error)
 			}
 		}
 	}
@@ -207,8 +216,8 @@
 	}
 }
 
-var cmdLogsRead = &cmdline.Command{
-	Run:      runLogsRead,
+var cmdLogsRead = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runLogsRead),
 	Name:     "read",
 	Short:    "Reads the content of a log file object.",
 	Long:     "Reads the content of a log file object.",
@@ -218,13 +227,13 @@
 `,
 }
 
-func runLogsRead(cmd *cmdline.Command, args []string) error {
+func runLogsRead(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if want, got := 1, len(args); want != got {
-		return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want %d", got, want)
+		return env.UsageErrorf("read: incorrect number of arguments, got %d, want %d", got, want)
 	}
 	name := args[0]
 	lf := logreader.LogFileClient(name)
-	stream, err := lf.ReadLog(gctx, startPos, int32(numEntries), follow)
+	stream, err := lf.ReadLog(ctx, startPos, int32(numEntries), follow)
 	if err != nil {
 		return err
 	}
@@ -232,9 +241,9 @@
 	for iterator.Advance() {
 		entry := iterator.Value()
 		if verbose {
-			fmt.Fprintf(cmd.Stdout(), "[%d] %s\n", entry.Position, entry.Line)
+			fmt.Fprintf(env.Stdout, "[%d] %s\n", entry.Position, entry.Line)
 		} else {
-			fmt.Fprintf(cmd.Stdout(), "%s\n", entry.Line)
+			fmt.Fprintf(env.Stdout, "%s\n", entry.Line)
 		}
 	}
 	if err = iterator.Err(); err != nil {
@@ -245,13 +254,13 @@
 		return err
 	}
 	if verbose {
-		fmt.Fprintf(cmd.Stdout(), "Offset: %d\n", offset)
+		fmt.Fprintf(env.Stdout, "Offset: %d\n", offset)
 	}
 	return nil
 }
 
-var cmdLogsSize = &cmdline.Command{
-	Run:      runLogsSize,
+var cmdLogsSize = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runLogsSize),
 	Name:     "size",
 	Short:    "Returns the size of a log file object.",
 	Long:     "Returns the size of a log file object.",
@@ -261,22 +270,22 @@
 `,
 }
 
-func runLogsSize(cmd *cmdline.Command, args []string) error {
+func runLogsSize(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if want, got := 1, len(args); want != got {
-		return cmd.UsageErrorf("size: incorrect number of arguments, got %d, want %d", got, want)
+		return env.UsageErrorf("size: incorrect number of arguments, got %d, want %d", got, want)
 	}
 	name := args[0]
 	lf := logreader.LogFileClient(name)
-	size, err := lf.Size(gctx)
+	size, err := lf.Size(ctx)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), size)
+	fmt.Fprintln(env.Stdout, size)
 	return nil
 }
 
-var cmdStatsRead = &cmdline.Command{
-	Run:      runStatsRead,
+var cmdStatsRead = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStatsRead),
 	Name:     "read",
 	Short:    "Returns the value of stats objects.",
 	Long:     "Returns the value of stats objects.",
@@ -287,13 +296,13 @@
 `,
 }
 
-func runStatsRead(cmd *cmdline.Command, args []string) error {
+func runStatsRead(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if min, got := 1, len(args); got < min {
-		return cmd.UsageErrorf("read: incorrect number of arguments, got %d, want >=%d", got, min)
+		return env.UsageErrorf("read: incorrect number of arguments, got %d, want >=%d", got, min)
 	}
 	globResults := make(chan naming.GlobReply)
 	errors := make(chan error)
-	doGlobs(gctx, args, globResults, errors)
+	doGlobs(ctx, args, globResults, errors)
 
 	output := make(chan string)
 	go func() {
@@ -302,7 +311,7 @@
 			switch v := me.(type) {
 			case *naming.GlobReplyEntry:
 				wg.Add(1)
-				go doValue(gctx, v.Value.Name, output, errors, &wg)
+				go doValue(ctx, v.Value.Name, output, errors, &wg)
 			}
 		}
 		wg.Wait()
@@ -314,12 +323,12 @@
 		select {
 		case err := <-errors:
 			lastErr = err
-			fmt.Fprintln(cmd.Stderr(), err)
+			fmt.Fprintln(env.Stderr, err)
 		case out, ok := <-output:
 			if !ok {
 				return lastErr
 			}
-			fmt.Fprintln(cmd.Stdout(), out)
+			fmt.Fprintln(env.Stdout, out)
 		}
 	}
 }
@@ -341,8 +350,8 @@
 	output <- fmt.Sprintf("%s: %v", name, fv)
 }
 
-var cmdStatsWatch = &cmdline.Command{
-	Run:      runStatsWatch,
+var cmdStatsWatch = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStatsWatch),
 	Name:     "watch",
 	Short:    "Returns a stream of all matching entries and their values as they change.",
 	Long:     "Returns a stream of all matching entries and their values as they change.",
@@ -352,9 +361,9 @@
 `,
 }
 
-func runStatsWatch(cmd *cmdline.Command, args []string) error {
+func runStatsWatch(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if want, got := 1, len(args); got < want {
-		return cmd.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want)
+		return env.UsageErrorf("watch: incorrect number of arguments, got %d, want >=%d", got, want)
 	}
 
 	results := make(chan string)
@@ -362,7 +371,7 @@
 	var wg sync.WaitGroup
 	wg.Add(len(args))
 	for _, arg := range args {
-		go doWatch(gctx, arg, results, errors, &wg)
+		go doWatch(ctx, arg, results, errors, &wg)
 	}
 	go func() {
 		wg.Wait()
@@ -373,12 +382,12 @@
 		select {
 		case err := <-errors:
 			lastErr = err
-			fmt.Fprintln(cmd.Stderr(), "Error:", err)
+			fmt.Fprintln(env.Stderr, "Error:", err)
 		case r, ok := <-results:
 			if !ok {
 				return lastErr
 			}
-			fmt.Fprintln(cmd.Stdout(), r)
+			fmt.Fprintln(env.Stdout, r)
 		}
 	}
 }
@@ -447,8 +456,8 @@
 	return ret + fmt.Sprint(pretty), err
 }
 
-var cmdPProfRun = &cmdline.Command{
-	Run:      runPProf,
+var cmdPProfRun = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runPProf),
 	Name:     "run",
 	Short:    "Runs the pprof tool.",
 	Long:     "Runs the pprof tool.",
@@ -464,16 +473,16 @@
 `,
 }
 
-func runPProf(cmd *cmdline.Command, args []string) error {
+func runPProf(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if min, got := 1, len(args); got < min {
-		return cmd.UsageErrorf("pprof: incorrect number of arguments, got %d, want >=%d", got, min)
+		return env.UsageErrorf("pprof: incorrect number of arguments, got %d, want >=%d", got, min)
 	}
 	name := args[0]
 	if len(args) == 1 {
-		return showPProfProfiles(cmd, name)
+		return showPProfProfiles(ctx, env, name)
 	}
 	profile := args[1]
-	listener, err := pproflib.StartProxy(gctx, name)
+	listener, err := pproflib.StartProxy(ctx, name)
 	if err != nil {
 		return err
 	}
@@ -487,24 +496,24 @@
 	}
 	pargs = append(pargs, shellEscape(fmt.Sprintf("http://%s/pprof/%s", listener.Addr(), profile)))
 	pcmd := strings.Join(pargs, " ")
-	fmt.Fprintf(cmd.Stdout(), "Running: %s\n", pcmd)
+	fmt.Fprintf(env.Stdout, "Running: %s\n", pcmd)
 	c := exec.Command("sh", "-c", pcmd)
 	c.Stdin = os.Stdin
-	c.Stdout = cmd.Stdout()
-	c.Stderr = cmd.Stderr()
+	c.Stdout = env.Stdout
+	c.Stderr = env.Stderr
 	return c.Run()
 }
 
-func showPProfProfiles(cmd *cmdline.Command, name string) error {
-	v, err := pprof.PProfClient(name).Profiles(gctx)
+func showPProfProfiles(ctx *context.T, env *cmdline2.Env, name string) error {
+	v, err := pprof.PProfClient(name).Profiles(ctx)
 	if err != nil {
 		return err
 	}
 	v = append(v, "profile")
 	sort.Strings(v)
-	fmt.Fprintln(cmd.Stdout(), "Available profiles:")
+	fmt.Fprintln(env.Stdout, "Available profiles:")
 	for _, p := range v {
-		fmt.Fprintf(cmd.Stdout(), "  %s\n", p)
+		fmt.Fprintf(env.Stdout, "  %s\n", p)
 	}
 	return nil
 }
@@ -517,8 +526,8 @@
 	return `"` + re.ReplaceAllString(s, "\\$1") + `"`
 }
 
-var cmdPProfRunProxy = &cmdline.Command{
-	Run:      runPProfProxy,
+var cmdPProfRunProxy = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runPProfProxy),
 	Name:     "proxy",
 	Short:    "Runs an http proxy to a pprof object.",
 	Long:     "Runs an http proxy to a pprof object.",
@@ -528,54 +537,50 @@
 `,
 }
 
-func runPProfProxy(cmd *cmdline.Command, args []string) error {
+func runPProfProxy(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if want, got := 1, len(args); got != want {
-		return cmd.UsageErrorf("proxy: incorrect number of arguments, got %d, want %d", got, want)
+		return env.UsageErrorf("proxy: incorrect number of arguments, got %d, want %d", got, want)
 	}
 	name := args[0]
-	listener, err := pproflib.StartProxy(gctx, name)
+	listener, err := pproflib.StartProxy(ctx, name)
 	if err != nil {
 		return err
 	}
 	defer listener.Close()
 
-	fmt.Fprintln(cmd.Stdout())
-	fmt.Fprintf(cmd.Stdout(), "The pprof proxy is listening at http://%s/pprof\n", listener.Addr())
-	fmt.Fprintln(cmd.Stdout())
-	fmt.Fprintln(cmd.Stdout(), "Hit CTRL-C to exit")
+	fmt.Fprintln(env.Stdout)
+	fmt.Fprintf(env.Stdout, "The pprof proxy is listening at http://%s/pprof\n", listener.Addr())
+	fmt.Fprintln(env.Stdout)
+	fmt.Fprintln(env.Stdout, "Hit CTRL-C to exit")
 
-	<-signals.ShutdownOnSignals(gctx)
+	<-signals.ShutdownOnSignals(ctx)
 	return nil
 }
 
-var cmdRoot = cmdline.Command{
+var cmdRoot = &cmdline2.Command{
 	Name:  "debug",
 	Short: "supports debugging Vanadium servers.",
 	Long:  "Command debug supports debugging Vanadium servers.",
-	Children: []*cmdline.Command{
+	Children: []*cmdline2.Command{
 		cmdGlob,
 		cmdVtrace,
-		&cmdline.Command{
+		&cmdline2.Command{
 			Name:     "logs",
 			Short:    "Accesses log files",
 			Long:     "Accesses log files",
-			Children: []*cmdline.Command{cmdLogsRead, cmdLogsSize},
+			Children: []*cmdline2.Command{cmdLogsRead, cmdLogsSize},
 		},
-		&cmdline.Command{
+		&cmdline2.Command{
 			Name:     "stats",
 			Short:    "Accesses stats",
 			Long:     "Accesses stats",
-			Children: []*cmdline.Command{cmdStatsRead, cmdStatsWatch},
+			Children: []*cmdline2.Command{cmdStatsRead, cmdStatsWatch},
 		},
-		&cmdline.Command{
+		&cmdline2.Command{
 			Name:     "pprof",
 			Short:    "Accesses profiling data",
 			Long:     "Accesses profiling data",
-			Children: []*cmdline.Command{cmdPProfRun, cmdPProfRunProxy},
+			Children: []*cmdline2.Command{cmdPProfRun, cmdPProfRunProxy},
 		},
 	},
 }
-
-func root() *cmdline.Command {
-	return &cmdRoot
-}
diff --git a/services/debug/debug/main.go b/services/debug/debug/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/services/debug/debug/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/services/device/device/acl_impl.go b/services/device/device/acl_impl.go
index 9e0c940..09d1073 100644
--- a/services/device/device/acl_impl.go
+++ b/services/device/device/acl_impl.go
@@ -9,15 +9,17 @@
 import (
 	"fmt"
 
+	"v.io/v23/context"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/services/device"
 	"v.io/v23/verror"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 )
 
-var cmdGet = &cmdline.Command{
-	Run:      runGet,
+var cmdGet = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runGet),
 	Name:     "get",
 	Short:    "Get Permissions for the given target.",
 	Long:     "Get Permissions for the given target.",
@@ -27,13 +29,13 @@
 application installation or instance.`,
 }
 
-func runGet(cmd *cmdline.Command, args []string) error {
+func runGet(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("get: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 
 	vanaName := args[0]
-	objPerms, _, err := device.ApplicationClient(vanaName).GetPermissions(gctx)
+	objPerms, _, err := device.ApplicationClient(vanaName).GetPermissions(ctx)
 	if err != nil {
 		return fmt.Errorf("GetPermissions on %s failed: %v", vanaName, err)
 	}
@@ -47,15 +49,15 @@
 			entries.Tags(b)[tag] = true
 		}
 	}
-	fmt.Fprintln(cmd.Stdout(), entries)
+	fmt.Fprintln(env.Stdout, entries)
 	return nil
 }
 
 // TODO(caprita): Add unit test logic for 'force set'.
 var forceSet bool
 
-var cmdSet = &cmdline.Command{
-	Run:      runSet,
+var cmdSet = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runSet),
 	Name:     "set",
 	Short:    "Set Permissions for the given target.",
 	Long:     "Set Permissions for the given target",
@@ -87,9 +89,9 @@
 	cmdSet.Flags.BoolVar(&forceSet, "f", false, "Instead of making the AccessLists additive, do a complete replacement based on the specified settings.")
 }
 
-func runSet(cmd *cmdline.Command, args []string) error {
+func runSet(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if got := len(args); !((got%2) == 1 && got >= 3) {
-		return cmd.UsageErrorf("set: incorrect number of arguments %d, must be 1 + 2n", got)
+		return env.UsageErrorf("set: incorrect number of arguments %d, must be 1 + 2n", got)
 	}
 
 	vanaName := args[0]
@@ -100,7 +102,7 @@
 		blessing := pairs[i]
 		tags, err := parseAccessTags(pairs[i+1])
 		if err != nil {
-			return cmd.UsageErrorf("failed to parse access tags for %q: %v", blessing, err)
+			return env.UsageErrorf("failed to parse access tags for %q: %v", blessing, err)
 		}
 		entries[blessing] = tags
 	}
@@ -110,7 +112,7 @@
 		objPerms, version := make(access.Permissions), ""
 		if !forceSet {
 			var err error
-			if objPerms, version, err = device.ApplicationClient(vanaName).GetPermissions(gctx); err != nil {
+			if objPerms, version, err = device.ApplicationClient(vanaName).GetPermissions(ctx); err != nil {
 				return fmt.Errorf("GetPermissions(%s) failed: %v", vanaName, err)
 			}
 		}
@@ -124,24 +126,22 @@
 				}
 			}
 		}
-		switch err := device.ApplicationClient(vanaName).SetPermissions(gctx, objPerms, version); {
+		switch err := device.ApplicationClient(vanaName).SetPermissions(ctx, objPerms, version); {
 		case err != nil && verror.ErrorID(err) != verror.ErrBadVersion.ID:
 			return fmt.Errorf("SetPermissions(%s) failed: %v", vanaName, err)
 		case err == nil:
 			return nil
 		}
-		fmt.Fprintln(cmd.Stderr(), "WARNING: trying again because of asynchronous change")
+		fmt.Fprintln(env.Stderr, "WARNING: trying again because of asynchronous change")
 	}
 	return nil
 }
 
-func aclRoot() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "acl",
-		Short: "Tool for setting device manager Permissions",
-		Long: `
+var cmdACL = &cmdline2.Command{
+	Name:  "acl",
+	Short: "Tool for setting device manager Permissions",
+	Long: `
 The acl tool manages Permissions on the device manger, installations and instances.
 `,
-		Children: []*cmdline.Command{cmdGet, cmdSet},
-	}
+	Children: []*cmdline2.Command{cmdGet, cmdSet},
 }
diff --git a/services/device/device/acl_test.go b/services/device/device/acl_test.go
index bf51feb..3cc5988 100644
--- a/services/device/device/acl_test.go
+++ b/services/device/device/acl_test.go
@@ -14,6 +14,9 @@
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/verror"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
 )
@@ -25,20 +28,20 @@
 )
 
 func TestAccessListGetCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := endpoint.Name()
 
 	// Test the 'get' command.
@@ -57,7 +60,7 @@
 		err:     nil,
 	}})
 
-	if err := cmd.Execute([]string{"acl", "get", deviceName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "get", deviceName}); err != nil {
 		t.Fatalf("error: %v", err)
 	}
 	if expected, got := strings.TrimSpace(`
@@ -73,24 +76,24 @@
 }
 
 func TestAccessListSetCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := endpoint.Name()
 
 	// Some tests to validate parse.
-	if err := cmd.Execute([]string{"acl", "set", deviceName}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 1, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -99,7 +102,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := cmd.Execute([]string{"acl", "set", deviceName, "foo"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo"}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 2, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -108,7 +111,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := cmd.Execute([]string{"acl", "set", deviceName, "foo", "bar", "ohno"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "bar", "ohno"}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 4, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -117,7 +120,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := cmd.Execute([]string{"acl", "set", deviceName, "foo", "!"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "!"}); err == nil {
 		t.Fatalf("failed to detect invalid parameter")
 	}
 	if expected, got := "ERROR: failed to parse access tags for \"foo\": empty access tag", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -163,7 +166,7 @@
 	// - Adds a blacklist entry for "friend/alice"  for "Admin"
 	// - Edits existing entry for "self" (adding "Write" access)
 	// - Removes entry for "other/bob/baddevice"
-	if err := cmd.Execute([]string{
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{
 		"acl",
 		"set",
 		deviceName,
@@ -237,7 +240,7 @@
 	},
 	})
 
-	if err := cmd.Execute([]string{"acl", "set", deviceName, "vana/bad", "Read"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "vana/bad", "Read"}); err == nil {
 		t.Fatalf("GetPermissions RPC inside perms set command failed but error wrongly not detected")
 	} else if expected, got := `^GetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
 		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
@@ -269,7 +272,7 @@
 		verror.New(errOops, nil),
 	})
 
-	if err := cmd.Execute([]string{"acl", "set", deviceName, "friend", "Read"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "friend", "Read"}); err == nil {
 		t.Fatalf("SetPermissions should have failed: %v", err)
 	} else if expected, got := `^SetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
 		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
diff --git a/services/device/device/associate_impl.go b/services/device/device/associate_impl.go
index 45a8a0c..6c1e634 100644
--- a/services/device/device/associate_impl.go
+++ b/services/device/device/associate_impl.go
@@ -10,11 +10,12 @@
 
 	"v.io/v23/context"
 	"v.io/v23/services/device"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 )
 
-var cmdList = &cmdline.Command{
-	Run:      runList,
+var cmdList = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runList),
 	Name:     "list",
 	Short:    "Lists the account associations.",
 	Long:     "Lists all account associations.",
@@ -23,12 +24,12 @@
 <devicemanager> is the name of the device manager to connect to.`,
 }
 
-func runList(cmd *cmdline.Command, args []string) error {
+func runList(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("list: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("list: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	assocs, err := device.DeviceClient(args[0]).ListAssociations(ctx)
 	if err != nil {
@@ -36,13 +37,13 @@
 	}
 
 	for _, a := range assocs {
-		fmt.Fprintf(cmd.Stdout(), "%s %s\n", a.IdentityName, a.AccountName)
+		fmt.Fprintf(env.Stdout, "%s %s\n", a.IdentityName, a.AccountName)
 	}
 	return nil
 }
 
-var cmdAdd = &cmdline.Command{
-	Run:      runAdd,
+var cmdAdd = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runAdd),
 	Name:     "add",
 	Short:    "Add the listed blessings with the specified system account.",
 	Long:     "Add the listed blessings with the specified system account.",
@@ -53,17 +54,17 @@
 <blessing>.. are the blessings to associate systemAccount with.`,
 }
 
-func runAdd(cmd *cmdline.Command, args []string) error {
+func runAdd(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 3, len(args); got < expected {
-		return cmd.UsageErrorf("add: incorrect number of arguments, expected at least %d, got %d", expected, got)
+		return env.UsageErrorf("add: incorrect number of arguments, expected at least %d, got %d", expected, got)
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	return device.DeviceClient(args[0]).AssociateAccount(ctx, args[2:], args[1])
 }
 
-var cmdRemove = &cmdline.Command{
-	Run:      runRemove,
+var cmdRemove = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
 	Name:     "remove",
 	Short:    "Removes system accounts associated with the listed blessings.",
 	Long:     "Removes system accounts associated with the listed blessings.",
@@ -73,22 +74,20 @@
 <blessing>... is a list of blessings.`,
 }
 
-func runRemove(cmd *cmdline.Command, args []string) error {
+func runRemove(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); got < expected {
-		return cmd.UsageErrorf("remove: incorrect number of arguments, expected at least %d, got %d", expected, got)
+		return env.UsageErrorf("remove: incorrect number of arguments, expected at least %d, got %d", expected, got)
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	return device.DeviceClient(args[0]).AssociateAccount(ctx, args[1:], "")
 }
 
-func associateRoot() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "associate",
-		Short: "Tool for creating associations between Vanadium blessings and a system account",
-		Long: `
+var cmdAssociate = &cmdline2.Command{
+	Name:  "associate",
+	Short: "Tool for creating associations between Vanadium blessings and a system account",
+	Long: `
 The associate tool facilitates managing blessing to system account associations.
 `,
-		Children: []*cmdline.Command{cmdList, cmdAdd, cmdRemove},
-	}
+	Children: []*cmdline2.Command{cmdList, cmdAdd, cmdRemove},
 }
diff --git a/services/device/device/impl.go b/services/device/device/impl.go
index f6be88a..6ebcc63 100644
--- a/services/device/device/impl.go
+++ b/services/device/device/impl.go
@@ -17,7 +17,8 @@
 	"v.io/v23/security"
 	"v.io/v23/services/application"
 	"v.io/v23/services/device"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 )
 
 type configFlag device.Config
@@ -55,8 +56,8 @@
 	cmdInstall.Flags.Var(&packagesOverride, "packages", "JSON-encoded application.Packages object, of the form: '{\"pkg1\":{\"File\":\"object name 1\"},\"pkg2\":{\"File\":\"object name 2\"}}'")
 }
 
-var cmdInstall = &cmdline.Command{
-	Run:      runInstall,
+var cmdInstall = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runInstall),
 	Name:     "install",
 	Short:    "Install the given application.",
 	Long:     "Install the given application and print the name of the new installation.",
@@ -68,12 +69,12 @@
 `,
 }
 
-func runInstall(cmd *cmdline.Command, args []string) error {
+func runInstall(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	deviceName, appName := args[0], args[1]
-	appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), application.Packages(packagesOverride))
+	appID, err := device.ApplicationClient(deviceName).Install(ctx, appName, device.Config(configOverride), application.Packages(packagesOverride))
 	// Reset the value for any future invocations of "install" or
 	// "install-local" (we run more than one command per process in unit
 	// tests).
@@ -82,12 +83,12 @@
 	if err != nil {
 		return fmt.Errorf("Install failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "%s\n", naming.Join(deviceName, appID))
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(deviceName, appID))
 	return nil
 }
 
-var cmdUninstall = &cmdline.Command{
-	Run:      runUninstall,
+var cmdUninstall = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runUninstall),
 	Name:     "uninstall",
 	Short:    "Uninstall the given application installation.",
 	Long:     "Uninstall the given application installation.",
@@ -98,20 +99,20 @@
 `,
 }
 
-func runUninstall(cmd *cmdline.Command, args []string) error {
+func runUninstall(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("uninstall: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("uninstall: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	installName := args[0]
-	if err := device.ApplicationClient(installName).Uninstall(gctx); err != nil {
+	if err := device.ApplicationClient(installName).Uninstall(ctx); err != nil {
 		return fmt.Errorf("Uninstall failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "Successfully uninstalled: %q\n", installName)
+	fmt.Fprintf(env.Stdout, "Successfully uninstalled: %q\n", installName)
 	return nil
 }
 
-var cmdInstantiate = &cmdline.Command{
-	Run:      runInstantiate,
+var cmdInstantiate = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runInstantiate),
 	Name:     "instantiate",
 	Short:    "Create an instance of the given application.",
 	Long:     "Create an instance of the given application, provide it with a blessing, and print the name of the new instance.",
@@ -134,13 +135,13 @@
 	return p.Bless(call.RemoteBlessings().PublicKey(), p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
 }
 
-func runInstantiate(cmd *cmdline.Command, args []string) error {
+func runInstantiate(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("instantiate: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("instantiate: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appInstallation, grant := args[0], args[1]
 
-	ctx, cancel := context.WithCancel(gctx)
+	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 	principal := v23.GetPrincipal(ctx)
 
@@ -162,19 +163,19 @@
 			}
 			call.SendStream().Send(device.BlessClientMessageAppBlessings{blessings})
 		default:
-			fmt.Fprintf(cmd.Stderr(), "Received unexpected message: %#v\n", msg)
+			fmt.Fprintf(env.Stderr, "Received unexpected message: %#v\n", msg)
 		}
 	}
 	var instanceID string
 	if instanceID, err = call.Finish(); err != nil {
 		return fmt.Errorf("Instantiate failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "%s\n", naming.Join(appInstallation, instanceID))
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(appInstallation, instanceID))
 	return nil
 }
 
-var cmdClaim = &cmdline.Command{
-	Run:      runClaim,
+var cmdClaim = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runClaim),
 	Name:     "claim",
 	Short:    "Claim the device.",
 	Long:     "Claim the device.",
@@ -192,9 +193,9 @@
 are claiming.`,
 }
 
-func runClaim(cmd *cmdline.Command, args []string) error {
+func runClaim(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, max, got := 2, 4, len(args); expected > got || got > max {
-		return cmd.UsageErrorf("claim: incorrect number of arguments, expected atleast %d (max: %d), got %d", expected, max, got)
+		return env.UsageErrorf("claim: incorrect number of arguments, expected atleast %d (max: %d), got %d", expected, max, got)
 	}
 	deviceName, grant := args[0], args[1]
 	var pairingToken string
@@ -215,15 +216,15 @@
 	}
 	// Skip server endpoint authorization since an unclaimed device might have
 	// roots that will not be recognized by the claimer.
-	if err := device.ClaimableClient(deviceName).Claim(gctx, pairingToken, &granter{extension: grant}, serverKeyOpts, options.SkipServerEndpointAuthorization{}); err != nil {
+	if err := device.ClaimableClient(deviceName).Claim(ctx, pairingToken, &granter{extension: grant}, serverKeyOpts, options.SkipServerEndpointAuthorization{}); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Successfully claimed.")
+	fmt.Fprintln(env.Stdout, "Successfully claimed.")
 	return nil
 }
 
-var cmdDescribe = &cmdline.Command{
-	Run:      runDescribe,
+var cmdDescribe = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDescribe),
 	Name:     "describe",
 	Short:    "Describe the device.",
 	Long:     "Describe the device.",
@@ -232,21 +233,21 @@
 <device> is the vanadium object name of the device manager's device service.`,
 }
 
-func runDescribe(cmd *cmdline.Command, args []string) error {
+func runDescribe(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("describe: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("describe: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	deviceName := args[0]
-	if description, err := device.DeviceClient(deviceName).Describe(gctx); err != nil {
+	if description, err := device.DeviceClient(deviceName).Describe(ctx); err != nil {
 		return fmt.Errorf("Describe failed: %v", err)
 	} else {
-		fmt.Fprintf(cmd.Stdout(), "%+v\n", description)
+		fmt.Fprintf(env.Stdout, "%+v\n", description)
 	}
 	return nil
 }
 
-var cmdUpdate = &cmdline.Command{
-	Run:      runUpdate,
+var cmdUpdate = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runUpdate),
 	Name:     "update",
 	Short:    "Update the device manager or application",
 	Long:     "Update the device manager or application",
@@ -256,20 +257,20 @@
 installation or instance to update.`,
 }
 
-func runUpdate(cmd *cmdline.Command, args []string) error {
+func runUpdate(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("update: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("update: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
-	if err := device.ApplicationClient(name).Update(gctx); err != nil {
+	if err := device.ApplicationClient(name).Update(ctx); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Update successful.")
+	fmt.Fprintln(env.Stdout, "Update successful.")
 	return nil
 }
 
-var cmdRevert = &cmdline.Command{
-	Run:      runRevert,
+var cmdRevert = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runRevert),
 	Name:     "revert",
 	Short:    "Revert the device manager or application",
 	Long:     "Revert the device manager or application to its previous version",
@@ -279,20 +280,20 @@
 installation to revert.`,
 }
 
-func runRevert(cmd *cmdline.Command, args []string) error {
+func runRevert(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("revert: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("revert: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	deviceName := args[0]
-	if err := device.ApplicationClient(deviceName).Revert(gctx); err != nil {
+	if err := device.ApplicationClient(deviceName).Revert(ctx); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Revert successful.")
+	fmt.Fprintln(env.Stdout, "Revert successful.")
 	return nil
 }
 
-var cmdDebug = &cmdline.Command{
-	Run:      runDebug,
+var cmdDebug = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDebug),
 	Name:     "debug",
 	Short:    "Debug the device.",
 	Long:     "Debug the device.",
@@ -301,21 +302,21 @@
 <app name> is the vanadium object name of an app installation or instance.`,
 }
 
-func runDebug(cmd *cmdline.Command, args []string) error {
+func runDebug(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("debug: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("debug: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appName := args[0]
-	if description, err := device.DeviceClient(appName).Debug(gctx); err != nil {
+	if description, err := device.DeviceClient(appName).Debug(ctx); err != nil {
 		return fmt.Errorf("Debug failed: %v", err)
 	} else {
-		fmt.Fprintf(cmd.Stdout(), "%v\n", description)
+		fmt.Fprintf(env.Stdout, "%v\n", description)
 	}
 	return nil
 }
 
-var cmdStatus = &cmdline.Command{
-	Run:      runStatus,
+var cmdStatus = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runStatus),
 	Name:     "status",
 	Short:    "Get application status.",
 	Long:     "Get the status of an application installation or instance.",
@@ -324,20 +325,20 @@
 <app name> is the vanadium object name of an app installation or instance.`,
 }
 
-func runStatus(cmd *cmdline.Command, args []string) error {
+func runStatus(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("status: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("status: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appName := args[0]
-	status, err := device.DeviceClient(appName).Status(gctx)
+	status, err := device.DeviceClient(appName).Status(ctx)
 	if err != nil {
 		return fmt.Errorf("Status failed: %v", err)
 	}
 	switch s := status.(type) {
 	case device.StatusInstance:
-		fmt.Fprintf(cmd.Stdout(), "Instance [State:%v,Version:%v]\n", s.Value.State, s.Value.Version)
+		fmt.Fprintf(env.Stdout, "Instance [State:%v,Version:%v]\n", s.Value.State, s.Value.Version)
 	case device.StatusInstallation:
-		fmt.Fprintf(cmd.Stdout(), "Installation [State:%v,Version:%v]\n", s.Value.State, s.Value.Version)
+		fmt.Fprintf(env.Stdout, "Installation [State:%v,Version:%v]\n", s.Value.State, s.Value.Version)
 	default:
 		return fmt.Errorf("Status returned unknown type: %T", s)
 	}
diff --git a/services/device/device/impl_test.go b/services/device/device/impl_test.go
index 9ac0b1d..93810ae 100644
--- a/services/device/device/impl_test.go
+++ b/services/device/device/impl_test.go
@@ -18,27 +18,31 @@
 	"v.io/v23/services/application"
 	"v.io/v23/services/device"
 	"v.io/v23/verror"
+	"v.io/x/lib/cmdline2"
 	"v.io/x/ref/lib/security"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/test"
+
 	cmd_device "v.io/x/ref/services/device/device"
 )
 
 //go:generate v23 test generate
 
 func TestListCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 
 	rootTape := tapes.forSuffix("")
@@ -57,7 +61,7 @@
 		err: nil,
 	}})
 
-	if err := cmd.Execute([]string{"associate", "list", deviceName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "list", deviceName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
@@ -70,7 +74,7 @@
 	stdout.Reset()
 
 	// Test list with bad parameters.
-	if err := cmd.Execute([]string{"associate", "list", deviceName, "hello"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "list", deviceName, "hello"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if got, expected := len(rootTape.Play()), 0; got != expected {
@@ -79,23 +83,23 @@
 }
 
 func TestAddCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 
-	if err := cmd.Execute([]string{"add", "one"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"add", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	rootTape := tapes.forSuffix("")
@@ -106,7 +110,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := cmd.Execute([]string{"associate", "add", deviceName, "alice", "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected := []interface{}{
@@ -119,7 +123,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := cmd.Execute([]string{"associate", "add", deviceName, "alice", "root/other", "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/other", "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected = []interface{}{
@@ -131,23 +135,23 @@
 }
 
 func TestRemoveCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 
-	if err := cmd.Execute([]string{"remove", "one"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"remove", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	rootTape := tapes.forSuffix("")
@@ -158,7 +162,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := cmd.Execute([]string{"associate", "remove", deviceName, "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "remove", deviceName, "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected := []interface{}{
@@ -170,20 +174,20 @@
 }
 
 func TestInstallCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 	appId := "myBestAppID"
 	cfg := device.Config{"someflag": "somevalue"}
@@ -254,7 +258,7 @@
 			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
 		}
 		c.args = append([]string{"install"}, c.args...)
-		err := cmd.Execute(c.args)
+		err := v23cmd.ParseAndRun(cmd, ctx, env, c.args)
 		if c.shouldErr {
 			if err == nil {
 				t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
@@ -279,28 +283,28 @@
 }
 
 func TestClaimCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
-	deviceKey, err := v23.GetPrincipal(gctx).PublicKey().MarshalBinary()
+	deviceKey, err := v23.GetPrincipal(ctx).PublicKey().MarshalBinary()
 	if err != nil {
 		t.Fatalf("Failed to marshal principal public key: %v", err)
 	}
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := cmd.Execute([]string{"claim", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -311,7 +315,7 @@
 	rootTape := tapes.forSuffix("")
 	rootTape.Rewind()
 
-	if err := cmd.Execute([]string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 5", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -331,7 +335,7 @@
 			t.Fatalf("Failed to marshal principal public key: %v", err)
 		}
 	}
-	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
 		t.Fatalf("wrongly failed to receive correct error on claim with incorrect device key:%v id:%v", err, verror.ErrorID(err))
 	}
 	stdout.Reset()
@@ -342,7 +346,7 @@
 	rootTape.SetResponses([]interface{}{
 		nil,
 	})
-	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
 		t.Fatalf("Claim(%s, %s, %s) failed: %v", deviceName, "grant", pairingToken, err)
 	}
 	if got, expected := len(rootTape.Play()), 1; got != expected {
@@ -365,7 +369,7 @@
 	rootTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken}); err == nil {
 		t.Fatalf("claim() failed to detect error", err)
 	}
 	expected = []interface{}{
@@ -377,24 +381,24 @@
 }
 
 func TestInstantiateCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := cmd.Execute([]string{"instantiate", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -405,7 +409,7 @@
 	rootTape := tapes.forSuffix("")
 	rootTape.Rewind()
 
-	if err := cmd.Execute([]string{"instantiate", "nope", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", "nope", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -421,7 +425,7 @@
 		instanceID: "app1",
 	},
 	})
-	if err := cmd.Execute([]string{"instantiate", appName, "grant"}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err != nil {
 		t.Fatalf("instantiate %s %s failed: %v", appName, "grant", err)
 	}
 
@@ -446,7 +450,7 @@
 		"",
 	},
 	})
-	if err := cmd.Execute([]string{"instantiate", appName, "grant"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err == nil {
 		t.Fatalf("instantiate failed to detect error")
 	}
 	expected = []interface{}{
@@ -458,24 +462,24 @@
 }
 
 func TestDebugCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "")
 
 	debugMessage := "the secrets of the universe, revealed"
 	rootTape := tapes.forSuffix("")
 	rootTape.SetResponses([]interface{}{debugMessage})
-	if err := cmd.Execute([]string{"debug", appName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"debug", appName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := debugMessage, strings.TrimSpace(stdout.String()); got != expected {
@@ -487,18 +491,18 @@
 }
 
 func TestStatusCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "")
 
 	rootTape := tapes.forSuffix("")
@@ -524,7 +528,7 @@
 		rootTape.Rewind()
 		stdout.Reset()
 		rootTape.SetResponses([]interface{}{c.tapeResponse})
-		if err := cmd.Execute([]string{"status", appName}); err != nil {
+		if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"status", appName}); err != nil {
 			t.Errorf("%v", err)
 		}
 		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/device/device/instance_impl.go b/services/device/device/instance_impl.go
index 7a3febd..e1bcd82 100644
--- a/services/device/device/instance_impl.go
+++ b/services/device/device/instance_impl.go
@@ -10,12 +10,14 @@
 	"fmt"
 	"time"
 
+	"v.io/v23/context"
 	"v.io/v23/services/device"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 )
 
-var cmdDelete = &cmdline.Command{
-	Run:      runDelete,
+var cmdDelete = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDelete),
 	Name:     "delete",
 	Short:    "Delete the given application instance.",
 	Long:     "Delete the given application instance.",
@@ -24,23 +26,23 @@
 <app instance> is the vanadium object name of the application instance to delete.`,
 }
 
-func runDelete(cmd *cmdline.Command, args []string) error {
+func runDelete(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appName := args[0]
 
-	if err := device.ApplicationClient(appName).Delete(gctx); err != nil {
+	if err := device.ApplicationClient(appName).Delete(ctx); err != nil {
 		return fmt.Errorf("Delete failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "Delete succeeded\n")
+	fmt.Fprintf(env.Stdout, "Delete succeeded\n")
 	return nil
 }
 
 const killDeadline = 10 * time.Second
 
-var cmdKill = &cmdline.Command{
-	Run:      runKill,
+var cmdKill = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runKill),
 	Name:     "kill",
 	Short:    "Kill the given application instance.",
 	Long:     "Kill the given application instance.",
@@ -49,21 +51,21 @@
 <app instance> is the vanadium object name of the application instance to kill.`,
 }
 
-func runKill(cmd *cmdline.Command, args []string) error {
+func runKill(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("kill: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("kill: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appName := args[0]
 
-	if err := device.ApplicationClient(appName).Kill(gctx, killDeadline); err != nil {
+	if err := device.ApplicationClient(appName).Kill(ctx, killDeadline); err != nil {
 		return fmt.Errorf("Kill failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "Kill succeeded\n")
+	fmt.Fprintf(env.Stdout, "Kill succeeded\n")
 	return nil
 }
 
-var cmdRun = &cmdline.Command{
-	Run:      runRun,
+var cmdRun = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runRun),
 	Name:     "run",
 	Short:    "Run the given application instance.",
 	Long:     "Run the given application instance.",
@@ -72,15 +74,15 @@
 <app instance> is the vanadium object name of the application instance to run.`,
 }
 
-func runRun(cmd *cmdline.Command, args []string) error {
+func runRun(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("run: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("run: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appName := args[0]
 
-	if err := device.ApplicationClient(appName).Run(gctx); err != nil {
+	if err := device.ApplicationClient(appName).Run(ctx); err != nil {
 		return fmt.Errorf("Run failed: %v,\nView log with:\n debug logs read `debug glob %s/logs/STDERR-*`", err, appName)
 	}
-	fmt.Fprintf(cmd.Stdout(), "Run succeeded\n")
+	fmt.Fprintf(env.Stdout, "Run succeeded\n")
 	return nil
 }
diff --git a/services/device/device/instance_impl_test.go b/services/device/device/instance_impl_test.go
index fdbe781..d072b04 100644
--- a/services/device/device/instance_impl_test.go
+++ b/services/device/device/instance_impl_test.go
@@ -13,29 +13,32 @@
 
 	"v.io/v23/naming"
 	"v.io/v23/verror"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
 )
 
 func TestKillCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "appname")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := cmd.Execute([]string{"kill"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -46,7 +49,7 @@
 	appTape := tapes.forSuffix("appname")
 	appTape.Rewind()
 
-	if err := cmd.Execute([]string{"kill", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -61,7 +64,7 @@
 		nil,
 	})
 
-	if err := cmd.Execute([]string{"kill", appName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", appName}); err != nil {
 		t.Fatalf("kill failed when it shouldn't: %v", err)
 	}
 	if expected, got := "Kill succeeded", strings.TrimSpace(stdout.String()); got != expected {
@@ -81,7 +84,7 @@
 	appTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := cmd.Execute([]string{"kill", appName}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", appName}); err == nil {
 		t.Fatalf("wrongly didn't receive a non-nil error.")
 	}
 	// expected the same.
@@ -91,24 +94,24 @@
 }
 
 func testHelper(t *testing.T, lower, upper string) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	appName := naming.JoinAddressName(endpoint.String(), "appname")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := cmd.Execute([]string{lower}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -119,7 +122,7 @@
 	appTape := tapes.forSuffix("appname")
 	appTape.Rewind()
 
-	if err := cmd.Execute([]string{lower, "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -133,7 +136,7 @@
 	appTape.SetResponses([]interface{}{
 		nil,
 	})
-	if err := cmd.Execute([]string{lower, appName}); err != nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, appName}); err != nil {
 		t.Fatalf("%s failed when it shouldn't: %v", lower, err)
 	}
 	if expected, got := upper+" succeeded", strings.TrimSpace(stdout.String()); got != expected {
@@ -150,7 +153,7 @@
 	appTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := cmd.Execute([]string{lower, appName}); err == nil {
+	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, appName}); err == nil {
 		t.Fatalf("wrongly didn't receive a non-nil error.")
 	}
 	// expected the same.
diff --git a/services/device/device/local_install.go b/services/device/device/local_install.go
index c124974..f0ef6c3 100644
--- a/services/device/device/local_install.go
+++ b/services/device/device/local_install.go
@@ -27,12 +27,13 @@
 	"v.io/v23/uniqueid"
 	"v.io/x/lib/vlog"
 
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/services/internal/packages"
 )
 
-var cmdInstallLocal = &cmdline.Command{
-	Run:      runInstallLocal,
+var cmdInstallLocal = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runInstallLocal),
 	Name:     "install-local",
 	Short:    "Install the given application from the local system.",
 	Long:     "Install the given application specified using a local path, and print the name of the new installation.",
@@ -250,9 +251,9 @@
 // TODO(caprita/ashankar): We should use bi-directional streams to get this
 // working over the same connection that the command makes to the device
 // manager.
-func runInstallLocal(cmd *cmdline.Command, args []string) error {
+func runInstallLocal(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expectedMin, got := 2, len(args); got < expectedMin {
-		return cmd.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
+		return env.UsageErrorf("install-local: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
 	}
 	deviceName, title := args[0], args[1]
 	args = args[2:]
@@ -268,7 +269,7 @@
 	envelope.Env = args[:firstNonEnv]
 	args = args[firstNonEnv:]
 	if len(args) == 0 {
-		return cmd.UsageErrorf("install-local: missing binary")
+		return env.UsageErrorf("install-local: missing binary")
 	}
 	binary := args[0]
 	args = args[1:]
@@ -285,7 +286,7 @@
 	if _, err := os.Stat(binary); err != nil {
 		return fmt.Errorf("binary %v not found: %v", binary, err)
 	}
-	server, cancel, err := createServer(gctx, cmd.Stderr())
+	server, cancel, err := createServer(ctx, env.Stderr)
 	if err != nil {
 		return fmt.Errorf("failed to create server: %v", err)
 	}
@@ -330,7 +331,7 @@
 		return err
 	}
 	vlog.VI(1).Infof("application serving envelope as %v", appName)
-	appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride), packagesRewritten)
+	appID, err := device.ApplicationClient(deviceName).Install(ctx, appName, device.Config(configOverride), packagesRewritten)
 	// Reset the value for any future invocations of "install" or
 	// "install-local" (we run more than one command per process in unit
 	// tests).
@@ -339,6 +340,6 @@
 	if err != nil {
 		return fmt.Errorf("Install failed: %v", err)
 	}
-	fmt.Fprintf(cmd.Stdout(), "%s\n", naming.Join(deviceName, appID))
+	fmt.Fprintf(env.Stdout, "%s\n", naming.Join(deviceName, appID))
 	return nil
 }
diff --git a/services/device/device/local_install_test.go b/services/device/device/local_install_test.go
index 0c9aeac..7f01b14 100644
--- a/services/device/device/local_install_test.go
+++ b/services/device/device/local_install_test.go
@@ -19,6 +19,9 @@
 	"v.io/v23/security"
 	"v.io/v23/services/application"
 	"v.io/v23/services/device"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
 )
@@ -30,19 +33,19 @@
 }
 
 func TestInstallLocalCommand(t *testing.T) {
-	shutdown := initTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	tapes := newTapeMap()
-	server, endpoint, err := startServer(t, gctx, tapes)
+	server, endpoint, err := startServer(t, ctx, tapes)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 	// Setup the command-line.
-	cmd := cmd_device.Root()
+	cmd := cmd_device.CmdRoot
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 	const appTitle = "Appo di tutti Appi"
 	binary := os.Args[0]
@@ -73,7 +76,7 @@
 		},
 	} {
 		c.args = append([]string{"install-local"}, c.args...)
-		if err := cmd.Execute(c.args); err == nil {
+		if err := v23cmd.ParseAndRun(cmd, ctx, env, c.args); err == nil {
 			t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
 		} else {
 			fmt.Fprintln(&stderr, "ERROR:", err)
@@ -237,7 +240,7 @@
 			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
 		}
 		c.args = append([]string{"install-local"}, c.args...)
-		if err := cmd.Execute(c.args); err != nil {
+		if err := v23cmd.ParseAndRun(cmd, ctx, env, c.args); err != nil {
 			t.Fatalf("test case %d: %v", i, err)
 		}
 		if expected, got := naming.Join(deviceName, appId), strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/device/device/main.go b/services/device/device/main.go
deleted file mode 100644
index b523dbc..0000000
--- a/services/device/device/main.go
+++ /dev/null
@@ -1,26 +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 .
-
-package main
-
-import (
-	"os"
-	"regexp"
-
-	"v.io/v23"
-	"v.io/x/lib/cmdline"
-	_ "v.io/x/ref/profiles/static"
-)
-
-func main() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^(v23\.namespace\.root)|(v23\.proxy)$`))
-	gctx, shutdown := v23.Init()
-	SetGlobalContext(gctx)
-	exitCode := Root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}
diff --git a/services/device/device/publish.go b/services/device/device/publish.go
index 129360c..ae49d3c 100644
--- a/services/device/device/publish.go
+++ b/services/device/device/publish.go
@@ -12,14 +12,15 @@
 	"strings"
 	"time"
 
+	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/security"
 	"v.io/v23/security/access"
 	"v.io/v23/services/application"
 	"v.io/v23/services/permissions"
 	"v.io/v23/verror"
-
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	"v.io/x/ref/services/internal/binarylib"
 	"v.io/x/ref/services/repository"
 )
@@ -28,10 +29,10 @@
 
 // TODO(caprita): Extend to include env, args, packages.
 
-var cmdPublish = &cmdline.Command{
-	Run:   runPublish,
-	Name:  "publish",
-	Short: "Publish the given application(s).",
+var cmdPublish = &cmdline2.Command{
+	Runner: v23cmd.RunnerFunc(runPublish),
+	Name:   "publish",
+	Short:  "Publish the given application(s).",
 	Long: `
 Publishes the given application(s) to the binary and application servers.
 The <title> can be optionally specified with @<title> (defaults to the binary
@@ -55,11 +56,11 @@
 	cmdPublish.Flags.StringVar(&readBlessings, "readers", "dev.v.io", "If non-empty, comma-separated blessing patterns to add to Read and Resolve AccessList.")
 }
 
-func setAccessLists(cmd *cmdline.Command, von string) error {
+func setAccessLists(ctx *context.T, env *cmdline2.Env, von string) error {
 	if readBlessings == "" {
 		return nil
 	}
-	perms, version, err := permissions.ObjectClient(von).GetPermissions(gctx)
+	perms, version, err := permissions.ObjectClient(von).GetPermissions(ctx)
 	if err != nil {
 		// TODO(caprita): This is a workaround until we sort out the
 		// default AccessLists for applicationd (see issue #1317).  At that
@@ -73,14 +74,14 @@
 			perms.Add(security.BlessingPattern(blessing), string(tag))
 		}
 	}
-	if err := permissions.ObjectClient(von).SetPermissions(gctx, perms, version); err != nil {
+	if err := permissions.ObjectClient(von).SetPermissions(ctx, perms, version); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Added patterns %q to Read,Resolve AccessList for %q\n", readBlessings, von)
+	fmt.Fprintf(env.Stdout, "Added patterns %q to Read,Resolve AccessList for %q\n", readBlessings, von)
 	return nil
 }
 
-func publishOne(cmd *cmdline.Command, binPath, binary string) error {
+func publishOne(ctx *context.T, env *cmdline2.Env, binPath, binary string) error {
 	binaryName, title := binary, binary
 	if parts := strings.SplitN(binary, "@", 2); len(parts) == 2 {
 		binaryName, title = parts[0], parts[1]
@@ -93,14 +94,14 @@
 	binaryVON := naming.Join(binaryService, binaryName, fmt.Sprintf("%s-%s", goosFlag, goarchFlag), timestamp)
 	binaryFile := filepath.Join(binPath, binaryName)
 	// TODO(caprita): Take signature of binary and put it in the envelope.
-	if _, err := binarylib.UploadFromFile(gctx, binaryVON, binaryFile); err != nil {
+	if _, err := binarylib.UploadFromFile(ctx, binaryVON, binaryFile); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Binary %q uploaded from file %s\n", binaryVON, binaryFile)
+	fmt.Fprintf(env.Stdout, "Binary %q uploaded from file %s\n", binaryVON, binaryFile)
 
 	// Step 2, set the perms for the uploaded binary.
 
-	if err := setAccessLists(cmd, binaryVON); err != nil {
+	if err := setAccessLists(ctx, env, binaryVON); err != nil {
 		return err
 	}
 
@@ -116,7 +117,7 @@
 	// NOTE: If profiles contains more than one entry, this will return only
 	// the first match.  But presumably that's ok, since we're going to set
 	// the envelopes for all the profiles to the same envelope anyway below.
-	envelope, err := appClient.Match(gctx, profiles)
+	envelope, err := appClient.Match(ctx, profiles)
 	if verror.ErrorID(err) == verror.ErrNoExist.ID {
 		// There was nothing published yet, create a new envelope.
 		envelope = application.Envelope{Title: title}
@@ -124,47 +125,47 @@
 		return err
 	}
 	envelope.Binary.File = binaryVON
-	if err := repository.ApplicationClient(appVON).Put(gctx, profiles, envelope); err != nil {
+	if err := repository.ApplicationClient(appVON).Put(ctx, profiles, envelope); err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Published %q\n", appVON)
+	fmt.Fprintf(env.Stdout, "Published %q\n", appVON)
 
 	// Step 4, set the perms for the uploaded envelope.
 
-	if err := setAccessLists(cmd, appVON); err != nil {
+	if err := setAccessLists(ctx, env, appVON); err != nil {
 		return err
 	}
 	return nil
 }
 
-func runPublish(cmd *cmdline.Command, args []string) error {
+func runPublish(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expectedMin, got := 1, len(args); got < expectedMin {
-		return cmd.UsageErrorf("publish: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
+		return env.UsageErrorf("publish: incorrect number of arguments, expected at least %d, got %d", expectedMin, got)
 	}
 	binaries := args
 	vroot := os.Getenv("V23_ROOT")
 	if vroot == "" {
-		return cmd.UsageErrorf("publish: $V23_ROOT environment variable should be set")
+		return env.UsageErrorf("publish: $V23_ROOT environment variable should be set")
 	}
 	binPath := filepath.Join(vroot, "release/go/bin")
 	if goosFlag != runtime.GOOS || goarchFlag != runtime.GOARCH {
 		binPath = filepath.Join(binPath, fmt.Sprintf("%s_%s", goosFlag, goarchFlag))
 	}
 	if fi, err := os.Stat(binPath); err != nil {
-		return cmd.UsageErrorf("publish: failed to stat %v: %v", binPath, err)
+		return env.UsageErrorf("publish: failed to stat %v: %v", binPath, err)
 	} else if !fi.IsDir() {
-		return cmd.UsageErrorf("publish: %v is not a directory", binPath)
+		return env.UsageErrorf("publish: %v is not a directory", binPath)
 	}
 	if binaryService == "" {
-		return cmd.UsageErrorf("publish: --binserv must point to a binary service name")
+		return env.UsageErrorf("publish: --binserv must point to a binary service name")
 	}
 	if applicationService == "" {
-		return cmd.UsageErrorf("publish: --appserv must point to an application service name")
+		return env.UsageErrorf("publish: --appserv must point to an application service name")
 	}
 	var lastErr error
 	for _, b := range binaries {
-		if err := publishOne(cmd, binPath, b); err != nil {
-			fmt.Fprintf(cmd.Stderr(), "Failed to publish %q: %v\n", b, err)
+		if err := publishOne(ctx, env, binPath, b); err != nil {
+			fmt.Fprintf(env.Stderr, "Failed to publish %q: %v\n", b, err)
 			lastErr = err
 		}
 	}
diff --git a/services/device/device/root.go b/services/device/device/root.go
index 809b3bb..ec07f48 100644
--- a/services/device/device/root.go
+++ b/services/device/device/root.go
@@ -5,24 +5,22 @@
 package main
 
 import (
-	"v.io/v23/context"
+	"regexp"
 
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	_ "v.io/x/ref/profiles/static"
 )
 
-var gctx *context.T
-
-func SetGlobalContext(ctx *context.T) {
-	gctx = ctx
-}
-
-func Root() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "device",
-		Short: "facilitates interaction with the Vanadium device manager",
-		Long: `
+var CmdRoot = &cmdline2.Command{
+	Name:  "device",
+	Short: "facilitates interaction with the Vanadium device manager",
+	Long: `
 Command device facilitates interaction with the Vanadium device manager.
 `,
-		Children: []*cmdline.Command{cmdInstall, cmdInstallLocal, cmdUninstall, associateRoot(), cmdDescribe, cmdClaim, cmdInstantiate, cmdDelete, cmdRun, cmdKill, cmdRevert, cmdUpdate, cmdUpdateAll, cmdStatus, cmdDebug, aclRoot(), cmdPublish},
-	}
+	Children: []*cmdline2.Command{cmdInstall, cmdInstallLocal, cmdUninstall, cmdAssociate, cmdDescribe, cmdClaim, cmdInstantiate, cmdDelete, cmdRun, cmdKill, cmdRevert, cmdUpdate, cmdUpdateAll, cmdStatus, cmdDebug, cmdACL, cmdPublish},
+}
+
+func main() {
+	cmdline2.HideGlobalFlagsExcept(regexp.MustCompile(`^((v23\.namespace\.root)|(v23\.proxy))$`))
+	cmdline2.Main(CmdRoot)
 }
diff --git a/services/device/device/updateall.go b/services/device/device/updateall.go
index c660863..b98b46b 100644
--- a/services/device/device/updateall.go
+++ b/services/device/device/updateall.go
@@ -11,11 +11,12 @@
 	"time"
 
 	"v.io/v23"
+	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/services/device"
 	"v.io/v23/verror"
-
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
 	deviceimpl "v.io/x/ref/services/device/internal/impl"
 )
 
@@ -24,8 +25,8 @@
 
 // TODO(caprita): Add unit test.
 
-var cmdUpdateAll = &cmdline.Command{
-	Run:      runUpdateAll,
+var cmdUpdateAll = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runUpdateAll),
 	Name:     "updateall",
 	Short:    "Update all installations/instances of an application",
 	Long:     "Given a name that can refer to an app instance or app installation or app or all apps on a device, updates all installations and instances under that name",
@@ -43,12 +44,12 @@
 `,
 }
 
-type updater func(cmd *cmdline.Command, von string) error
+type updater func(ctx *context.T, env *cmdline2.Env, von string) error
 
-func updateChildren(cmd *cmdline.Command, von string, updateChild updater) error {
-	ns := v23.GetNamespace(gctx)
+func updateChildren(ctx *context.T, env *cmdline2.Env, von string, updateChild updater) error {
+	ns := v23.GetNamespace(ctx)
 	pattern := naming.Join(von, "*")
-	c, err := ns.Glob(gctx, pattern)
+	c, err := ns.Glob(ctx, pattern)
 	if err != nil {
 		return fmt.Errorf("ns.Glob(%q) failed: %v", pattern, err)
 	}
@@ -62,7 +63,7 @@
 		case *naming.GlobReplyEntry:
 			pending.Add(1)
 			go func() {
-				if err := updateChild(cmd, v.Value.Name); err != nil {
+				if err := updateChild(ctx, env, v.Value.Name); err != nil {
 					numErrorsMu.Lock()
 					numErrors++
 					numErrorsMu.Unlock()
@@ -70,7 +71,7 @@
 				pending.Done()
 			}()
 		case *naming.GlobReplyError:
-			fmt.Fprintf(cmd.Stderr(), "Glob error for %q: %v\n", v.Value.Name, v.Value.Error)
+			fmt.Fprintf(env.Stderr, "Glob error for %q: %v\n", v.Value.Name, v.Value.Error)
 			numErrorsMu.Lock()
 			numErrors++
 			numErrorsMu.Unlock()
@@ -83,8 +84,8 @@
 	return nil
 }
 
-func instanceIsRunning(von string) (bool, error) {
-	status, err := device.ApplicationClient(von).Status(gctx)
+func instanceIsRunning(ctx *context.T, von string) (bool, error) {
+	status, err := device.ApplicationClient(von).Status(ctx)
 	if err != nil {
 		return false, fmt.Errorf("Failed to get status for instance %q: %v", von, err)
 	}
@@ -95,79 +96,79 @@
 	return s.Value.State == device.InstanceStateRunning, nil
 }
 
-func updateInstance(cmd *cmdline.Command, von string) (retErr error) {
+func updateInstance(ctx *context.T, env *cmdline2.Env, von string) (retErr error) {
 	defer func() {
 		if retErr == nil {
-			fmt.Fprintf(cmd.Stdout(), "Successfully updated instance %q.\n", von)
+			fmt.Fprintf(env.Stdout, "Successfully updated instance %q.\n", von)
 		} else {
 			retErr = fmt.Errorf("failed to update instance %q: %v", von, retErr)
-			fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", retErr)
+			fmt.Fprintf(env.Stderr, "ERROR: %v.\n", retErr)
 		}
 	}()
-	running, err := instanceIsRunning(von)
+	running, err := instanceIsRunning(ctx, von)
 	if err != nil {
 		return err
 	}
 	if running {
 		// Try killing the app.
-		if err := device.ApplicationClient(von).Kill(gctx, killDeadline); err != nil {
+		if err := device.ApplicationClient(von).Kill(ctx, killDeadline); err != nil {
 			// Check the app's state again in case we killed it,
 			// nevermind any errors.  The sleep is because Kill
 			// currently (4/29/15) returns asynchronously with the
 			// device manager shooting the app down.
 			time.Sleep(time.Second)
-			running, rerr := instanceIsRunning(von)
+			running, rerr := instanceIsRunning(ctx, von)
 			if rerr != nil {
 				return rerr
 			}
 			if running {
 				return fmt.Errorf("failed to kill instance %q: %v", von, err)
 			}
-			fmt.Fprintf(cmd.Stderr(), "Kill(%s) returned an error (%s) but app is now not running.\n", von, err)
+			fmt.Fprintf(env.Stderr, "Kill(%s) returned an error (%s) but app is now not running.\n", von, err)
 		}
 		// App was running, and we killed it.
 		defer func() {
 			// Re-start the instance.
-			if err := device.ApplicationClient(von).Run(gctx); err != nil {
+			if err := device.ApplicationClient(von).Run(ctx); err != nil {
 				err = fmt.Errorf("failed to run instance %q: %v", von, err)
 				if retErr == nil {
 					retErr = err
 				} else {
-					fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", err)
+					fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
 				}
 			}
 		}()
 	}
 	// Update the instance.
-	switch err := device.ApplicationClient(von).Update(gctx); {
+	switch err := device.ApplicationClient(von).Update(ctx); {
 	case err == nil:
 		return nil
 	case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
 		// TODO(caprita): Ideally, we wouldn't even attempt a kill /
 		// restart if there's no newer version of the application.
-		fmt.Fprintf(cmd.Stdout(), "Instance %q already up to date.\n", von)
+		fmt.Fprintf(env.Stdout, "Instance %q already up to date.\n", von)
 		return nil
 	default:
 		return err
 	}
 }
 
-func updateInstallation(cmd *cmdline.Command, von string) (retErr error) {
+func updateInstallation(ctx *context.T, env *cmdline2.Env, von string) (retErr error) {
 	defer func() {
 		if retErr == nil {
-			fmt.Fprintf(cmd.Stdout(), "Successfully updated installation %q.\n", von)
+			fmt.Fprintf(env.Stdout, "Successfully updated installation %q.\n", von)
 		} else {
 			retErr = fmt.Errorf("failed to update installation %q: %v", von, retErr)
-			fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", retErr)
+			fmt.Fprintf(env.Stderr, "ERROR: %v.\n", retErr)
 		}
 	}()
 
 	// First, update the installation.
-	switch err := device.ApplicationClient(von).Update(gctx); {
+	switch err := device.ApplicationClient(von).Update(ctx); {
 	case err == nil:
-		fmt.Fprintf(cmd.Stdout(), "Successfully updated version for installation %q.\n", von)
+		fmt.Fprintf(env.Stdout, "Successfully updated version for installation %q.\n", von)
 	case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
-		fmt.Fprintf(cmd.Stdout(), "Installation %q already up to date.\n", von)
+		fmt.Fprintf(env.Stdout, "Installation %q already up to date.\n", von)
 		// NOTE: we still proceed to update the instances in this case,
 		// since it's possible that some instances are still running
 		// from older versions.
@@ -175,32 +176,32 @@
 		return err
 	}
 	// Then, update all the instances for the installation.
-	return updateChildren(cmd, von, updateInstance)
+	return updateChildren(ctx, env, von, updateInstance)
 }
 
-func updateApp(cmd *cmdline.Command, von string) error {
-	if err := updateChildren(cmd, von, updateInstallation); err != nil {
+func updateApp(ctx *context.T, env *cmdline2.Env, von string) error {
+	if err := updateChildren(ctx, env, von, updateInstallation); err != nil {
 		err = fmt.Errorf("failed to update app %q: %v", von, err)
-		fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", err)
+		fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Successfully updated app %q.\n", von)
+	fmt.Fprintf(env.Stdout, "Successfully updated app %q.\n", von)
 	return nil
 }
 
-func updateAllApps(cmd *cmdline.Command, von string) error {
-	if err := updateChildren(cmd, von, updateApp); err != nil {
+func updateAllApps(ctx *context.T, env *cmdline2.Env, von string) error {
+	if err := updateChildren(ctx, env, von, updateApp); err != nil {
 		err = fmt.Errorf("failed to update all apps %q: %v", von, err)
-		fmt.Fprintf(cmd.Stderr(), "ERROR: %v.\n", err)
+		fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "Successfully updated all apps %q.\n", von)
+	fmt.Fprintf(env.Stdout, "Successfully updated all apps %q.\n", von)
 	return nil
 }
 
-func runUpdateAll(cmd *cmdline.Command, args []string) error {
+func runUpdateAll(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("updateall: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("updateall: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	appVON := args[0]
 	components := strings.Split(appVON, "/")
@@ -223,13 +224,13 @@
 	fmt.Printf("prefix: %q, components: %q\n", prefix, components)
 	switch len(components) {
 	case 0:
-		return updateAllApps(cmd, appVON)
+		return updateAllApps(ctx, env, appVON)
 	case 1:
-		return updateApp(cmd, appVON)
+		return updateApp(ctx, env, appVON)
 	case 2:
-		return updateInstallation(cmd, appVON)
+		return updateInstallation(ctx, env, appVON)
 	case 3:
-		return updateInstance(cmd, appVON)
+		return updateInstance(ctx, env, appVON)
 	}
-	return cmd.UsageErrorf("updateall: name %q does not refer to a supported app hierarchy object", appVON)
+	return env.UsageErrorf("updateall: name %q does not refer to a supported app hierarchy object", appVON)
 }
diff --git a/services/device/device/util_test.go b/services/device/device/util_test.go
deleted file mode 100644
index 0be10bc..0000000
--- a/services/device/device/util_test.go
+++ /dev/null
@@ -1,26 +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.
-
-package main_test
-
-import (
-	"v.io/v23"
-	"v.io/v23/context"
-
-	cmd_device "v.io/x/ref/services/device/device"
-	"v.io/x/ref/test"
-)
-
-var gctx *context.T
-
-func initTest() v23.Shutdown {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
-	cmd_device.SetGlobalContext(gctx)
-	return func() {
-		shutdown()
-		cmd_device.SetGlobalContext(nil)
-		gctx = nil
-	}
-}
diff --git a/services/device/deviced/server.go b/services/device/deviced/server.go
index 61e8773..01c378e 100644
--- a/services/device/deviced/server.go
+++ b/services/device/deviced/server.go
@@ -47,7 +47,7 @@
 )
 
 func init() {
-	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^(name)|(restart-exit-code)|(neighborhood-name)|(deviced-port)|(proxy-port)|(use-pairing-token)$`))
+	cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((name)|(restart-exit-code)|(neighborhood-name)|(deviced-port)|(proxy-port)|(use-pairing-token))$`))
 }
 
 func runServer(*cmdline.Command, []string) error {
diff --git a/services/profile/profile/doc.go b/services/profile/profile/doc.go
index 51ea9ca..d5281d8 100644
--- a/services/profile/profile/doc.go
+++ b/services/profile/profile/doc.go
@@ -114,11 +114,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:
    profile help [flags] [command/topic ...]
 
@@ -131,5 +126,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/profile/profile/impl.go b/services/profile/profile/impl.go
index 1bed93e..e4cb9f3 100644
--- a/services/profile/profile/impl.go
+++ b/services/profile/profile/impl.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 .
+
 package main
 
 import (
@@ -10,17 +13,23 @@
 
 	"v.io/v23/context"
 	"v.io/v23/services/build"
-	"v.io/x/lib/cmdline"
+	"v.io/x/lib/cmdline2"
+	"v.io/x/ref/lib/v23cmd"
+	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/profile"
 	"v.io/x/ref/services/repository"
 )
 
-func init() {
-	cmdline.HideGlobalFlagsExcept()
+func main() {
+	cmdline2.Main(cmdRoot)
 }
 
-var cmdLabel = &cmdline.Command{
-	Run:      runLabel,
+func init() {
+	cmdline2.HideGlobalFlagsExcept()
+}
+
+var cmdLabel = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runLabel),
 	Name:     "label",
 	Short:    "Shows a human-readable profile key for the profile.",
 	Long:     "Shows a human-readable profile key for the profile.",
@@ -28,24 +37,24 @@
 	ArgsLong: "<profile> is the full name of the profile.",
 }
 
-func runLabel(cmd *cmdline.Command, args []string) error {
+func runLabel(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p := repository.ProfileClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	label, err := p.Label(ctx)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), label)
+	fmt.Fprintln(env.Stdout, label)
 	return nil
 }
 
-var cmdDescription = &cmdline.Command{
-	Run:      runDescription,
+var cmdDescription = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runDescription),
 	Name:     "description",
 	Short:    "Shows a human-readable profile description for the profile.",
 	Long:     "Shows a human-readable profile description for the profile.",
@@ -53,24 +62,24 @@
 	ArgsLong: "<profile> is the full name of the profile.",
 }
 
-func runDescription(cmd *cmdline.Command, args []string) error {
+func runDescription(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p := repository.ProfileClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	desc, err := p.Description(ctx)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), desc)
+	fmt.Fprintln(env.Stdout, desc)
 	return nil
 }
 
-var cmdSpecification = &cmdline.Command{
-	Run:      runSpecification,
+var cmdSpecification = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runSpecification),
 	Name:     "specification",
 	Short:    "Shows the specification of the profile.",
 	Long:     "Shows the specification of the profile.",
@@ -78,24 +87,24 @@
 	ArgsLong: "<profile> is the full name of the profile.",
 }
 
-func runSpecification(cmd *cmdline.Command, args []string) error {
+func runSpecification(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("specification: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("specification: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p := repository.ProfileClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	spec, err := p.Specification(ctx)
 	if err != nil {
 		return err
 	}
-	fmt.Fprintf(cmd.Stdout(), "%#v\n", spec)
+	fmt.Fprintf(env.Stdout, "%#v\n", spec)
 	return nil
 }
 
-var cmdPut = &cmdline.Command{
-	Run:      runPut,
+var cmdPut = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runPut),
 	Name:     "put",
 	Short:    "Sets a placeholder specification for the profile.",
 	Long:     "Sets a placeholder specification for the profile.",
@@ -103,9 +112,9 @@
 	ArgsLong: "<profile> is the full name of the profile.",
 }
 
-func runPut(cmd *cmdline.Command, args []string) error {
+func runPut(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p := repository.ProfileClient(name)
@@ -119,17 +128,17 @@
 		Label:       "example",
 		Os:          build.OperatingSystemLinux,
 	}
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	if err := p.Put(ctx, spec); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Profile added successfully.")
+	fmt.Fprintln(env.Stdout, "Profile added successfully.")
 	return nil
 }
 
-var cmdRemove = &cmdline.Command{
-	Run:      runRemove,
+var cmdRemove = &cmdline2.Command{
+	Runner:   v23cmd.RunnerFunc(runRemove),
 	Name:     "remove",
 	Short:    "removes the profile specification for the profile.",
 	Long:     "removes the profile specification for the profile.",
@@ -137,28 +146,26 @@
 	ArgsLong: "<profile> is the full name of the profile.",
 }
 
-func runRemove(cmd *cmdline.Command, args []string) error {
+func runRemove(ctx *context.T, env *cmdline2.Env, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+		return env.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p := repository.ProfileClient(name)
-	ctx, cancel := context.WithTimeout(gctx, time.Minute)
+	ctx, cancel := context.WithTimeout(ctx, time.Minute)
 	defer cancel()
 	if err := p.Remove(ctx); err != nil {
 		return err
 	}
-	fmt.Fprintln(cmd.Stdout(), "Profile removed successfully.")
+	fmt.Fprintln(env.Stdout, "Profile removed successfully.")
 	return nil
 }
 
-func root() *cmdline.Command {
-	return &cmdline.Command{
-		Name:  "profile",
-		Short: "manages the Vanadium profile repository",
-		Long: `
+var cmdRoot = &cmdline2.Command{
+	Name:  "profile",
+	Short: "manages the Vanadium profile repository",
+	Long: `
 Command profile manages the Vanadium profile repository.
 `,
-		Children: []*cmdline.Command{cmdLabel, cmdDescription, cmdSpecification, cmdPut, cmdRemove},
-	}
+	Children: []*cmdline2.Command{cmdLabel, cmdDescription, cmdSpecification, cmdPut, cmdRemove},
 }
diff --git a/services/profile/profile/impl_test.go b/services/profile/profile/impl_test.go
index d40090f..9a13e99 100644
--- a/services/profile/profile/impl_test.go
+++ b/services/profile/profile/impl_test.go
@@ -17,7 +17,9 @@
 	"v.io/v23/security"
 	"v.io/v23/services/build"
 
+	"v.io/x/lib/cmdline2"
 	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/v23cmd"
 	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/profile"
 	"v.io/x/ref/services/repository"
@@ -115,23 +117,21 @@
 }
 
 func TestProfileClient(t *testing.T) {
-	var shutdown v23.Shutdown
-	gctx, shutdown = test.InitForTest()
+	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
-	server, endpoint, err := startServer(t, gctx)
+	server, endpoint, err := startServer(t, ctx)
 	if err != nil {
 		return
 	}
 	defer stopServer(t, server)
 	// Setup the command-line.
-	cmd := root()
 	var stdout, stderr bytes.Buffer
-	cmd.Init(nil, &stdout, &stderr)
+	env := &cmdline2.Env{Stdout: &stdout, Stderr: &stderr}
 	exists := naming.JoinAddressName(endpoint.String(), "exists")
 
 	// Test the 'label' command.
-	if err := cmd.Execute([]string{"label", exists}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"label", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := spec.Label, strings.TrimSpace(stdout.String()); got != expected {
@@ -140,7 +140,7 @@
 	stdout.Reset()
 
 	// Test the 'description' command.
-	if err := cmd.Execute([]string{"description", exists}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"description", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := spec.Description, strings.TrimSpace(stdout.String()); got != expected {
@@ -149,7 +149,7 @@
 	stdout.Reset()
 
 	// Test the 'spec' command.
-	if err := cmd.Execute([]string{"specification", exists}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"specification", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := fmt.Sprintf("%#v", spec), strings.TrimSpace(stdout.String()); got != expected {
@@ -158,7 +158,7 @@
 	stdout.Reset()
 
 	// Test the 'put' command.
-	if err := cmd.Execute([]string{"put", exists}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"put", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Profile added successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -167,7 +167,7 @@
 	stdout.Reset()
 
 	// Test the 'remove' command.
-	if err := cmd.Execute([]string{"remove", exists}); err != nil {
+	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"remove", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Profile removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/profile/profile/main.go b/services/profile/profile/main.go
deleted file mode 100644
index e33dc99..0000000
--- a/services/profile/profile/main.go
+++ /dev/null
@@ -1,27 +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 .
-
-package main
-
-import (
-	"os"
-
-	"v.io/v23"
-	"v.io/v23/context"
-
-	_ "v.io/x/ref/profiles"
-)
-
-var gctx *context.T
-
-func main() {
-	var shutdown v23.Shutdown
-	gctx, shutdown = v23.Init()
-	exitCode := root().Main()
-	shutdown()
-	os.Exit(exitCode)
-}