Repository: tetratelabs/getenvoy
Pull request #218 (2021-05-14 13:00:12)

Changes to urfave CLI, implements version command and one-page doc
This changes to urfave CLI, which is easier to work with than Cobra.

Pros of both:
* long source history, large amount of stars, reasonably frequent releases
* support the platforms we need (macOS, Windows, linux)
* support things we need in the future like auto-completion #115 

Pros of urfave:
* significantly less indirect dependencies (283 less lines in go.sum)
* supports ENV variables without introducing another dependency
* cleaner support for go context (haven't used it yet)
* less awkward function names and behaviors (ex. `Before` instead of `PersistentPreRunE`)
* simpler help output and markdown (ex single page vs multi-page)
* slightly smaller binary (They even [measure](https://github.com/urfave/cli/blob/45952a7d1b0e956db2099493fe1a42837ff5bd0d/internal/build/build.go#L186-L202) it!)

Cons of urfave
* doesn't yet support [argument metadata](https://github.com/urfave/cli/issues/1074)
  * We only have two args, the `<reference>` and then anything passed to envoy `-- .*`, so this isn't a biggie

https://github.com/urfave/cli/blob/master/docs/v2/manual.md

codefromthecrypt: I will try to finish this tomorrow morning asia time
codefromthecrypt: once this is merged, I will help simplify the getenvoy-ui usage of "getenvoy doc" to emit the now much simpler single page. This should suffice, but if we later feel we want to edit it, we can override the template like so https://github.com/google/skia-buildbot/blob/4c87209ff9378a9befc65d5f93c4040d4fdd5922/go/urfavecli/urfavecli.go#L34 eventually, we should make markdown completely driven by the UI project instead of a hidden command as mentioned in #205 
codefromthecrypt: I have a WIP branch for the website that correctly renders now https://github.com/tetratelabs/getenvoy.io/compare/new-usage

![Screen Shot 2021-05-21 at 9 28 09 AM](https://user-images.githubusercontent.com/64215/119068486-e3deac00-ba16-11eb-8bcb-1dcdb9cf37d0.png)


Patch:
diff --git a/cmd/getenvoy/main.go b/cmd/getenvoy/main.go
index 5f18a09..5714be6 100644
--- a/cmd/getenvoy/main.go
+++ b/cmd/getenvoy/main.go
@@ -19,6 +19,8 @@ import (
 	"io"
 	"os"
 
+	"github.com/urfave/cli/v2"
+
 	cmdutil "github.com/tetratelabs/getenvoy/pkg/cmd"
 	"github.com/tetratelabs/getenvoy/pkg/globals"
 )
@@ -30,12 +32,22 @@ func main() {
 // run handles all error logging and coding so that no other place needs to.
 func run(stdout, stderr io.Writer, args []string) int {
 	app := cmdutil.NewApp(&globals.GlobalOpts{Out: stdout})
-	app.SetArgs(args[1:])
-	app.SetOut(stdout)
-	app.SetErr(stderr)
-	if err := app.Execute(); err != nil {
+	app.Writer = stdout
+	app.ErrWriter = stderr
+	app.Action = func(c *cli.Context) error {
+		command := c.Args().First()
+		if command == "" { // Show help by default
+			return cli.ShowSubcommandHelp(c)
+		}
+		return cmdutil.NewValidationError("unknown command %q", command)
+	}
+	app.OnUsageError = func(c *cli.Context, err error, isSub bool) error {
+		return cmdutil.NewValidationError(err.Error())
+	}
+	if err := app.Run(args); err != nil {
 		if _, ok := err.(*cmdutil.ValidationError); ok {
-			logUsageError(app.Name(), err, stderr)
+			fmt.Fprintln(stderr, err) //nolint
+			logUsageError(app.Name, stderr)
 		} else {
 			fmt.Fprintln(stderr, "error:", err) //nolint
 		}
@@ -44,7 +56,6 @@ func run(stdout, stderr io.Writer, args []string) int {
 	return 0
 }
 
-func logUsageError(name string, err error, stderr io.Writer) {
-	fmt.Fprintln(stderr, err)                            //nolint
+func logUsageError(name string, stderr io.Writer) {
 	fmt.Fprintln(stderr, "show usage with:", name, "-h") //nolint
 }
diff --git a/go.mod b/go.mod
index d6df720..b4660db 100644
--- a/go.mod
+++ b/go.mod
@@ -10,10 +10,10 @@ require (
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/schollz/progressbar/v3 v3.8.0
 	github.com/shirou/gopsutil/v3 v3.21.4
-	github.com/spf13/cobra v1.1.3
 	github.com/stretchr/testify v1.7.0
 	github.com/tklauser/go-sysconf v0.3.5 // indirect
 	github.com/ulikunitz/xz v0.5.10
+	github.com/urfave/cli/v2 v2.3.0
 	golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
 	golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
 	golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
diff --git a/go.sum b/go.sum
index 4446ce3..b2d4cea 100644
--- a/go.sum
+++ b/go.sum
@@ -1,272 +1,57 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
 github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
 github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/schollz/progressbar/v3 v3.8.0 h1:BKyefEMgFBDbo+JaeqHcm/9QdSj8qG8sUY+6UppGpnw=
 github.com/schollz/progressbar/v3 v3.8.0/go.mod h1:Y9mmL2knZj3LUaBDyBEzFdPrymIr08hnlFMZmfxwbx4=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/shirou/gopsutil/v3 v3.21.4 h1:XB/+p+kVnyYLuPHCfa99lxz2aJyvVhnyd+FxZqH/k7M=
 github.com/shirou/gopsutil/v3 v3.21.4/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
-github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
 github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
 github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
 github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
 github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
 github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
 github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
+github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
 golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -282,66 +67,10 @@ golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9sn
 golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
 golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/pkg/cmd/app.go b/pkg/cmd/app.go
index 91dc5e4..3a01fcd 100644
--- a/pkg/cmd/app.go
+++ b/pkg/cmd/app.go
@@ -16,11 +16,10 @@ package cmd
 
 import (
 	"net/url"
-	"os"
 	"os/user"
 	"path/filepath"
 
-	"github.com/spf13/cobra"
+	"github.com/urfave/cli/v2"
 
 	"github.com/tetratelabs/getenvoy/internal/version"
 	"github.com/tetratelabs/getenvoy/pkg/globals"
@@ -29,59 +28,57 @@ import (
 
 // NewApp create a new root command. The globals.GlobalOpts parameter allows tests to scope overrides, which avoids
 // having to define a flag for everything needed in tests.
-func NewApp(o *globals.GlobalOpts) *cobra.Command {
-	homeDir, manifestURL := initializeGlobalOpts(o)
+func NewApp(o *globals.GlobalOpts) *cli.App {
+	var homeDir, manifestURL string
 
-	rootCmd := &cobra.Command{
-		Use:               "getenvoy",
-		SilenceErrors:     true, // We can't adjust the error message on Ctrl+C, so we redo error logging in Execute
-		SilenceUsage:      true, // We decided to return short usage form on error in Execute
-		DisableAutoGenTag: true, // removes autogenerate on ___ from produced docs
-		Short:             "Fetch and run Envoy",
-		Long:              `Manage Envoy lifecycle including fetching binaries and collection of process state.`,
-		Version:           version.Current,
-		PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
-			if err := setHomeDir(o, homeDir); err != nil {
-				return err
-			}
-			return setManifestURL(o, manifestURL)
+	app := cli.NewApp()
+	app.Name = "getenvoy"
+	app.HelpName = "getenvoy"
+	app.HideHelpCommand = true
+	app.Usage = `Download and run Envoy`
+	app.Version = version.Current
+	app.Flags = []cli.Flag{
+		&cli.StringFlag{
+			Name:        "home-dir",
+			Usage:       "GetEnvoy home directory (location of downloaded artifacts, caches, etc)",
+			DefaultText: globals.DefaultHomeDir,
+			Destination: &homeDir,
+			EnvVars:     []string{"GETENVOY_HOME"},
 		},
+		&cli.StringFlag{
+			Name:        "manifest",
+			Usage:       "GetEnvoy manifest URL (list of available Envoy builds)",
+			Hidden:      true,
+			DefaultText: globals.DefaultManifestURL,
+			Destination: &manifestURL,
+			EnvVars:     []string{"GETENVOY_MANIFEST_URL"},
+		}}
+	app.Before = func(c *cli.Context) error {
+		if err := setHomeDir(o, homeDir); err != nil {
+			return err
+		}
+		return setManifestURL(o, manifestURL)
 	}
 
-	rootCmd.AddCommand(NewRunCmd(o))
-	rootCmd.AddCommand(NewListCmd(o))
-	rootCmd.AddCommand(NewFetchCmd(o))
-	rootCmd.AddCommand(NewDocCmd())
-	rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) // only support -h
-
-	rootCmd.PersistentFlags().StringVar(&homeDir, "home-dir", homeDir,
-		"GetEnvoy home directory (location of downloaded artifacts, caches, etc)")
-
-	rootCmd.PersistentFlags().StringVar(&manifestURL, "manifest", manifestURL,
-		"GetEnvoy manifest URL (source of information about available Envoy builds)")
-	rootCmd.PersistentFlags().MarkHidden("manifest") // nolint
-	return rootCmd
-}
-
-func initializeGlobalOpts(o *globals.GlobalOpts) (homeDir, manifestURL string) {
-	if o.HomeDir == "" { // don't lookup homedir when overridden for tests
-		homeDir = os.Getenv("GETENVOY_HOME")
-	}
-
-	if o.ManifestURL == "" {
-		manifestURL = os.Getenv("GETENVOY_MANIFEST_URL")
+	app.Commands = []*cli.Command{
+		NewRunCmd(o),
+		NewListCmd(o),
+		NewFetchCmd(o),
+		NewDocCmd(),
 	}
-	return
+	return app
 }
 
 func setManifestURL(o *globals.GlobalOpts, manifestURL string) error {
-	if o.ManifestURL == "" { // not overridden for tests
-		if manifestURL == "" {
-			manifestURL = globals.DefaultManifestURL
-		}
+	if o.ManifestURL != "" { // overridden for tests
+		return nil
+	}
+	if manifestURL == "" {
+		o.ManifestURL = globals.DefaultManifestURL
+	} else {
 		otherURL, err := url.Parse(manifestURL)
 		if err != nil || otherURL.Host == "" || otherURL.Scheme == "" {
-			return newValidationError("%q is not a valid manifest URL", manifestURL)
+			return NewValidationError("%q is not a valid manifest URL", manifestURL)
 		}
 		o.ManifestURL = manifestURL
 	}
@@ -89,29 +86,31 @@ func setManifestURL(o *globals.GlobalOpts, manifestURL string) error {
 }
 
 func setHomeDir(o *globals.GlobalOpts, homeDir string) error {
-	if o.HomeDir == "" { // not overridden for tests
-		if homeDir == "" {
-			u, err := user.Current()
-			if err != nil || u.HomeDir == "" {
-				return newValidationError("unable to determine home directory. Set GETENVOY_HOME instead: %v", err)
-			}
-			homeDir = filepath.Join(u.HomeDir, ".getenvoy")
+	if o.HomeDir != "" { // overridden for tests
+		return nil
+	}
+	if homeDir == "" {
+		u, err := user.Current()
+		if err != nil || u.HomeDir == "" {
+			return NewValidationError("unable to determine home directory. Set GETENVOY_HOME instead: %v", err)
 		}
+		o.HomeDir = filepath.Join(u.HomeDir, ".getenvoy")
+	} else {
 		abs, err := filepath.Abs(homeDir)
 		if err != nil {
-			return newValidationError(err.Error())
+			return NewValidationError(err.Error())
 		}
 		o.HomeDir = abs
 	}
 	return nil
 }
 
-func validateReferenceArg(_ *cobra.Command, args []string) error {
-	if len(args) == 0 {
-		return newValidationError("missing reference parameter")
+func validateReferenceArg(c *cli.Context) error {
+	if c.NArg() == 0 {
+		return NewValidationError("missing reference parameter")
 	}
-	if _, e := manifest.ParseReference(args[0]); e != nil {
-		return newValidationError(e.Error())
+	if _, e := manifest.ParseReference(c.Args().First()); e != nil {
+		return NewValidationError(e.Error())
 	}
 	return nil
 }
diff --git a/pkg/cmd/doc.go b/pkg/cmd/doc.go
index fb7dc17..6c96659 100644
--- a/pkg/cmd/doc.go
+++ b/pkg/cmd/doc.go
@@ -16,56 +16,26 @@ package cmd
 
 import (
 	"fmt"
-	"path"
-	"path/filepath"
 	"strings"
 
-	"github.com/spf13/cobra"
-	"github.com/spf13/cobra/doc"
-)
-
-var (
-	outputDir string
-	linkDir   string
+	"github.com/urfave/cli/v2"
 )
 
 // NewDocCmd returns command that generates documentation
-func NewDocCmd() *cobra.Command {
-	cmd := &cobra.Command{
-		Use:    "doc",
-		Short:  "Generates Markdown documentation for the CLI.",
+func NewDocCmd() *cli.Command {
+	cmd := &cli.Command{
+		Name:   "doc",
+		Usage:  "Generates Markdown documentation for the CLI.",
 		Hidden: true,
-		RunE: func(cmd *cobra.Command, _ []string) error {
-			getenvoy := cmd.Parent()
-			return doc.GenMarkdownTreeCustom(getenvoy, outputDir, filePrepender, linkHandler)
+		Action: func(c *cli.Context) error {
+			m, err := c.App.ToMarkdown()
+			m = strings.ReplaceAll(m, "% getenvoy 8\n\n", "") // remove man header until urfave/cli#1275
+			if err != nil {
+				return err
+			}
+			_, err = fmt.Fprintln(c.App.Writer, m)
+			return err
 		},
 	}
-	cmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "directory to create generated docs")
-	cmd.PersistentFlags().StringVarP(&linkDir, "link", "l", "", "directory to prepend to filename in links")
 	return cmd
 }
-
-func filePrepender(filename string) string {
-	name := filepath.Base(filename)
-	base := strings.TrimSuffix(name, path.Ext(name))
-	split := strings.Split(base, "_")
-	parent := "root"
-	if len(split) > 1 {
-		parent = split[len(split)-2]
-	}
-	command := split[len(split)-1]
-	return fmt.Sprintf(fmTemplate, strings.Join(split, " "), parent, command)
-}
-
-const fmTemplate = `+++
-title = "%s"
-type = "reference"
-parent = "%s"
-command = "%s"
-+++
-`
-
-func linkHandler(name string) string {
-	base := strings.TrimSuffix(name, path.Ext(name))
-	return filepath.Join(linkDir, strings.ToLower(base))
-}
diff --git a/pkg/cmd/errors.go b/pkg/cmd/errors.go
index b41b975..f2d5160 100644
--- a/pkg/cmd/errors.go
+++ b/pkg/cmd/errors.go
@@ -16,7 +16,8 @@ package cmd
 
 import "fmt"
 
-func newValidationError(format string, a ...interface{}) error {
+// NewValidationError generates an error with a given format string.
+func NewValidationError(format string, a ...interface{}) error {
 	return &ValidationError{fmt.Sprintf(format, a...)}
 }
 
diff --git a/pkg/cmd/fetch.go b/pkg/cmd/fetch.go
index 20698c7..931e59f 100644
--- a/pkg/cmd/fetch.go
+++ b/pkg/cmd/fetch.go
@@ -17,26 +17,31 @@ package cmd
 import (
 	"fmt"
 
-	"github.com/spf13/cobra"
+	"github.com/urfave/cli/v2"
 
-	"github.com/tetratelabs/getenvoy/internal/reference"
+	internalreference "github.com/tetratelabs/getenvoy/internal/reference"
 	"github.com/tetratelabs/getenvoy/pkg/binary/envoy"
 	"github.com/tetratelabs/getenvoy/pkg/globals"
 )
 
 // NewFetchCmd create a command responsible for retrieving Envoy binaries
-func NewFetchCmd(o *globals.GlobalOpts) *cobra.Command {
-	return &cobra.Command{
-		Use:   "fetch <reference>",
-		Short: "Downloads a version of Envoy. Available builds can be retrieved using `getenvoy list`.",
-		Example: fmt.Sprintf(`# Fetch using a partial manifest reference to retrieve a build suitable for your operating system.
-getenvoy fetch %[1]s
-
-# Fetch using a full manifest reference to retrieve a specific build for Linux.
-getenvoy fetch %[1]s/linux-glibc`, reference.Latest),
-		Args: validateReferenceArg,
-		RunE: func(cmd *cobra.Command, args []string) error {
-			_, err := envoy.FetchIfNeeded(o, args[0])
+func NewFetchCmd(o *globals.GlobalOpts) *cli.Command {
+	return &cli.Command{
+		Name:      "fetch",
+		Usage:     "Download a build of Envoy",
+		ArgsUsage: "<reference>",
+		Description: fmt.Sprintf(`The '<reference>' minimally includes the Envoy version.
+
+Example: Prefetch Envoy prior to invoking `+"`getenvoy run`"+`
+$ getenvoy fetch %[1]s
+
+Example: Fetch Envoy to run on a specific platform
+$ getenvoy fetch %[1]s/linux-glibc
+
+To view all available builds, invoke the "list" command.`, internalreference.Latest),
+		Before: validateReferenceArg,
+		Action: func(c *cli.Context) error {
+			_, err := envoy.FetchIfNeeded(o, c.Args().First())
 			return err
 		},
 	}
diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go
index e55193d..d400f46 100644
--- a/pkg/cmd/list.go
+++ b/pkg/cmd/list.go
@@ -17,18 +17,18 @@ package cmd
 import (
 	"os"
 
-	"github.com/spf13/cobra"
+	"github.com/urfave/cli/v2"
 
 	"github.com/tetratelabs/getenvoy/pkg/globals"
 	"github.com/tetratelabs/getenvoy/pkg/manifest"
 )
 
 // NewListCmd returns command that lists available Envoy binaries
-func NewListCmd(o *globals.GlobalOpts) *cobra.Command {
-	return &cobra.Command{
-		Use:   "list",
-		Short: "List available Envoy version references you can run",
-		RunE: func(cmd *cobra.Command, _ []string) error {
+func NewListCmd(o *globals.GlobalOpts) *cli.Command {
+	return &cli.Command{
+		Name:  "list",
+		Usage: "List available Envoy builds",
+		Action: func(c *cli.Context) error {
 			m, err := manifest.FetchManifest(o.ManifestURL)
 			if err != nil {
 				return err
diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go
index 9456c6f..cf23c7c 100644
--- a/pkg/cmd/run.go
+++ b/pkg/cmd/run.go
@@ -21,7 +21,7 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/spf13/cobra"
+	"github.com/urfave/cli/v2"
 
 	internalreference "github.com/tetratelabs/getenvoy/internal/reference"
 	"github.com/tetratelabs/getenvoy/pkg/binary/envoy"
@@ -30,36 +30,54 @@ import (
 )
 
 // NewRunCmd create a command responsible for starting an Envoy process
-func NewRunCmd(o *globals.GlobalOpts) *cobra.Command {
-	cmd := &cobra.Command{
-		Use:   "run reference [flags] [-- <envoy-args>]",
-		Short: "Runs Envoy and collects process state on exit. Available builds can be retrieved using `getenvoy list`.",
-		Example: fmt.Sprintf(`# Run using a manifest reference.
-getenvoy run %[1]s -- --config-path ./bootstrap.yaml
+func NewRunCmd(o *globals.GlobalOpts) *cli.Command {
+	cmd := &cli.Command{
+		Name:      "run",
+		Usage:     "Run Envoy and collect process state on exit",
+		ArgsUsage: "<reference> -- <envoy-args>",
+		Description: fmt.Sprintf(`The '<reference>' minimally includes the Envoy version.
+The '<envoy-args>' are interpreted by Envoy.
 
-# List available Envoy flags.
-getenvoy run %[1]s -- --help
-`, internalreference.Latest),
-		Args: validateReferenceArg,
-		RunE: func(c *cobra.Command, args []string) error {
+Example:
+$ getenvoy run %[1]s -- --config-path ./bootstrap.yaml
+
+To view all available builds, invoke the "list" command.`, internalreference.Latest),
+		Before: validateReferenceArg,
+		Action: func(c *cli.Context) error {
+			args := c.Args().Slice()
 			if err := initializeRunOpts(o, args[0]); err != nil {
 				return err
 			}
 			r := envoy.NewRuntime(&o.RunOpts)
-			r.Out = c.OutOrStderr()
-			r.Err = c.ErrOrStderr()
+
+			r.Out = c.App.Writer
+			r.Err = c.App.ErrWriter
 
 			for _, err := range debug.EnableAll(r) {
 				fmt.Fprintln(r.Out, "failed to enable debug option:", err) //nolint
 			}
 
-			envoyArgs := args[1:]
-			return r.Run(c.Context(), envoyArgs)
+			envoyArgs := findEnvoyArgs(args)
+			return r.Run(c.Context, envoyArgs)
 		},
 	}
 	return cmd
 }
 
+// findEnvoyArgs returns any args after "--"
+func findEnvoyArgs(args []string) []string {
+	var envoyArgs []string
+	for i, v := range args {
+		if v == "--" {
+			if len(args) > i+1 {
+				return args[i+1:]
+			}
+			break
+		}
+	}
+	return envoyArgs
+}
+
 // initializeRunOpts allows us to default values when not overridden for tests.
 // The reference parameter corresponds to the globals.GlobalOpts EnvoyPath which is fetched if needed.
 // Notably, this creates and sets a globals.GlobalOpts WorkingDirectory for Envoy, and any files that precede it.
@@ -68,7 +86,7 @@ func initializeRunOpts(o *globals.GlobalOpts, reference string) error {
 	if o.EnvoyPath == "" { // not overridden for tests
 		envoyPath, err := envoy.FetchIfNeeded(o, reference)
 		if err != nil {
-			return newValidationError(err.Error())
+			return NewValidationError(err.Error())
 		}
 		o.EnvoyPath = envoyPath
 	}
@@ -79,7 +97,7 @@ func initializeRunOpts(o *globals.GlobalOpts, reference string) error {
 
 		// When the directory is implicitly generated, we should create it to avoid late errors.
 		if err := os.MkdirAll(workingDir, 0750); err != nil {
-			return newValidationError("unable to create working directory %q, so we cannot run envoy", workingDir)
+			return NewValidationError("unable to create working directory %q, so we cannot run envoy", workingDir)
 		}
 		runOpts.WorkingDir = workingDir
 	}
diff --git a/pkg/globals/globals.go b/pkg/globals/globals.go
index 5494126..99f3c83 100644
--- a/pkg/globals/globals.go
+++ b/pkg/globals/globals.go
@@ -34,17 +34,21 @@ type RunOpts struct {
 //  1) value that precedes flag parsing, used in tests
 //  2) to a value of the command line argument, e.g. `--home-dir`
 //  3) optional mapping to an environment variable, e.g. `GETENVOY_HOME` (not all flags are mapped to ENV)
-//  4) otherwise, to the default value, e.g. `${HOME}/.getenvoy`
+//  4) otherwise, to the default value, e.g. DefaultHomeDir
 type GlobalOpts struct {
 	// RunOpts are inlined to allow tests to override parameters without changing ENV variables or flags
 	RunOpts
-	// ManifestURL is the path to the getenvoy manifest json
+	// ManifestURL is the path to the getenvoy manifest json. Defaults to DefaultManifestURL
 	ManifestURL string
-	// HomeDir most importantly contains envoy binaries fetched from ManifestURL. Defaults to $HOME/.getenvoy
+	// HomeDir most importantly contains envoy binaries fetched from ManifestURL. Defaults to DefaultHomeDir
 	HomeDir string
-	// Out is where status messages are written. Commands should prefix "$name: ". Defaults to os.Stdout
+	// Out is where status messages are written. Defaults to os.Stdout
 	Out io.Writer
 }
 
-// DefaultManifestURL is the default value for GlobalOpts.ManifestURL
-const DefaultManifestURL = "https://dl.getenvoy.io/public/raw/files/manifest.json"
+const (
+	// DefaultHomeDir is the default value for GlobalOpts.HomeDir
+	DefaultHomeDir = "${HOME}/.getenvoy"
+	// DefaultManifestURL is the default value for GlobalOpts.ManifestURL
+	DefaultManifestURL = "https://dl.getenvoy.io/public/raw/files/manifest.json"
+)


Test patch:
diff --git a/cmd/getenvoy/main_test.go b/cmd/getenvoy/main_test.go
index ab7f0c5..81d94c7 100644
--- a/cmd/getenvoy/main_test.go
+++ b/cmd/getenvoy/main_test.go
@@ -46,7 +46,8 @@ func TestRunErrors(t *testing.T) {
 			name:           "incorrect global flag name",
 			args:           []string{"getenvoy", "--d"},
 			expectedStatus: 1,
-			expectedStderr: `error: unknown flag: --d
+			expectedStderr: `flag provided but not defined: -d
+show usage with: getenvoy -h
 `,
 		},
 		{
@@ -61,7 +62,8 @@ show usage with: getenvoy -h
 			name:           "unknown command",
 			args:           []string{"getenvoy", "fly"},
 			expectedStatus: 1,
-			expectedStderr: `error: unknown command "fly" for "getenvoy"
+			expectedStderr: `unknown command "fly"
+show usage with: getenvoy -h
 `,
 		},
 		{
diff --git a/internal/reference/latest.go b/internal/reference/latest.go
index 422f63f..ee56500 100644
--- a/internal/reference/latest.go
+++ b/internal/reference/latest.go
@@ -19,7 +19,6 @@ import (
 	_ "embed"
 )
 
-// Latest includes the default version in manifest.NewKey format. This is embedded for re-use in build and CI
-// scripts.
+// Latest is the default version in manifest.Reference format. This is embedded for re-use in build and CI scripts.
 //go:embed latest.txt
 var Latest string
diff --git a/pkg/cmd/app_test.go b/pkg/cmd/app_test.go
index de43c8f..5f7231f 100644
--- a/pkg/cmd/app_test.go
+++ b/pkg/cmd/app_test.go
@@ -21,8 +21,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/spf13/cobra"
 	"github.com/stretchr/testify/require"
+	"github.com/urfave/cli/v2"
 
 	rootcmd "github.com/tetratelabs/getenvoy/pkg/cmd"
 	"github.com/tetratelabs/getenvoy/pkg/globals"
@@ -45,7 +45,7 @@ func TestGetEnvoyValidateArgs(t *testing.T) {
 		test := test // pin! see https://github.com/kyoh86/scopelint for why
 
 		t.Run(test.name, func(t *testing.T) {
-			err := runTestCommand(t, &globals.GlobalOpts{}, test.args[1:])
+			err := runTestCommand(t, &globals.GlobalOpts{}, test.args)
 			require.EqualError(t, err, test.expectedErr)
 		})
 	}
@@ -102,7 +102,7 @@ func TestGetEnvoyHomeDir(t *testing.T) {
 			}
 
 			o := &globals.GlobalOpts{}
-			err := runTestCommand(t, o, test.args[1:])
+			err := runTestCommand(t, o, test.args)
 
 			require.NoError(t, err)
 			require.Equal(t, test.expected, o.HomeDir)
@@ -158,7 +158,7 @@ func TestGetEnvoyManifest(t *testing.T) {
 			}
 
 			o := &globals.GlobalOpts{}
-			err := runTestCommand(t, o, test.args[1:])
+			err := runTestCommand(t, o, test.args)
 
 			require.NoError(t, err)
 			require.Equal(t, test.expected, o.ManifestURL)
@@ -178,25 +178,23 @@ func requireSetenv(t *testing.T, key, value string) func() {
 }
 
 // newApp initializes a command with buffers for stdout and stderr.
-func newApp(o *globals.GlobalOpts) (c *cobra.Command, stdout, stderr *bytes.Buffer) {
+func newApp(o *globals.GlobalOpts) (c *cli.App, stdout, stderr *bytes.Buffer) {
 	stdout = new(bytes.Buffer)
 	stderr = new(bytes.Buffer)
 	c = rootcmd.NewApp(o)
-	c.SetOut(stdout)
-	c.SetErr(stderr)
-	return c, stdout, stderr
+	c.Name = "getenvoy"
+	c.Writer = stdout
+	c.ErrWriter = stderr
+	return
 }
 
 func runTestCommand(t *testing.T, o *globals.GlobalOpts, args []string) error {
 	c, stdout, stderr := newApp(o)
-	c.AddCommand(&cobra.Command{
-		Use: "test",
-		RunE: func(cmd *cobra.Command, _ []string) error {
-			return nil
-		},
-	})
-	c.SetArgs(append(args, "test"))
-	err := c.Execute()
+	c.Commands = append(c.Commands, &cli.Command{Name: "test", Action: func(_ *cli.Context) error {
+		return nil
+	}})
+
+	err := c.Run(append(args, "test"))
 
 	// Main handles logging of errors, so we expect nothing in stdout or stderr even in error case
 	require.Empty(t, stdout)
diff --git a/pkg/cmd/doc_test.go b/pkg/cmd/doc_test.go
index 5a39325..164b6e1 100644
--- a/pkg/cmd/doc_test.go
+++ b/pkg/cmd/doc_test.go
@@ -24,26 +24,15 @@ import (
 
 	"github.com/tetratelabs/getenvoy/internal/reference"
 	"github.com/tetratelabs/getenvoy/pkg/globals"
-	"github.com/tetratelabs/getenvoy/pkg/test/morerequire"
 )
 
 func TestGetEnvoyDoc(t *testing.T) {
-	tempDir, deleteTempDir := morerequire.RequireNewTempDir(t)
-	defer deleteTempDir()
-
-	c, _, _ := newApp(&globals.GlobalOpts{})
-	c.SetArgs([]string{"doc", "-o", tempDir, "-l", "/reference/"})
-	require.NoError(t, c.Execute())
-
-	for _, file := range []string{"getenvoy.md", "getenvoy_list.md", "getenvoy_fetch.md", "getenvoy_run.md"} {
-		file := file // pin! see https://github.com/kyoh86/scopelint for why
-
-		t.Run(file, func(t *testing.T) {
-			want, err := os.ReadFile(filepath.Join("testdata", file))
-			require.NoError(t, err)
-			have, err := os.ReadFile(filepath.Join(tempDir, file))
-			require.NoError(t, err)
-			require.Equal(t, strings.ReplaceAll(string(want), "ENVOY_VERSION", reference.Latest), string(have))
-		})
-	}
+	c, stdout, stderr := newApp(&globals.GlobalOpts{})
+	err := c.Run([]string{"getenvoy", "doc"})
+	require.NoError(t, err)
+	require.Empty(t, stderr)
+
+	want, err := os.ReadFile(filepath.Join("testdata", "getenvoy.md"))
+	require.NoError(t, err)
+	require.Equal(t, strings.ReplaceAll(string(want), "ENVOY_VERSION", reference.Latest), stdout.String())
 }
diff --git a/pkg/cmd/help_test.go b/pkg/cmd/help_test.go
index d84b15f..e945659 100644
--- a/pkg/cmd/help_test.go
+++ b/pkg/cmd/help_test.go
@@ -32,12 +32,11 @@ func TestGetEnvoyHelp(t *testing.T) {
 		command := command
 		t.Run(command, func(t *testing.T) {
 			c, stdout, _ := newApp(&globals.GlobalOpts{})
-			args := []string{"-h"}
+			args := []string{"getenvoy", "-h"}
 			if command != "" {
-				args = []string{command, "-h"}
+				args = []string{"getenvoy", command, "-h"}
 			}
-			c.SetArgs(args)
-			require.NoError(t, c.Execute())
+			require.NoError(t, c.Run(args))
 
 			expected := "getenvoy_help.txt"
 			if command != "" {
diff --git a/pkg/cmd/run_test.go b/pkg/cmd/run_test.go
index 0aaecc2..78b71f8 100644
--- a/pkg/cmd/run_test.go
+++ b/pkg/cmd/run_test.go
@@ -54,14 +54,13 @@ func TestGetEnvoyRunValidateFlag(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			// Run "getenvoy run"
 			c, stdout, stderr := newApp(&globals.GlobalOpts{})
-			c.SetArgs(test.args[1:])
-			err := c.Execute()
+			err := c.Run(test.args)
 
 			// Verify the command failed with the expected error
-			require.EqualError(t, err, test.expectedErr, `expected an error running [%v]`, c)
+			require.EqualError(t, err, test.expectedErr)
 			// Main handles logging of errors, so we expect nothing in stdout or stderr
-			require.Empty(t, stdout, `expected no stdout running [%v]`, c)
-			require.Empty(t, stderr, `expected no stderr running [%v]`, c)
+			require.Empty(t, stdout)
+			require.Empty(t, stderr)
 		})
 	}
 }
@@ -94,13 +93,12 @@ func TestGetEnvoyRun(t *testing.T) {
 			o, cleanup := setupTest(t)
 			defer cleanup()
 
-			// Run "getenvoy run standard:1.17.1 -- -c envoy.yaml"
+			// Run "getenvoy run 1.17.1 -- -c envoy.yaml"
 			c, stdout, stderr := newApp(&o.GlobalOpts)
-			c.SetArgs(test.args[1:])
-			err := c.Execute()
+			err := c.Run(test.args)
 
 			// Verify the command invoked, passing the correct default commandline
-			require.NoError(t, err, `expected no error running [%v]`, c)
+			require.NoError(t, err)
 
 			// We expect getenvoy to print the context it will run, and Envoy to execute the same, except adding the
 			// --admin-address-path flag
@@ -109,8 +107,8 @@ working directory: %[1]s
 envoy wd: %[1]s
 envoy bin: %[2]s
 envoy args:%[3]s --admin-address-path admin-address.txt`, o.WorkingDir, o.EnvoyPath, test.expectedEnvoyArgs)
-			require.Equal(t, expectedStdout+"\n", stdout.String(), `expected stdout running [%v]`, c)
-			require.Equal(t, "envoy stderr\n", stderr.String(), `expected stderr running [%v]`, c)
+			require.Equal(t, expectedStdout+"\n", stdout.String())
+			require.Equal(t, "envoy stderr\n", stderr.String())
 		})
 	}
 }
@@ -124,16 +122,15 @@ func TestGetEnvoyRunFailWithUnknownVersion(t *testing.T) {
 
 	// Run "getenvoy run unknown"
 	version := "unknown"
-	c.SetArgs([]string{"run", version})
-	err := c.Execute()
+	err := c.Run([]string{"getenvoy", "run", version})
 
 	// Verify the command failed with the expected error.
 	r := version + "/" + o.platform
 	expectedErr := fmt.Sprintf(`unable to find matching GetEnvoy build for reference "%s"`, r)
-	require.EqualError(t, err, expectedErr, `expected an error running [%v]`, c)
+	require.EqualError(t, err, expectedErr)
 	// Main handles logging of errors, so we expect nothing in stdout or stderr
-	require.Empty(t, stdout, `expected no stdout running [%v]`, c)
-	require.Empty(t, stderr, `expected no stderr running [%v]`, c)
+	require.Empty(t, stdout)
+	require.Empty(t, stderr)
 }
 
 type testEnvoyExtensionConfig struct {
diff --git a/pkg/cmd/testdata/getenvoy.md b/pkg/cmd/testdata/getenvoy.md
index 124d301..ab18229 100644
--- a/pkg/cmd/testdata/getenvoy.md
+++ b/pkg/cmd/testdata/getenvoy.md
@@ -1,27 +1,43 @@
-+++
-title = "getenvoy"
-type = "reference"
-parent = "root"
-command = "getenvoy"
-+++
-## getenvoy
+# NAME
 
-Fetch and run Envoy
+getenvoy - Download and run Envoy
 
-### Synopsis
+# SYNOPSIS
 
-Manage Envoy lifecycle including fetching binaries and collection of process state.
+getenvoy
 
-### Options
+```
+[--help|-h]
+[--home-dir]=[value]
+[--version|-v]
+```
+
+**Usage**:
 
 ```
-  -h, --help              help for getenvoy
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
+getenvoy [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
 ```
 
-### SEE ALSO
+# GLOBAL OPTIONS
+
+**--help, -h**: show help
+
+**--home-dir**="": GetEnvoy home directory (location of downloaded artifacts, caches, etc)
+
+**--version, -v**: print the version
+
+
+# COMMANDS
+
+## run
+
+Run Envoy and collect process state on exit
+
+## list
+
+List available Envoy builds
+
+## fetch
 
-* [getenvoy fetch](/reference/getenvoy_fetch)	 - Downloads a version of Envoy. Available builds can be retrieved using `getenvoy list`.
-* [getenvoy list](/reference/getenvoy_list)	 - List available Envoy version references you can run
-* [getenvoy run](/reference/getenvoy_run)	 - Runs Envoy and collects process state on exit. Available builds can be retrieved using `getenvoy list`.
+Download a build of Envoy
 
diff --git a/pkg/cmd/testdata/getenvoy_fetch.md b/pkg/cmd/testdata/getenvoy_fetch.md
deleted file mode 100644
index 9edc9e6..0000000
--- a/pkg/cmd/testdata/getenvoy_fetch.md
+++ /dev/null
@@ -1,40 +0,0 @@
-+++
-title = "getenvoy fetch"
-type = "reference"
-parent = "getenvoy"
-command = "fetch"
-+++
-## getenvoy fetch
-
-Downloads a version of Envoy. Available builds can be retrieved using `getenvoy list`.
-
-```
-getenvoy fetch <reference> [flags]
-```
-
-### Examples
-
-```
-# Fetch using a partial manifest reference to retrieve a build suitable for your operating system.
-getenvoy fetch ENVOY_VERSION
-
-# Fetch using a full manifest reference to retrieve a specific build for Linux.
-getenvoy fetch ENVOY_VERSION/linux-glibc
-```
-
-### Options
-
-```
-  -h, --help   help for fetch
-```
-
-### Options inherited from parent commands
-
-```
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
-```
-
-### SEE ALSO
-
-* [getenvoy](/reference/getenvoy)	 - Fetch and run Envoy
-
diff --git a/pkg/cmd/testdata/getenvoy_fetch_help.txt b/pkg/cmd/testdata/getenvoy_fetch_help.txt
index bdffbab..c8c409e 100644
--- a/pkg/cmd/testdata/getenvoy_fetch_help.txt
+++ b/pkg/cmd/testdata/getenvoy_fetch_help.txt
@@ -1,17 +1,20 @@
-Downloads a version of Envoy. Available builds can be retrieved using `getenvoy list`.
+NAME:
+   getenvoy fetch - Download a build of Envoy
 
-Usage:
-  getenvoy fetch <reference> [flags]
+USAGE:
+   getenvoy fetch [command options] <reference>
 
-Examples:
-# Fetch using a partial manifest reference to retrieve a build suitable for your operating system.
-getenvoy fetch ENVOY_VERSION
+DESCRIPTION:
+   The '<reference>' minimally includes the Envoy version.
+   
+   Example: Prefetch Envoy prior to invoking `getenvoy run`
+   $ getenvoy fetch 1.17.1
+   
+   Example: Fetch Envoy to run on a specific platform
+   $ getenvoy fetch 1.17.1/linux-glibc
+   
+   To view all available builds, invoke the "list" command.
 
-# Fetch using a full manifest reference to retrieve a specific build for Linux.
-getenvoy fetch ENVOY_VERSION/linux-glibc
-
-Flags:
-  -h, --help   help for fetch
-
-Global Flags:
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
+OPTIONS:
+   --help, -h  show help (default: false)
+   
diff --git a/pkg/cmd/testdata/getenvoy_help.txt b/pkg/cmd/testdata/getenvoy_help.txt
index 53c37eb..46108d3 100644
--- a/pkg/cmd/testdata/getenvoy_help.txt
+++ b/pkg/cmd/testdata/getenvoy_help.txt
@@ -1,16 +1,18 @@
-Manage Envoy lifecycle including fetching binaries and collection of process state.
+NAME:
+   getenvoy - Download and run Envoy
 
-Usage:
-  getenvoy [command]
+USAGE:
+   getenvoy [global options] command [command options] [arguments...]
 
-Available Commands:
-  fetch       Downloads a version of Envoy. Available builds can be retrieved using `getenvoy list`.
-  list        List available Envoy version references you can run
-  run         Runs Envoy and collects process state on exit. Available builds can be retrieved using `getenvoy list`.
+VERSION:
+   dev
 
-Flags:
-  -h, --help              help for getenvoy
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
-  -v, --version           version for getenvoy
+COMMANDS:
+   run    Run Envoy and collect process state on exit
+   list   List available Envoy builds
+   fetch  Download a build of Envoy
 
-Use "getenvoy [command] --help" for more information about a command.
+GLOBAL OPTIONS:
+   --home-dir value  GetEnvoy home directory (location of downloaded artifacts, caches, etc) (default: ${HOME}/.getenvoy) [$GETENVOY_HOME]
+   --help, -h        show help (default: false)
+   --version, -v     print the version (default: false)
diff --git a/pkg/cmd/testdata/getenvoy_list.md b/pkg/cmd/testdata/getenvoy_list.md
deleted file mode 100644
index 32fb2d3..0000000
--- a/pkg/cmd/testdata/getenvoy_list.md
+++ /dev/null
@@ -1,30 +0,0 @@
-+++
-title = "getenvoy list"
-type = "reference"
-parent = "getenvoy"
-command = "list"
-+++
-## getenvoy list
-
-List available Envoy version references you can run
-
-```
-getenvoy list [flags]
-```
-
-### Options
-
-```
-  -h, --help   help for list
-```
-
-### Options inherited from parent commands
-
-```
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
-```
-
-### SEE ALSO
-
-* [getenvoy](/reference/getenvoy)	 - Fetch and run Envoy
-
diff --git a/pkg/cmd/testdata/getenvoy_list_help.txt b/pkg/cmd/testdata/getenvoy_list_help.txt
index a2d8228..4961adc 100644
--- a/pkg/cmd/testdata/getenvoy_list_help.txt
+++ b/pkg/cmd/testdata/getenvoy_list_help.txt
@@ -1,10 +1,9 @@
-List available Envoy version references you can run
+NAME:
+   getenvoy list - List available Envoy builds
 
-Usage:
-  getenvoy list [flags]
+USAGE:
+   getenvoy list [command options] [arguments...]
 
-Flags:
-  -h, --help   help for list
-
-Global Flags:
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
+OPTIONS:
+   --help, -h  show help (default: false)
+   
diff --git a/pkg/cmd/testdata/getenvoy_run.md b/pkg/cmd/testdata/getenvoy_run.md
deleted file mode 100644
index 307931b..0000000
--- a/pkg/cmd/testdata/getenvoy_run.md
+++ /dev/null
@@ -1,41 +0,0 @@
-+++
-title = "getenvoy run"
-type = "reference"
-parent = "getenvoy"
-command = "run"
-+++
-## getenvoy run
-
-Runs Envoy and collects process state on exit. Available builds can be retrieved using `getenvoy list`.
-
-```
-getenvoy run reference [flags] [-- <envoy-args>]
-```
-
-### Examples
-
-```
-# Run using a manifest reference.
-getenvoy run ENVOY_VERSION -- --config-path ./bootstrap.yaml
-
-# List available Envoy flags.
-getenvoy run ENVOY_VERSION -- --help
-
-```
-
-### Options
-
-```
-  -h, --help   help for run
-```
-
-### Options inherited from parent commands
-
-```
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
-```
-
-### SEE ALSO
-
-* [getenvoy](/reference/getenvoy)	 - Fetch and run Envoy
-
diff --git a/pkg/cmd/testdata/getenvoy_run_help.txt b/pkg/cmd/testdata/getenvoy_run_help.txt
index 5e284ba..f733052 100644
--- a/pkg/cmd/testdata/getenvoy_run_help.txt
+++ b/pkg/cmd/testdata/getenvoy_run_help.txt
@@ -1,18 +1,18 @@
-Runs Envoy and collects process state on exit. Available builds can be retrieved using `getenvoy list`.
+NAME:
+   getenvoy run - Run Envoy and collect process state on exit
 
-Usage:
-  getenvoy run reference [flags] [-- <envoy-args>]
+USAGE:
+   getenvoy run [command options] <reference> -- <envoy-args>
 
-Examples:
-# Run using a manifest reference.
-getenvoy run ENVOY_VERSION -- --config-path ./bootstrap.yaml
+DESCRIPTION:
+   The '<reference>' minimally includes the Envoy version.
+   The '<envoy-args>' are interpreted by Envoy.
+   
+   Example:
+   $ getenvoy run 1.17.1 -- --config-path ./bootstrap.yaml
+   
+   To view all available builds, invoke the "list" command.
 
-# List available Envoy flags.
-getenvoy run ENVOY_VERSION -- --help
-
-
-Flags:
-  -h, --help   help for run
-
-Global Flags:
-      --home-dir string   GetEnvoy home directory (location of downloaded artifacts, caches, etc)
+OPTIONS:
+   --help, -h  show help (default: false)
+