Repository: openshift-pipelines/pipelines-as-code
Pull request #155 (2021-06-25 14:49:12)

Add a cli for list and describe
* `tkn pac repo list`
* `tkn pac` repo describe repo
* Include completion
* remopve tknresolve binary and expose into `tkn pac resolve`

Demo : 

https://user-images.githubusercontent.com/98980/123408765-ac32c900-d5ad-11eb-83bf-6ef068a787bb.mp4

codecov-commenter: # [Codecov](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> :exclamation: No coverage uploaded for pull request base (`main@bf113db`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#section-missing-base-commit).
> The diff coverage is `39.09%`.

[![Impacted file tree graph](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/graphs/tree.svg?width=650&height=150&src=pr&token=XOBPWX11IU&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@           Coverage Diff           @@
##             main     #155   +/-   ##
=======================================
  Coverage        ?   71.23%           
=======================================
  Files           ?       17           
  Lines           ?      984           
  Branches        ?        0           
=======================================
  Hits            ?      701           
  Misses          ?      202           
  Partials        ?       81           
```


| [Impacted Files](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pkg/cmd/repository/root.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NtZC9yZXBvc2l0b3J5L3Jvb3QuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/config/repo\_runinfo\_matcher.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NvbmZpZy9yZXBvX3J1bmluZm9fbWF0Y2hlci5nbw==) | `92.00% <ø> (ø)` | |
| [pkg/pipelineascode/pipelineascode.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3BpcGVsaW5lYXNjb2RlL3BpcGVsaW5lYXNjb2RlLmdv) | `68.00% <ø> (ø)` | |
| [pkg/pipelineascode/sort\_repository\_status.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3BpcGVsaW5lYXNjb2RlL3NvcnRfcmVwb3NpdG9yeV9zdGF0dXMuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/cmd/repository/list.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NtZC9yZXBvc2l0b3J5L2xpc3QuZ28=) | `36.84% <36.84%> (ø)` | |
| [pkg/cmd/repository/describe.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NtZC9yZXBvc2l0b3J5L2Rlc2NyaWJlLmdv) | `53.57% <53.57%> (ø)` | |
| [pkg/cmd/resolve/resolve.go](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL2NtZC9yZXNvbHZlL3Jlc29sdmUuZ28=) | `75.38% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [bf113db...9805624](https://codecov.io/gh/openshift-pipelines/pipelines-as-code/pull/155?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index fbd0a9d1..b265dc9a 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -28,7 +28,6 @@ linters:
   - gocritic
   - misspell
   - deadcode
-  - gomnd
   - gosec
   - gosimple
   - govet
diff --git a/Dockerfile b/Dockerfile
index 517913ca..9f399a30 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,7 @@ FROM registry.access.redhat.com/ubi8/go-toolset:latest	 AS builder
 COPY . /src
 WORKDIR /src
 RUN go build -mod=vendor -v -o /tmp/pipelines-as-code ./cmd/pipelines-as-code && strip /tmp/pipelines-as-code
+RUN go build -mod=vendor -v -o /tmp/tkn-pac ./cmd/tkn-pac && strip /tmp/tkn-pac
 
 FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
 
@@ -14,4 +15,5 @@ LABEL com.redhat.component=pipelines-as-code \
 LABEL version=0.1.0
 
 COPY --from=builder /tmp/pipelines-as-code /usr/bin/pipelines-as-code
+COPY --from=builder /tmp/tkn-pac /usr/bin/tkn-pac
 CMD ["/usr/bin/pipelines-as-code"]
diff --git a/Makefile b/Makefile
index 7c2a279f..1b91ebfb 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ ifneq ($(FLAGS),)
 	LDFLAGS := -ldflags "$(FLAGS)"
 endif
 
-all: bin/pipelines-as-code test
+all: bin/pipelines-as-code bin/tkn-pac test
 
 FORCE:
 
@@ -61,6 +61,14 @@ test-unit: ./vendor ## run unit tests
 	@echo "Running unit tests..."
 	@go test -failfast $(GO_TEST_FLAGS) ./...
 
+.PHONY: update-golden
+update-golden: ./vendor ## run unit tests (updating golden files)
+	@echo "Running unit tests to update golden files..."
+	@./hack/update-golden.sh
+
+.PHONY: generated
+generated: update-golden fumpt ## generate all files that needs to be generated
+
 .PHONY: html-coverage
 html-coverage: ./vendor ## generate html coverage
 	@mkdir -p tmp
diff --git a/README.md b/README.md
index bcebaa86..171ba70c 100644
--- a/README.md
+++ b/README.md
@@ -212,15 +212,8 @@ tasks are referenced).
 If pipelines as code cannot resolve the referenced tasks in the `Pipeline` or `PipelineSpec` it will fails before applying the pipelinerun onto the cluster.
 
 If you need to test your `PipelineRun` locally before sending it in a PR, you can use
-the `tkresolver` CLI, by installing it like this :
-
-```shell
-go install github.com/openshift-pipelines/pipelines-as-code/cmd/tknresolve
-```
-
-and you can use the tknresolve binary to generate the PipelineRun the say way it
-is generated on events. See the `--help` of the `tknresolve` to learn about how
-this CLI and on how to use it.
+the `resolve` command from the `tkn-pac` CLI See the `--help` of the command to learn about
+how to use it.
 
 #### Remote Task support
 
@@ -390,6 +383,40 @@ Pipelines as Code reuse this for its push pipeline, see it here :
 
 [.tekton/push.yaml](.tekton/push.yaml)
 
+## CLI
+
+`Pipeline as Code` provide a CLI which is design to work as tkn plugin.
+
+Install it like this :
+
+```shell
+go install github.com/openshift-pipelines/pipelines-as-code/cmd/tkn-pac
+```
+
+and you can then see the usage of the commands with :
+
+```shell
+tkn pac help
+```
+
+for example to list all repo status in your system (if you have the right for it) :
+
+```shell
+tkn pac repo ls -A
+```
+
+and to dig into a specific repository status:
+
+```shell
+tkn pac repo desc repository-name -n namespace
+```
+
+Here is a short video :
+
+https://user-images.githubusercontent.com/98980/123408765-ac32c900-d5ad-11eb-83bf-6ef068a787bb.mp4
+
+
+
 ## Setup
 
 You simply need to run this command :
diff --git a/cmd/pipelines-as-code/main.go b/cmd/pipelines-as-code/main.go
index 790512ba..523dd182 100644
--- a/cmd/pipelines-as-code/main.go
+++ b/cmd/pipelines-as-code/main.go
@@ -4,19 +4,12 @@ import (
 	"os"
 
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
-	"github.com/openshift-pipelines/pipelines-as-code/pkg/cmd"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cmd/pipelineascode"
 )
 
 func main() {
 	tp := &cli.PacParams{}
-	pac := cmd.Root(tp)
-
-	cmd, _, err := pac.Find(os.Args[1:])
-	// default cmd if no cmd is given
-	if err != nil || cmd.Use == "" {
-		args := append([]string{"run"}, os.Args[1:]...)
-		pac.SetArgs(args)
-	}
+	pac := pipelineascode.Command(tp)
 
 	if err := pac.Execute(); err != nil {
 		os.Exit(1)
diff --git a/cmd/tkn-pac/main.go b/cmd/tkn-pac/main.go
new file mode 100644
index 00000000..1c93039b
--- /dev/null
+++ b/cmd/tkn-pac/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"os"
+
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/completion"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cmd/repository"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cmd/resolve"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/flags"
+	"github.com/spf13/cobra"
+)
+
+func Root(p cli.Params) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:          "tkn-pac",
+		Short:        "Pipeline as Code CLI",
+		Long:         `This is the Pipelines as Code`,
+		SilenceUsage: true,
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			return flags.InitParams(p, cmd)
+		},
+	}
+	flags.AddPacOptions(cmd)
+	flags.AddPacCliOptions(cmd)
+
+	cmd.AddCommand(repository.Root(p))
+	cmd.AddCommand(resolve.Command(p))
+	cmd.AddCommand(completion.Command())
+
+	return cmd
+}
+
+func main() {
+	tp := &cli.PacParams{}
+	pac := Root(tp)
+
+	if err := pac.Execute(); err != nil {
+		os.Exit(1)
+	}
+}
diff --git a/cmd/tknresolve/main.go b/cmd/tknresolve/main.go
deleted file mode 100644
index cd234b97..00000000
--- a/cmd/tknresolve/main.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package main
-
-import (
-	"os"
-
-	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
-	"github.com/openshift-pipelines/pipelines-as-code/pkg/cmd"
-)
-
-func main() {
-	tp := &cli.PacParams{}
-	tkn := cmd.Root(tp)
-
-	cmd, _, err := tkn.Find(os.Args[1:])
-	// default cmd if no cmd is given
-	if err != nil || cmd.Use == "" {
-		args := append([]string{"resolve"}, os.Args[1:]...)
-		tkn.SetArgs(args)
-	}
-
-	if err := tkn.Execute(); err != nil {
-		os.Exit(1)
-	}
-}
diff --git a/go.mod b/go.mod
index 2f254dcb..aec58371 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,15 @@ module github.com/openshift-pipelines/pipelines-as-code
 go 1.15
 
 require (
+	github.com/briandowns/spinner v1.16.0
 	github.com/gobwas/glob v0.2.3
 	github.com/google/go-cmp v0.5.6
 	github.com/google/go-github/v35 v35.3.0
 	github.com/hako/durafmt v0.0.0-20210601083242-f49dacec7612
 	github.com/jonboulle/clockwork v0.1.1-0.20190114141812-62fb9bc030d1
+	github.com/mattn/go-colorable v0.1.2
+	github.com/mattn/go-isatty v0.0.13
+	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/cobra v1.1.3
 	github.com/tektoncd/pipeline v0.24.3
diff --git a/go.sum b/go.sum
index 05a0c799..31203c39 100644
--- a/go.sum
+++ b/go.sum
@@ -123,6 +123,8 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y
 github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE=
 github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc=
 github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
+github.com/briandowns/spinner v1.16.0 h1:DFmp6hEaIx2QXXuqSJmtfSBSAjRmpGiKG6ip2Wm/yOs=
+github.com/briandowns/spinner v1.16.0/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
 github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@@ -206,6 +208,7 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
 github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@@ -515,13 +518,20 @@ github.com/mailru/easyjson v0.7.1-0.20191009090205-6c0755d89d1e/go.mod h1:KAzv3t
 github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
+github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/miekg/dns v1.1.17/go.mod h1:WgzbA6oji13JREwiNsRDNfl7jYdPnmz+VEuLrA+/48M=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -908,6 +918,7 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190209173611-3b5209105503/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-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/hack/update-golden.sh b/hack/update-golden.sh
new file mode 100755
index 00000000..96cf4821
--- /dev/null
+++ b/hack/update-golden.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+# This will run `go test a/package -test.update-golden=true` on all packages that are importing `gotest.tools/v3/golden`
+# This will update the golden files with the current output.
+# Run this only when you are sure the output is meant to change.
+go test $(go list -f '{{ .ImportPath }} {{ .TestImports }}' ./... | grep gotest.tools/v3/golden | awk '{print $1}' | tr '\n' ' ') -test.update-golden=true
diff --git a/pkg/cli/completion/cmd.go b/pkg/cli/completion/cmd.go
new file mode 100644
index 00000000..98c2aa7c
--- /dev/null
+++ b/pkg/cli/completion/cmd.go
@@ -0,0 +1,94 @@
+// Copyright © 2021 The Tekton Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package completion
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+const (
+	desc = `This command prints shell completion code which must be evaluated to provide
+interactive completion
+
+Supported Shells:
+	- bash
+	- zsh
+	- fish
+	- powershell
+`
+	eg = `To load completions:
+
+Bash:
+
+$ source <(tkn-pac completion bash)
+
+# To load completions for each session, execute once:
+Linux:
+  $ tkn-pac completion bash > /etc/bash_completion.d/tkn-pac
+
+MacOS:
+  $ tkn-pac completion bash > /usr/local/etc/bash_completion.d/tkn-pac
+
+Zsh:
+
+# If shell completion is not already enabled in your environment you will need
+# to enable it.  You can execute the following once:
+
+$ echo "autoload -U compinit; compinit" >> ~/.zshrc
+
+# To load completions for each session, execute once:
+$ tkn-pac completion zsh > "${fpath[1]}/_tkn-pac"
+
+# You will need to start a new shell for this setup to take effect.
+
+Fish:
+
+$ tkn-pac completion fish | source
+
+# To load completions for each session, execute once:
+$ tkn-pac completion fish > ~/.config/fish/completions/tkn-pac.fish
+`
+)
+
+func Command() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:       "completion [SHELL]",
+		Short:     "Prints shell completion scripts",
+		Long:      desc,
+		ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
+		Example:   eg,
+		Annotations: map[string]string{
+			"commandType": "utility",
+		},
+		Args: cobra.ExactValidArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			switch args[0] {
+			case "bash":
+				_ = cmd.Root().GenBashCompletion(os.Stdout)
+			case "zsh":
+				_ = cmd.Root().GenZshCompletion(os.Stdout)
+			case "fish":
+				_ = cmd.Root().GenFishCompletion(os.Stdout, true)
+			case "powershell":
+				_ = cmd.Root().GenPowerShellCompletion(os.Stdout)
+			}
+
+			return nil
+		},
+	}
+	return cmd
+}
diff --git a/pkg/cli/completion/completion.go b/pkg/cli/completion/completion.go
new file mode 100644
index 00000000..1e2a0849
--- /dev/null
+++ b/pkg/cli/completion/completion.go
@@ -0,0 +1,31 @@
+package completion
+
+import (
+	"os/exec"
+	"strings"
+
+	"github.com/spf13/cobra"
+)
+
+// GetObjectsWithKubectl return completions with kubectl, we are doing this with
+// kubectl since we have caching and without it completion is way too slow
+func GetObjectsWithKubectl(obj string) []string {
+	out, err := exec.Command("kubectl", "get", obj, "-o=jsonpath={range .items[*]}{.metadata.name} {end}").Output()
+	if err != nil {
+		return nil
+	}
+	return strings.Fields(string(out))
+}
+
+// BaseCompletion return a completion for a kubernetes object using Kubectl
+func BaseCompletion(target string, args []string) ([]string, cobra.ShellCompDirective) {
+	if len(args) != 0 {
+		return nil, cobra.ShellCompDirectiveNoFileComp
+	}
+	return GetObjectsWithKubectl(target), cobra.ShellCompDirectiveNoFileComp
+}
+
+// ParentCompletion do completion of command to the Parent
+func ParentCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+	return BaseCompletion(cmd.Parent().Name(), args)
+}
diff --git a/pkg/cli/interface.go b/pkg/cli/interface.go
index 7390639e..2caa4685 100644
--- a/pkg/cli/interface.go
+++ b/pkg/cli/interface.go
@@ -36,6 +36,9 @@ type Params interface {
 	// SetGitAPIURL Set github token TODO: rename to a generic vcs
 	SetGitHubAPIURL(string)
 
+	// GetNamespace Get namesace
+	GetNamespace() string
+
 	Clients() (*Clients, error)
 	KubeClient() (k8s.Interface, error)
 }
diff --git a/pkg/cli/params.go b/pkg/cli/params.go
index 127bb15f..2fcdb4ce 100644
--- a/pkg/cli/params.go
+++ b/pkg/cli/params.go
@@ -24,6 +24,10 @@ type PacParams struct {
 
 var _ Params = (*PacParams)(nil)
 
+func (p *PacParams) GetNamespace() string {
+	return p.namespace
+}
+
 func (p *PacParams) SetKubeConfigPath(path string) {
 	p.kubeConfigPath = path
 }
diff --git a/pkg/cli/ui/color.go b/pkg/cli/ui/color.go
new file mode 100644
index 00000000..18913ff9
--- /dev/null
+++ b/pkg/cli/ui/color.go
@@ -0,0 +1,264 @@
+package ui
+
+import (
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/mgutz/ansi"
+)
+
+var (
+	magenta   = ansi.ColorFunc("magenta")
+	cyan      = ansi.ColorFunc("cyan")
+	red       = ansi.ColorFunc("red")
+	redBold   = ansi.ColorFunc("red+b")
+	yellow    = ansi.ColorFunc("yellow")
+	blue      = ansi.ColorFunc("blue")
+	green     = ansi.ColorFunc("green")
+	greenBold = ansi.ColorFunc("green+b")
+	gray      = ansi.ColorFunc("black+i")
+	bold      = ansi.ColorFunc("default+b")
+	dimmed    = ansi.ColorFunc("default+d")
+	underline = ansi.ColorFunc("default+u")
+	cyanBold  = ansi.ColorFunc("cyan+b")
+
+	gray256 = func(t string) string {
+		return fmt.Sprintf("\x1b[%d;5;%dm%s\x1b[m", 38, 242, t)
+	}
+)
+
+func EnvColorDisabled() bool {
+	return os.Getenv("NO_COLOR") != "" || os.Getenv("CLICOLOR") == "0"
+}
+
+func EnvColorForced() bool {
+	return os.Getenv("CLICOLOR_FORCE") != "" && os.Getenv("CLICOLOR_FORCE") != "0"
+}
+
+func Is256ColorSupported() bool {
+	term := os.Getenv("TERM")
+	colorterm := os.Getenv("COLORTERM")
+
+	return strings.Contains(term, "256") ||
+		strings.Contains(term, "24bit") ||
+		strings.Contains(term, "truecolor") ||
+		strings.Contains(colorterm, "256") ||
+		strings.Contains(colorterm, "24bit") ||
+		strings.Contains(colorterm, "truecolor")
+}
+
+func NewColorScheme(enabled, is256enabled bool) *ColorScheme {
+	return &ColorScheme{
+		enabled:      enabled,
+		is256enabled: is256enabled,
+	}
+}
+
+type ColorScheme struct {
+	enabled      bool
+	is256enabled bool
+}
+
+func (c *ColorScheme) ColorStatus(status string) string {
+	switch strings.ToLower(status) {
+	case "succeeded":
+		return c.GreenBold(status)
+	case "failed":
+		return c.RedBold(status)
+	case "norun":
+		return c.Dimmed(status)
+	}
+	return status
+}
+
+func (c *ColorScheme) Bold(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return bold(t)
+}
+
+func (c *ColorScheme) Dimmed(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return dimmed(t)
+}
+
+func (c *ColorScheme) Boldf(t string, args ...interface{}) string {
+	return c.Bold(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Red(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return red(t)
+}
+
+func (c *ColorScheme) RedBold(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return redBold(t)
+}
+
+func (c *ColorScheme) Bullet() string {
+	if !c.enabled {
+		return ""
+	}
+
+	return "∙ "
+}
+
+func (c *ColorScheme) BulletSpace() string {
+	if !c.enabled {
+		return ""
+	}
+
+	return "  "
+}
+
+func (c *ColorScheme) Redf(t string, args ...interface{}) string {
+	return c.Red(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Yellow(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return yellow(t)
+}
+
+func (c *ColorScheme) Yellowf(t string, args ...interface{}) string {
+	return c.Yellow(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Green(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return green(t)
+}
+
+func (c *ColorScheme) Underline(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return underline(t)
+}
+
+func (c *ColorScheme) Greenf(t string, args ...interface{}) string {
+	return c.Green(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Gray(t string) string {
+	if !c.enabled {
+		return t
+	}
+	if c.is256enabled {
+		return gray256(t)
+	}
+	return gray(t)
+}
+
+func (c *ColorScheme) Grayf(t string, args ...interface{}) string {
+	return c.Gray(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Magenta(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return magenta(t)
+}
+
+func (c *ColorScheme) Magentaf(t string, args ...interface{}) string {
+	return c.Magenta(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) Cyan(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return cyan(t)
+}
+
+func (c *ColorScheme) Cyanf(t string, args ...interface{}) string {
+	return c.Cyan(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) CyanBold(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return cyanBold(t)
+}
+
+func (c *ColorScheme) Blue(t string) string {
+	if !c.enabled {
+		return t
+	}
+	return blue(t)
+}
+
+func (c *ColorScheme) Bluef(t string, args ...interface{}) string {
+	return c.Blue(fmt.Sprintf(t, args...))
+}
+
+func (c *ColorScheme) SuccessIcon() string {
+	return c.SuccessIconWithColor(c.Green)
+}
+
+func (c *ColorScheme) SuccessIconWithColor(colo func(string) string) string {
+	return colo("✓")
+}
+
+func (c *ColorScheme) WarningIcon() string {
+	return c.Yellow("!")
+}
+
+func (c *ColorScheme) FailureIcon() string {
+	return c.FailureIconWithColor(c.Red)
+}
+
+func (c *ColorScheme) FailureIconWithColor(colo func(string) string) string {
+	return colo("X")
+}
+
+func (c *ColorScheme) ColorFromString(s string) func(string) string {
+	s = strings.ToLower(s)
+	var fn func(string) string
+	switch s {
+	case "bold":
+		fn = c.Bold
+	case "red":
+		fn = c.Red
+	case "yellow":
+		fn = c.Yellow
+	case "green":
+		fn = c.Green
+	case "gray":
+		fn = c.Gray
+	case "magenta":
+		fn = c.Magenta
+	case "cyan":
+		fn = c.Cyan
+	case "blue":
+		fn = c.Blue
+	default:
+		fn = func(s string) string {
+			return s
+		}
+	}
+
+	return fn
+}
+
+func (c *ColorScheme) GreenBold(s string) string {
+	if !c.enabled {
+		return s
+	}
+	return greenBold(s)
+}
diff --git a/pkg/cli/ui/formating.go b/pkg/cli/ui/formating.go
new file mode 100644
index 00000000..ce214845
--- /dev/null
+++ b/pkg/cli/ui/formating.go
@@ -0,0 +1,56 @@
+package ui
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+)
+
+var shortShaLength = 7
+
+func GetRepoOwnerFromGHURL(ghURL string) (string, error) {
+	u, err := url.Parse(ghURL)
+	if err != nil {
+		return "", err
+	}
+	sp := strings.Split(u.Path, "/")
+	if len(sp) == 1 {
+		return "", fmt.Errorf("not a URL with a REPO/OWNER at the end")
+	}
+	return fmt.Sprintf("%s/%s", sp[len(sp)-2], sp[len(sp)-1]), nil
+}
+
+// CamelCasit pull_request > PullRequest
+func CamelCasit(s string) string {
+	return strings.ReplaceAll(strings.Title(strings.ReplaceAll(s, "_", " ")), " ", "")
+}
+
+func ShowStatus(repository v1alpha1.Repository, cs *ColorScheme) string {
+	var status string
+	if len(repository.Status) == 0 {
+		status = "NoRun"
+	} else {
+		status = repository.Status[len(repository.Status)-1].Status.Conditions[0].GetReason()
+	}
+
+	return cs.ColorStatus(status)
+}
+
+func ShortSHA(sha string) string {
+	if sha == "" {
+		return ""
+	}
+	if shortShaLength >= len(sha)+1 {
+		return sha
+	}
+	return sha[0:shortShaLength]
+}
+
+func ShowLastSHA(repository v1alpha1.Repository) string {
+	if len(repository.Status) == 0 {
+		return "---"
+	}
+	return ShortSHA(*repository.Status[len(repository.Status)-1].SHA)
+}
diff --git a/pkg/cli/ui/iostreams.go b/pkg/cli/ui/iostreams.go
new file mode 100644
index 00000000..1a6d2aa1
--- /dev/null
+++ b/pkg/cli/ui/iostreams.go
@@ -0,0 +1,98 @@
+package ui
+
+import (
+	"io"
+	"os"
+	"time"
+
+	"github.com/briandowns/spinner"
+	"github.com/mattn/go-colorable"
+	"github.com/mattn/go-isatty"
+)
+
+type IOStreams struct {
+	In     io.ReadCloser
+	Out    io.Writer
+	ErrOut io.Writer
+
+	progressIndicator        *spinner.Spinner
+	colorEnabled             bool
+	progressIndicatorEnabled bool
+	stdoutTTYOverride        bool
+	stderrTTYOverride        bool
+	stderrIsTTY              bool
+	stdoutIsTTY              bool
+	is256enabled             bool
+}
+
+func (s *IOStreams) StartProgressIndicator() {
+	sp := spinner.New(spinner.CharSets[11], 400*time.Millisecond, spinner.WithWriter(s.ErrOut))
+	sp.Start()
+	s.progressIndicator = sp
+}
+
+func (s *IOStreams) StopProgressIndicator() {
+	if s.progressIndicator == nil {
+		return
+	}
+	s.progressIndicator.Stop()
+	s.progressIndicator = nil
+}
+
+func (s *IOStreams) ColorScheme() *ColorScheme {
+	return NewColorScheme(s.ColorEnabled(), s.ColorSupport256())
+}
+
+func (s *IOStreams) ColorEnabled() bool {
+	return s.colorEnabled
+}
+
+func (s *IOStreams) ColorSupport256() bool {
+	return s.is256enabled
+}
+
+func NewIOStreams() *IOStreams {
+	stdoutIsTTY := isTerminal(os.Stdout)
+	stderrIsTTY := isTerminal(os.Stderr)
+
+	io := &IOStreams{
+		In:           os.Stdin,
+		Out:          colorable.NewColorable(os.Stdout),
+		ErrOut:       colorable.NewColorable(os.Stderr),
+		colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
+		is256enabled: Is256ColorSupported(),
+	}
+
+	if stdoutIsTTY && stderrIsTTY {
+		io.progressIndicatorEnabled = true
+	}
+
+	// prevent duplicate isTerminal queries now that we know the answer
+	io.SetStdoutTTY(stdoutIsTTY)
+	io.SetStderrTTY(stderrIsTTY)
+	return io
+}
+
+func (s *IOStreams) SetStdoutTTY(isTTY bool) {
+	s.stdoutTTYOverride = true
+	s.stdoutIsTTY = isTTY
+}
+
+func (s *IOStreams) SetStderrTTY(isTTY bool) {
+	s.stderrTTYOverride = true
+	s.stderrIsTTY = isTTY
+}
+
+func (s *IOStreams) IsStdoutTTY() bool {
+	if s.stdoutTTYOverride {
+		return s.stdoutIsTTY
+	}
+	if stdout, ok := s.Out.(*os.File); ok {
+		return isTerminal(stdout)
+	}
+	return false
+}
+
+func isTerminal(f *os.File) bool {
+	return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
+}
diff --git a/pkg/cmd/repository/describe.go b/pkg/cmd/repository/describe.go
new file mode 100644
index 00000000..7b386a86
--- /dev/null
+++ b/pkg/cmd/repository/describe.go
@@ -0,0 +1,142 @@
+package repository
+
+import (
+	"context"
+	"fmt"
+	"text/tabwriter"
+	"text/template"
+
+	"github.com/jonboulle/clockwork"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/completion"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/ui"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/flags"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/pipelineascode"
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+const describeTemplate = `{{ $.ColorScheme.Bold "Name" }}:	{{.Repository.Name}}
+{{ $.ColorScheme.Bold "Namespace" }}:	{{.Repository.Namespace}}
+{{ $.ColorScheme.Bold "URL" }}:	{{.Repository.Spec.URL}}
+{{ $.ColorScheme.Bold "Event Type" }}:	{{formatEventType .Repository.Spec.EventType}}
+{{ $.ColorScheme.Bold "Target Branch" }}:	{{.Repository.Spec.Branch}}
+
+{{- if eq (len .Repository.Status) 0 }}
+
+{{ $.ColorScheme.Dimmed "No runs has started."}}
+{{- else }}
+
+{{- $status := (index .Statuses 0) }}
+
+{{ $.ColorScheme.Underline "Last Run:" }}
+
+{{ $.ColorScheme.Bold "PipelineRun" }}:	{{ $status.PipelineRunName }}
+{{ $.ColorScheme.Bold "Status" }}:	{{ $.ColorScheme.ColorStatus (index $status.Status.Conditions 0).Reason  }}
+{{ $.ColorScheme.Bold "Commit" }}:	{{.Repository.Spec.URL}}/commit/{{ shortSHA $status.SHA }}
+{{ $.ColorScheme.Bold "Commit Title" }}:	{{ $status.Title }}
+{{ $.ColorScheme.Bold "StartTime" }}:	{{ formatTime $status.StartTime $.Clock }}
+{{ $.ColorScheme.Bold "Duration" }}:	{{ formatDuration $status.StartTime $status.CompletionTime }}
+
+{{- if gt (len .Repository.Status) 1 }}
+
+{{ $.ColorScheme.Underline "Other Runs:" }}
+
+{{ $.ColorScheme.BulletSpace }}PIPELINERUN	SHA	START_TIME	DURATION	STATUS
+
+{{- range $i, $st := (slice .Statuses 1 (len .Repository.Status)) }}
+{{ $.ColorScheme.Bullet }}{{ formatStatus $st $.ColorScheme $.Clock }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+`
+
+func formatStatus(status v1alpha1.RepositoryRunStatus, cs *ui.ColorScheme, c clockwork.Clock) string {
+	return fmt.Sprintf("%s\t%s\t%s\t%s\t%s",
+		status.PipelineRunName,
+		ui.ShortSHA(*status.SHA),
+		pipelineascode.Age(status.StartTime, c),
+		pipelineascode.Duration(status.StartTime, status.CompletionTime),
+		cs.ColorStatus(status.Status.Conditions[0].Reason))
+}
+
+func DescribeCommand(p cli.Params) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "describe",
+		Aliases: []string{"desc"},
+		Short:   "Describe a repository",
+		Annotations: map[string]string{
+			"commandType": "main",
+		},
+		ValidArgsFunction: completion.ParentCompletion,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			opts := &flags.CliOpts{}
+			err := opts.SetFromFlags(cmd)
+			if err != nil {
+				return err
+			}
+
+			if len(args) == 0 {
+				// TODO: Auto selection
+				return fmt.Errorf("we need a repository as the first argument")
+			}
+
+			ctx := context.Background()
+			clock := clockwork.NewRealClock()
+			cs, err := p.Clients()
+			if err != nil {
+				return err
+			}
+			ioStreams := ui.NewIOStreams()
+
+			return describe(ctx, cs, clock, opts, ioStreams, p.GetNamespace(), args[0])
+		},
+	}
+	return cmd
+}
+
+func describe(ctx context.Context, cs *cli.Clients, clock clockwork.Clock, opts *flags.CliOpts,
+	ioStreams *ui.IOStreams, namespace, repoName string) error {
+	if opts.Namespace != "" {
+		namespace = opts.Namespace
+	}
+
+	repository, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories(namespace).Get(ctx,
+		repoName, metav1.GetOptions{})
+	if err != nil {
+		return err
+	}
+
+	colorScheme := ioStreams.ColorScheme()
+
+	funcMap := template.FuncMap{
+		"formatStatus":    formatStatus,
+		"formatEventType": ui.CamelCasit,
+		"formatDuration":  pipelineascode.Duration,
+		"formatTime":      pipelineascode.Age,
+		"shortSHA":        ui.ShortSHA,
+	}
+
+	data := struct {
+		Repository  *v1alpha1.Repository
+		Statuses    []v1alpha1.RepositoryRunStatus
+		ColorScheme *ui.ColorScheme
+		Clock       clockwork.Clock
+	}{
+		Repository:  repository,
+		Statuses:    pipelineascode.SortedStatus(repository.Status),
+		ColorScheme: colorScheme,
+		Clock:       clock,
+	}
+
+	w := tabwriter.NewWriter(ioStreams.Out, 0, 5, 3, ' ', tabwriter.TabIndent)
+	t := template.Must(template.New("Describe Repository").Funcs(funcMap).Parse(describeTemplate))
+
+	if err := t.Execute(w, data); err != nil {
+		return err
+	}
+
+	return w.Flush()
+}
diff --git a/pkg/cmd/repository/list.go b/pkg/cmd/repository/list.go
new file mode 100644
index 00000000..afd18640
--- /dev/null
+++ b/pkg/cmd/repository/list.go
@@ -0,0 +1,111 @@
+package repository
+
+import (
+	"context"
+	"fmt"
+	"text/tabwriter"
+
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/ui"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/flags"
+	"github.com/spf13/cobra"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+var (
+	header            = "NAME\tOWNER/REPOSITORY\tSHA\tEVENT-TYPE"
+	body              = "%s\t%s\t%s\t%s"
+	allNamespacesFlag = "all-namespaces"
+)
+
+func ListCommand(p cli.Params) *cobra.Command {
+	var noheaders, allNamespaces bool
+	var selectors string
+
+	cmd := &cobra.Command{
+		Use:     "list",
+		Aliases: []string{"ls"},
+		Short:   "List repositories",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			var err error
+			ioStreams := ui.NewIOStreams()
+
+			opts := &flags.CliOpts{AllNameSpaces: allNamespaces}
+			opts.AllNameSpaces, err = cmd.Flags().GetBool(allNamespacesFlag)
+			if err != nil {
+				return err
+			}
+			if err := opts.SetFromFlags(cmd); err != nil {
+				return err
+			}
+			cs, err := p.Clients()
+			if err != nil {
+				return err
+			}
+			ctx := context.Background()
+			return list(ctx, cs, opts, ioStreams, p.GetNamespace(), selectors, noheaders)
+		},
+	}
+
+	cmd.PersistentFlags().BoolVarP(&allNamespaces, allNamespacesFlag, "A", false, "If present, "+
+		"list the repository across all namespaces. Namespace in current context is ignored even if specified with"+
+		" --namespace.")
+
+	cmd.Flags().BoolVar(
+		&noheaders, "no-headers", false, "don't print headers.")
+
+	cmd.Flags().StringVarP(&selectors, "selectors", "l",
+		"", "Selector (label query) to filter on, "+
+			"supports '=', "+
+			"'==',"+
+			" and '!='.(e.g. -l key1=value1,key2=value2)")
+	return cmd
+}
+
+func list(ctx context.Context, cs *cli.Clients, opts *flags.CliOpts, ioStreams *ui.IOStreams,
+	currentNamespace, selectors string, noheaders bool) error {
+	if opts.Namespace != "" {
+		currentNamespace = opts.Namespace
+	}
+	if opts.AllNameSpaces {
+		currentNamespace = ""
+	}
+
+	lopt := metav1.ListOptions{LabelSelector: selectors}
+
+	repositories, err := cs.PipelineAsCode.PipelinesascodeV1alpha1().Repositories(currentNamespace).List(
+		ctx, lopt)
+	if err != nil {
+		return err
+	}
+
+	w := tabwriter.NewWriter(ioStreams.Out, 0, 5, 3, ' ', tabwriter.TabIndent)
+
+	if !noheaders {
+		_, _ = fmt.Fprint(w, header)
+		if opts.AllNameSpaces {
+			fmt.Fprint(w, "\tNAMESPACE")
+		}
+		fmt.Fprintln(w, "\tSTATUS")
+	}
+	for _, repository := range repositories.Items {
+		repoOwner, err := ui.GetRepoOwnerFromGHURL(repository.Spec.URL)
+		if err != nil {
+			return err
+		}
+
+		fmt.Fprintf(w, body, repository.GetName(), repoOwner,
+			ui.ShowLastSHA(repository),
+			repository.Spec.EventType)
+
+		if opts.AllNameSpaces {
+			fmt.Fprintf(w, "\t%s", repository.GetNamespace())
+		}
+
+		fmt.Fprintf(w, "\t%s", ui.ShowStatus(repository, ioStreams.ColorScheme()))
+		fmt.Fprint(w, "\n")
+	}
+
+	w.Flush()
+	return nil
+}
diff --git a/pkg/cmd/repository/root.go b/pkg/cmd/repository/root.go
new file mode 100644
index 00000000..10a1eba9
--- /dev/null
+++ b/pkg/cmd/repository/root.go
@@ -0,0 +1,20 @@
+package repository
+
+import (
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/spf13/cobra"
+)
+
+func Root(p cli.Params) *cobra.Command {
+	cmd := &cobra.Command{
+		Use:          "repository",
+		Aliases:      []string{"repo", "repsitories"},
+		Short:        "Pipeline as Code repositories",
+		Long:         `Manage Pipeline as Code repositories`,
+		SilenceUsage: true,
+	}
+
+	cmd.AddCommand(ListCommand(p))
+	cmd.AddCommand(DescribeCommand(p))
+	return cmd
+}
diff --git a/pkg/cmd/resolve/resolve.go b/pkg/cmd/resolve/resolve.go
index 290d2317..2398a7a8 100644
--- a/pkg/cmd/resolve/resolve.go
+++ b/pkg/cmd/resolve/resolve.go
@@ -58,7 +58,7 @@ func Command(p cli.Params) *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "resolve",
 		Long:  longhelp,
-		Short: "Resolve a bunch of yaml file in a single PipelineRun",
+		Short: "Embed PipelineRun references as a single resource.",
 		RunE: func(cmd *cobra.Command, args []string) error {
 			cs, err := p.Clients()
 			if err != nil {
diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go
deleted file mode 100644
index 362c0b79..00000000
--- a/pkg/cmd/root.go
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
-Copyright © 2021 Red Hat
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-package cmd
-
-import (
-	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
-	pac "github.com/openshift-pipelines/pipelines-as-code/pkg/cmd/pipelineascode"
-	resolve "github.com/openshift-pipelines/pipelines-as-code/pkg/cmd/resolve"
-	"github.com/spf13/cobra"
-)
-
-func Root(p cli.Params) *cobra.Command {
-	cmd := &cobra.Command{
-		Short:        "Pipeline as Code entrypoint",
-		Long:         ``,
-		SilenceUsage: true,
-	}
-	cmd.AddCommand(resolve.Command(p))
-	cmd.AddCommand(pac.Command(p))
-
-	return cmd
-}
diff --git a/pkg/config/repo_runinfo_matcher.go b/pkg/config/repo_runinfo_matcher.go
index 633270fd..d5eaf638 100644
--- a/pkg/config/repo_runinfo_matcher.go
+++ b/pkg/config/repo_runinfo_matcher.go
@@ -45,7 +45,7 @@ func GetRepoByCR(ctx context.Context, cs *cli.Clients, ns string, runinfo *webvc
 			}
 
 			// Disallow attempts for hijacks. If the installed CR is not configured on the
-			// Namespace the Spec is targeting then disallow it.
+			// namespace the Spec is targeting then disallow it.
 			if value.Namespace != value.Spec.Namespace {
 				return nil, fmt.Errorf("repo CR %s matches but belongs to %s while it should be in %s",
 					value.Name,
diff --git a/pkg/flags/cli.go b/pkg/flags/cli.go
new file mode 100644
index 00000000..c8440d69
--- /dev/null
+++ b/pkg/flags/cli.go
@@ -0,0 +1,44 @@
+package flags
+
+import (
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/completion"
+	"github.com/spf13/cobra"
+)
+
+var (
+	noColor   = "no-color"
+	namespace = "namespace"
+)
+
+type CliOpts struct {
+	NoColoring    bool
+	AllNameSpaces bool
+	Namespace     string
+}
+
+func (c *CliOpts) SetFromFlags(cmd *cobra.Command) error {
+	var err error
+	c.NoColoring, err = cmd.Flags().GetBool(noColor)
+	if err != nil {
+		return err
+	}
+	c.Namespace, err = cmd.Flags().GetString(namespace)
+	if err != nil {
+		return err
+	}
+	return err
+}
+
+func AddPacCliOptions(cmd *cobra.Command) {
+	cmd.PersistentFlags().StringP(
+		namespace, "n", "",
+		"If present, the namespace scope for this CLI request")
+
+	_ = cmd.RegisterFlagCompletionFunc(namespace,
+		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return completion.BaseCompletion(namespace, args)
+		},
+	)
+
+	cmd.PersistentFlags().BoolP(noColor, "C", false, "disable coloring")
+}
diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go
index 3c4d5d02..772e1fd7 100644
--- a/pkg/flags/flags.go
+++ b/pkg/flags/flags.go
@@ -1,6 +1,7 @@
 package flags
 
 import (
+	"fmt"
 	"os"
 
 	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
@@ -38,25 +39,25 @@ func InitParams(p cli.Params, cmd *cobra.Command) error {
 	}
 	p.SetGitHubAPIURL(githubAPIURL)
 
-	// ensure that the config is valid by creating a client
-	if _, err := p.Clients(); err != nil {
-		return err
-	}
-
 	return nil
 }
 
 // AddPacOptions amends command to add flags required to initialize a cli.Param
 func AddPacOptions(cmd *cobra.Command) {
+	envkconfig := os.Getenv("KUBECONFIG")
+	if envkconfig == "" {
+		envkconfig = "$HOME/.kube/config"
+	}
+
 	cmd.PersistentFlags().StringP(
 		kubeConfig, "k", "",
-		"kubectl config file (default: $HOME/.kube/config)")
+		fmt.Sprintf("kubectl config file (default: %s)", envkconfig))
 
 	cmd.PersistentFlags().StringP(
-		token, "", os.Getenv("PAC_TOKEN"),
+		token, "", os.Getenv("PAC_WEBVCS_TOKEN"),
 		"Web VCS (ie: GitHub) Token")
 
 	cmd.PersistentFlags().StringP(
-		apiURL, "", os.Getenv("PAC_URL"),
+		apiURL, "", os.Getenv("PAC_WEBVCS_URL"),
 		"Web VCS (ie: GitHub Enteprise) API URL")
 }
diff --git a/pkg/kubeinteraction/namespace.go b/pkg/kubeinteraction/namespace.go
index 4da80aaf..aa28bdd8 100644
--- a/pkg/kubeinteraction/namespace.go
+++ b/pkg/kubeinteraction/namespace.go
@@ -10,9 +10,9 @@ import (
 func (k Interaction) GetNamespace(ctx context.Context, namespace string) error {
 	_, err := k.Clients.Kube.CoreV1().Namespaces().Get(ctx, namespace, v1.GetOptions{})
 	if err != nil {
-		k.Clients.Log.Infof("Namespace: %s cannot be found", namespace)
+		k.Clients.Log.Infof("namespace: %s cannot be found", namespace)
 		return err
 	}
-	k.Clients.Log.Infof("Namespace is: %s", namespace)
+	k.Clients.Log.Infof("namespace is: %s", namespace)
 	return nil
 }
diff --git a/pkg/pipelineascode/pipelineascode.go b/pkg/pipelineascode/pipelineascode.go
index ef2b8c55..07905cc3 100644
--- a/pkg/pipelineascode/pipelineascode.go
+++ b/pkg/pipelineascode/pipelineascode.go
@@ -110,7 +110,7 @@ func Run(ctx context.Context, cs *cli.Clients, k8int cli.KubeInteractionIntf, ru
 
 	// Make sure we have the namespace already created or error it.
 	// TODO: this probably can be trashed since repo is only can be created in
-	// Namespace
+	// namespace
 	err = k8int.GetNamespace(ctx, repo.Spec.Namespace)
 	if err != nil {
 		return err
diff --git a/pkg/pipelineascode/sort_repository_status.go b/pkg/pipelineascode/sort_repository_status.go
new file mode 100644
index 00000000..bb285533
--- /dev/null
+++ b/pkg/pipelineascode/sort_repository_status.go
@@ -0,0 +1,38 @@
+package pipelineascode
+
+import (
+	"sort"
+
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+)
+
+type repoRunStatus []v1alpha1.RepositoryRunStatus
+
+func (rs repoRunStatus) Len() int {
+	return len(rs)
+}
+
+func (rs repoRunStatus) Swap(i, j int) {
+	rs[i], rs[j] = rs[j], rs[i]
+}
+
+func (rs repoRunStatus) Less(i, j int) bool {
+	if rs[j].StartTime == nil {
+		return false
+	}
+
+	if rs[i].StartTime == nil {
+		return true
+	}
+
+	return rs[j].StartTime.Before(rs[i].StartTime)
+}
+
+func SortedStatus(repoStatus []v1alpha1.RepositoryRunStatus) []v1alpha1.RepositoryRunStatus {
+	rrstatus := repoRunStatus{}
+	for _, status := range repoStatus {
+		rrstatus = append(rrstatus, status)
+	}
+	sort.Sort(rrstatus)
+	return rrstatus
+}
diff --git a/vendor/github.com/briandowns/spinner/.gitignore b/vendor/github.com/briandowns/spinner/.gitignore
new file mode 100644
index 00000000..21ec6b71
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/.gitignore
@@ -0,0 +1,29 @@
+# Created by .gitignore support plugin (hsz.mobi)
+### Go template
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+.idea
+*.iml
diff --git a/vendor/github.com/briandowns/spinner/.travis.yml b/vendor/github.com/briandowns/spinner/.travis.yml
new file mode 100644
index 00000000..5a00f590
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/.travis.yml
@@ -0,0 +1,18 @@
+arch:
+  - amd64
+  - ppc64le
+language: go
+go:
+  - 1.13
+  - 1.14.1
+env:
+  - GOARCH: amd64
+  - GOARCH: 386
+script:
+  - go test -v
+notifications:
+  email:
+    recipients:
+      - brian.downs@gmail.com
+    on_success: change
+    on_failure: always
diff --git a/vendor/github.com/briandowns/spinner/LICENSE b/vendor/github.com/briandowns/spinner/LICENSE
new file mode 100644
index 00000000..dd5b3a58
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/LICENSE
@@ -0,0 +1,174 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
diff --git a/vendor/github.com/briandowns/spinner/Makefile b/vendor/github.com/briandowns/spinner/Makefile
new file mode 100644
index 00000000..bf0b48ae
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/Makefile
@@ -0,0 +1,18 @@
+GO = GO111MODULE=on GOFLAGS=-mod=vendor go
+
+.PHONY: deps
+deps:
+	$(GO) mod download
+	$(GO) mod vendor
+
+.PHONY: test
+test:
+	$(GO) test -v -cover ./...
+
+.PHONY: check
+check:
+	if [ -d vendor ]; then cp -r vendor/* ${GOPATH}/src/; fi
+
+.PHONY: clean
+clean:
+	$(GO) clean
diff --git a/vendor/github.com/briandowns/spinner/README.md b/vendor/github.com/briandowns/spinner/README.md
new file mode 100644
index 00000000..ddae178b
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/README.md
@@ -0,0 +1,282 @@
+# Spinner
+
+[![GoDoc](https://godoc.org/github.com/briandowns/spinner?status.svg)](https://godoc.org/github.com/briandowns/spinner) [![Build Status](https://travis-ci.org/briandowns/spinner.svg?branch=master)](https://travis-ci.org/briandowns/spinner)
+
+spinner is a simple package to add a spinner / progress indicator to any terminal application. Examples can be found below as well as full examples in the examples directory.
+
+For more detail about the library and its features, reference your local godoc once installed.
+
+Contributions welcome!
+
+## Installation
+
+```bash
+go get github.com/briandowns/spinner
+```
+
+## Available Character Sets
+(Numbered by their slice index)
+
+| index | character set                                                                                                                                                                                                                                                                             | sample gif                 |
+| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- |
+| 0     | ```←↖↑↗→↘↓↙```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/0.gif)  |
+| 1     | ```▁▃▄▅▆▇█▇▆▅▄▃▁```                                                                                                                                                                                                                                                                       | ![Sample Gif](gifs/1.gif)  |
+| 2     | ```▖▘▝▗```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/2.gif)  |
+| 3     | ```┤┘┴└├┌┬┐```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/3.gif)  |
+| 4     | ```◢◣◤◥```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/4.gif)  |
+| 5     | ```◰◳◲◱```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/5.gif)  |
+| 6     | ```◴◷◶◵```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/6.gif)  |
+| 7     | ```◐◓◑◒```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/7.gif)  |
+| 8     | ```.oO@*```                                                                                                                                                                                                                                                                               | ![Sample Gif](gifs/8.gif)  |
+| 9     | ```\|/-\```                                                                                                                                                                                                                                                                               | ![Sample Gif](gifs/9.gif)  |
+| 10    | ```◡◡⊙⊙◠◠```                                                                                                                                                                                                                                                                              | ![Sample Gif](gifs/10.gif) |
+| 11    | ```⣾⣽⣻⢿⡿⣟⣯⣷```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/11.gif) |
+| 12    | ```>))'> >))'>  >))'>   >))'>    >))'>   <'((<  <'((< <'((<```                                                                                                                                                                                                                            | ![Sample Gif](gifs/12.gif) |
+| 13    | ```⠁⠂⠄⡀⢀⠠⠐⠈```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/13.gif) |
+| 14    | ```⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏```                                                                                                                                                                                                                                                                          | ![Sample Gif](gifs/14.gif) |
+| 15    | ```abcdefghijklmnopqrstuvwxyz```                                                                                                                                                                                                                                                          | ![Sample Gif](gifs/15.gif) |
+| 16    | ```▉▊▋▌▍▎▏▎▍▌▋▊▉```                                                                                                                                                                                                                                                                       | ![Sample Gif](gifs/16.gif) |
+| 17    | ```■□▪▫```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/17.gif) |
+| 18    | ```←↑→↓```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/18.gif) |
+| 19    | ```╫╪```                                                                                                                                                                                                                                                                                  | ![Sample Gif](gifs/19.gif) |
+| 20    | ```⇐⇖⇑⇗⇒⇘⇓⇙```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/20.gif) |
+| 21    | ```⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈```                                                                                                                                                                                                                                                       | ![Sample Gif](gifs/21.gif) |
+| 22    | ```⠈⠉⠋⠓⠒⠐⠐⠒⠖⠦⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈```                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/22.gif) |
+| 23    | ```⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠴⠲⠒⠂⠂⠒⠚⠙⠉⠁```                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/23.gif) |
+| 24    | ```⠋⠙⠚⠒⠂⠂⠒⠲⠴⠦⠖⠒⠐⠐⠒⠓⠋```                                                                                                                                                                                                                                                                   | ![Sample Gif](gifs/24.gif) |
+| 25    | ```ｦｧｨｩｪｫｬｭｮｯｱｲｳｴｵｶｷｸｹｺｻｼｽｾｿﾀﾁﾂﾃﾄﾅﾆﾇﾈﾉﾊﾋﾌﾍﾎﾏﾐﾑﾒﾓﾔﾕﾖﾗﾘﾙﾚﾛﾜﾝ```                                                                                                                                                                                                                             | ![Sample Gif](gifs/25.gif) |
+| 26    | ```. .. ...```                                                                                                                                                                                                                                                                            | ![Sample Gif](gifs/26.gif) |
+| 27    | ```▁▂▃▄▅▆▇█▉▊▋▌▍▎▏▏▎▍▌▋▊▉█▇▆▅▄▃▂▁```                                                                                                                                                                                                                                                      | ![Sample Gif](gifs/27.gif) |
+| 28    | ```.oO°Oo.```                                                                                                                                                                                                                                                                             | ![Sample Gif](gifs/28.gif) |
+| 29    | ```+x```                                                                                                                                                                                                                                                                                  | ![Sample Gif](gifs/29.gif) |
+| 30    | ```v<^>```                                                                                                                                                                                                                                                                                | ![Sample Gif](gifs/30.gif) |
+| 31    | ```>>---> >>--->  >>--->   >>--->    >>--->    <---<<    <---<<   <---<<  <---<< <---<<```                                                                                                                                                                                                | ![Sample Gif](gifs/31.gif) |
+| 32    | ```\| \|\| \|\|\| \|\|\|\| \|\|\|\|\| \|\|\|\|\|\| \|\|\|\|\| \|\|\|\| \|\|\| \|\| \|```                                                                                                                                                                                                  | ![Sample Gif](gifs/32.gif) |
+| 33    | ```[] [=] [==] [===] [====] [=====] [======] [=======] [========] [=========] [==========]```                                                                                                                                                                                             | ![Sample Gif](gifs/33.gif) |
+| 34    | ```(*---------) (-*--------) (--*-------) (---*------) (----*-----) (-----*----) (------*---) (-------*--) (--------*-) (---------*)```                                                                                                                                                   | ![Sample Gif](gifs/34.gif) |
+| 35    | ```█▒▒▒▒▒▒▒▒▒ ███▒▒▒▒▒▒▒ █████▒▒▒▒▒ ███████▒▒▒ ██████████```                                                                                                                                                                                                                              | ![Sample Gif](gifs/35.gif) |
+| 36    | ```[                    ] [=>                  ] [===>                ] [=====>              ] [======>             ] [========>           ] [==========>         ] [============>       ] [==============>     ] [================>   ] [==================> ] [===================>]``` | ![Sample Gif](gifs/36.gif) |
+| 37    | ```🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛```                                                                                                                                                                                                                                                             | ![Sample Gif](gifs/37.gif) |
+| 38    | ```🕐 🕜 🕑 🕝 🕒 🕞 🕓 🕟 🕔 🕠 🕕 🕡 🕖 🕢 🕗 🕣 🕘 🕤 🕙 🕥 🕚 🕦 🕛 🕧```                                                                                                                                                                                                                                     | ![Sample Gif](gifs/38.gif) |
+| 39    | ```🌍 🌎 🌏```                                                                                                                                                                                                                                                                               | ![Sample Gif](gifs/39.gif) |
+| 40    | ```◜ ◝ ◞ ◟```                                                                                                                                                                                                                                                                             | ![Sample Gif](gifs/40.gif) |
+| 41    | ```⬒ ⬔ ⬓ ⬕```                                                                                                                                                                                                                                                                             | ![Sample Gif](gifs/41.gif) |
+| 42    | ```⬖ ⬘ ⬗ ⬙```                                                                                                                                                                                                                                                                             | ![Sample Gif](gifs/42.gif) |
+| 43    | ```[>>>          >] []>>>>        [] []  >>>>      [] []    >>>>    [] []      >>>>  [] []        >>>>[] [>>          >>]```                                                                                                                                                              | ![Sample Gif](gifs/43.gif) |
+
+## Features
+
+* Start
+* Stop
+* Restart
+* Reverse direction
+* Update the spinner character set
+* Update the spinner speed
+* Prefix or append text
+* Change spinner color, background, and text attributes such as bold / italics
+* Get spinner status
+* Chain, pipe, redirect output
+* Output final string on spinner/indicator completion
+
+## Examples
+
+```Go
+package main
+
+import (
+	"github.com/briandowns/spinner"
+	"time"
+)
+
+func main() {
+	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)  // Build our new spinner
+	s.Start()                                                    // Start the spinner
+	time.Sleep(4 * time.Second)                                  // Run for some time to simulate work
+	s.Stop()
+}
+```
+
+## Update the character set and restart the spinner
+
+```Go
+s.UpdateCharSet(spinner.CharSets[1])  // Update spinner to use a different character set
+s.Restart()                           // Restart the spinner
+time.Sleep(4 * time.Second)
+s.Stop()
+```
+
+## Update spin speed and restart the spinner
+
+```Go
+s.UpdateSpeed(200 * time.Millisecond) // Update the speed the spinner spins at
+s.Restart()
+time.Sleep(4 * time.Second)
+s.Stop()
+```
+
+## Reverse the direction of the spinner
+
+```Go
+s.Reverse() // Reverse the direction the spinner is spinning
+s.Restart()
+time.Sleep(4 * time.Second)
+s.Stop()
+```
+
+## Provide your own spinner
+
+(or send me an issue or pull request to add to the project)
+
+```Go
+someSet := []string{"+", "-"}
+s := spinner.New(someSet, 100*time.Millisecond)
+```
+
+## Prefix or append text to the spinner
+
+```Go
+s.Prefix = "prefixed text: " // Prefix text before the spinner
+s.Suffix = "  :appended text" // Append text after the spinner
+```
+
+## Set or change the color of the spinner.  Default color is white. The spinner will need to be restarted to pick up the change.
+
+```Go
+s.Color("red") // Set the spinner color to red
+```
+
+You can specify both the background and foreground color, as well as additional attributes such as `bold` or `underline`.
+
+```Go
+s.Color("red", "bold") // Set the spinner color to a bold red
+```
+
+Or to set the background to black, the foreground to a bold red:
+
+```Go
+s.Color("bgBlack", "bold", "fgRed")
+```
+
+Below is the full color and attribute list:
+
+```
+// default colors
+red
+black
+green
+yellow
+blue
+magenta
+cyan
+white
+
+// attributes
+reset
+bold
+faint
+italic
+underline
+blinkslow
+blinkrapid
+reversevideo
+concealed
+crossedout
+
+// foreground text
+fgBlack
+fgRed
+fgGreen
+fgYellow
+fgBlue
+fgMagenta
+fgCyan
+fgWhite
+
+// foreground Hi-Intensity text
+fgHiBlack
+fgHiRed
+fgHiGreen
+fgHiYellow
+fgHiBlue
+fgHiMagenta
+fgHiCyan
+fgHiWhite
+
+// background text
+bgBlack
+bgRed
+bgGreen
+bgYellow
+bgBlue
+bgMagenta
+bgCyan
+bgWhite
+
+// background Hi-Intensity text
+bgHiBlack
+bgHiRed
+bgHiGreen
+bgHiYellow
+bgHiBlue
+bgHiMagenta
+bgHiCyan
+bgHiWhite
+```
+
+## Generate a sequence of numbers
+
+```Go
+setOfDigits := spinner.GenerateNumberSequence(25)    // Generate a 25 digit string of numbers
+s := spinner.New(setOfDigits, 100*time.Millisecond)
+```
+
+## Get spinner status
+
+```Go
+fmt.Println(s.Active())
+```
+
+## Unix pipe and redirect
+
+Feature suggested and write up by [dekz](https://github.com/dekz)
+
+Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output. 
+
+This is the preferred method of setting a Writer at this time.
+
+```go
+s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
+s.Suffix = " Encrypting data..."
+s.Start()
+// Encrypt the data into ciphertext
+fmt.Println(os.Stdout, ciphertext)
+```
+
+```sh
+> myprog encrypt "Secret text" > encrypted.txt
+⣯ Encrypting data...
+```
+
+```sh
+> cat encrypted.txt
+1243hjkbas23i9ah27sj39jghv237n2oa93hg83
+```
+
+## Final String Output
+
+Add additional output when the spinner/indicator has completed. The "final" output string can be multi-lined and will be written to wherever the `io.Writer` has been configured for.
+
+```Go
+s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
+s.FinalMSG = "Complete!\nNew line!\nAnother one!\n"
+s.Start()                 
+time.Sleep(4 * time.Second)
+s.Stop()                   
+```
+
+Output
+```sh
+Complete!
+New line!
+Another one!
+```
diff --git a/vendor/github.com/briandowns/spinner/character_sets.go b/vendor/github.com/briandowns/spinner/character_sets.go
new file mode 100644
index 00000000..f0d74a8f
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/character_sets.go
@@ -0,0 +1,96 @@
+package spinner
+
+const (
+	clockOneOClock = '\U0001F550'
+	clockOneThirty = '\U0001F55C'
+)
+
+// CharSets contains the available character sets
+var CharSets = map[int][]string{
+	0:  {"←", "↖", "↑", "↗", "→", "↘", "↓", "↙"},
+	1:  {"▁", "▃", "▄", "▅", "▆", "▇", "█", "▇", "▆", "▅", "▄", "▃", "▁"},
+	2:  {"▖", "▘", "▝", "▗"},
+	3:  {"┤", "┘", "┴", "└", "├", "┌", "┬", "┐"},
+	4:  {"◢", "◣", "◤", "◥"},
+	5:  {"◰", "◳", "◲", "◱"},
+	6:  {"◴", "◷", "◶", "◵"},
+	7:  {"◐", "◓", "◑", "◒"},
+	8:  {".", "o", "O", "@", "*"},
+	9:  {"|", "/", "-", "\\"},
+	10: {"◡◡", "⊙⊙", "◠◠"},
+	11: {"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"},
+	12: {">))'>", " >))'>", "  >))'>", "   >))'>", "    >))'>", "   <'((<", "  <'((<", " <'((<"},
+	13: {"⠁", "⠂", "⠄", "⡀", "⢀", "⠠", "⠐", "⠈"},
+	14: {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
+	15: {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"},
+	16: {"▉", "▊", "▋", "▌", "▍", "▎", "▏", "▎", "▍", "▌", "▋", "▊", "▉"},
+	17: {"■", "□", "▪", "▫"},
+	18: {"←", "↑", "→", "↓"},
+	19: {"╫", "╪"},
+	20: {"⇐", "⇖", "⇑", "⇗", "⇒", "⇘", "⇓", "⇙"},
+	21: {"⠁", "⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈", "⠈"},
+	22: {"⠈", "⠉", "⠋", "⠓", "⠒", "⠐", "⠐", "⠒", "⠖", "⠦", "⠤", "⠠", "⠠", "⠤", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋", "⠉", "⠈"},
+	23: {"⠁", "⠉", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠤", "⠄", "⠄", "⠤", "⠴", "⠲", "⠒", "⠂", "⠂", "⠒", "⠚", "⠙", "⠉", "⠁"},
+	24: {"⠋", "⠙", "⠚", "⠒", "⠂", "⠂", "⠒", "⠲", "⠴", "⠦", "⠖", "⠒", "⠐", "⠐", "⠒", "⠓", "⠋"},
+	25: {"ｦ", "ｧ", "ｨ", "ｩ", "ｪ", "ｫ", "ｬ", "ｭ", "ｮ", "ｯ", "ｱ", "ｲ", "ｳ", "ｴ", "ｵ", "ｶ", "ｷ", "ｸ", "ｹ", "ｺ", "ｻ", "ｼ", "ｽ", "ｾ", "ｿ", "ﾀ", "ﾁ", "ﾂ", "ﾃ", "ﾄ", "ﾅ", "ﾆ", "ﾇ", "ﾈ", "ﾉ", "ﾊ", "ﾋ", "ﾌ", "ﾍ", "ﾎ", "ﾏ", "ﾐ", "ﾑ", "ﾒ", "ﾓ", "ﾔ", "ﾕ", "ﾖ", "ﾗ", "ﾘ", "ﾙ", "ﾚ", "ﾛ", "ﾜ", "ﾝ"},
+	26: {".", "..", "..."},
+	27: {"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁"},
+	28: {".", "o", "O", "°", "O", "o", "."},
+	29: {"+", "x"},
+	30: {"v", "<", "^", ">"},
+	31: {">>--->", " >>--->", "  >>--->", "   >>--->", "    >>--->", "    <---<<", "   <---<<", "  <---<<", " <---<<", "<---<<"},
+	32: {"|", "||", "|||", "||||", "|||||", "|||||||", "||||||||", "|||||||", "||||||", "|||||", "||||", "|||", "||", "|"},
+	33: {"[          ]", "[=         ]", "[==        ]", "[===       ]", "[====      ]", "[=====     ]", "[======    ]", "[=======   ]", "[========  ]", "[========= ]", "[==========]"},
+	34: {"(*---------)", "(-*--------)", "(--*-------)", "(---*------)", "(----*-----)", "(-----*----)", "(------*---)", "(-------*--)", "(--------*-)", "(---------*)"},
+	35: {"█▒▒▒▒▒▒▒▒▒", "███▒▒▒▒▒▒▒", "█████▒▒▒▒▒", "███████▒▒▒", "██████████"},
+	36: {"[                    ]", "[=>                  ]", "[===>                ]", "[=====>              ]", "[======>             ]", "[========>           ]", "[==========>         ]", "[============>       ]", "[==============>     ]", "[================>   ]", "[==================> ]", "[===================>]"},
+	39: {"🌍", "🌎", "🌏"},
+	40: {"◜", "◝", "◞", "◟"},
+	41: {"⬒", "⬔", "⬓", "⬕"},
+	42: {"⬖", "⬘", "⬗", "⬙"},
+	43: {"[>>>          >]", "[]>>>>        []", "[]  >>>>      []", "[]    >>>>    []", "[]      >>>>  []", "[]        >>>>[]", "[>>          >>]"},
+	44: {"♠", "♣", "♥", "♦"},
+	45: {"➞", "➟", "➠", "➡", "➠", "➟"},
+	46: {"  |  ", ` \   `, "_    ", ` \   `, "  |  ", "   / ", "    _", "   / "},
+	47: {"  . . . .", ".   . . .", ". .   . .", ". . .   .", ". . . .  ", ". . . . ."},
+	48: {" |     ", "  /    ", "   _   ", `    \  `, "     | ", `    \  `, "   _   ", "  /    "},
+	49: {"⎺", "⎻", "⎼", "⎽", "⎼", "⎻"},
+	50: {"▹▹▹▹▹", "▸▹▹▹▹", "▹▸▹▹▹", "▹▹▸▹▹", "▹▹▹▸▹", "▹▹▹▹▸"},
+	51: {"[    ]", "[   =]", "[  ==]", "[ ===]", "[====]", "[=== ]", "[==  ]", "[=   ]"},
+	52: {"( ●    )", "(  ●   )", "(   ●  )", "(    ● )", "(     ●)", "(    ● )", "(   ●  )", "(  ●   )", "( ●    )"},
+	53: {"✶", "✸", "✹", "✺", "✹", "✷"},
+	54: {"▐|\\____________▌", "▐_|\\___________▌", "▐__|\\__________▌", "▐___|\\_________▌", "▐____|\\________▌", "▐_____|\\_______▌", "▐______|\\______▌", "▐_______|\\_____▌", "▐________|\\____▌", "▐_________|\\___▌", "▐__________|\\__▌", "▐___________|\\_▌", "▐____________|\\▌", "▐____________/|▌", "▐___________/|_▌", "▐__________/|__▌", "▐_________/|___▌", "▐________/|____▌", "▐_______/|_____▌", "▐______/|______▌", "▐_____/|_______▌", "▐____/|________▌", "▐___/|_________▌", "▐__/|__________▌", "▐_/|___________▌", "▐/|____________▌"},
+	55: {"▐⠂       ▌", "▐⠈       ▌", "▐ ⠂      ▌", "▐ ⠠      ▌", "▐  ⡀     ▌", "▐  ⠠     ▌", "▐   ⠂    ▌", "▐   ⠈    ▌", "▐    ⠂   ▌", "▐    ⠠   ▌", "▐     ⡀  ▌", "▐     ⠠  ▌", "▐      ⠂ ▌", "▐      ⠈ ▌", "▐       ⠂▌", "▐       ⠠▌", "▐       ⡀▌", "▐      ⠠ ▌", "▐      ⠂ ▌", "▐     ⠈  ▌", "▐     ⠂  ▌", "▐    ⠠   ▌", "▐    ⡀   ▌", "▐   ⠠    ▌", "▐   ⠂    ▌", "▐  ⠈     ▌", "▐  ⠂     ▌", "▐ ⠠      ▌", "▐ ⡀      ▌", "▐⠠       ▌"},
+	56: {"¿", "?"},
+	57: {"⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"},
+	58: {"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
+	59: {".  ", ".. ", "...", " ..", "  .", "   "},
+	60: {".", "o", "O", "°", "O", "o", "."},
+	61: {"▓", "▒", "░"},
+	62: {"▌", "▀", "▐", "▄"},
+	63: {"⊶", "⊷"},
+	64: {"▪", "▫"},
+	65: {"□", "■"},
+	66: {"▮", "▯"},
+	67: {"-", "=", "≡"},
+	68: {"d", "q", "p", "b"},
+	69: {"∙∙∙", "●∙∙", "∙●∙", "∙∙●", "∙∙∙"},
+	70: {"🌑 ", "🌒 ", "🌓 ", "🌔 ", "🌕 ", "🌖 ", "🌗 ", "🌘 "},
+	71: {"☗", "☖"},
+	72: {"⧇", "⧆"},
+	73: {"◉", "◎"},
+	74: {"㊂", "㊀", "㊁"},
+	75: {"⦾", "⦿"},
+	76: {"ဝ", "၀"},
+	77: {"▌", "▀", "▐▄"},
+	78: {"⠈⠁", "⠈⠑", "⠈⠱", "⠈⡱", "⢀⡱", "⢄⡱", "⢄⡱", "⢆⡱", "⢎⡱", "⢎⡰", "⢎⡠", "⢎⡀", "⢎⠁", "⠎⠁", "⠊⠁"},
+	79: {"________", "-_______", "_-______", "__-_____", "___-____", "____-___", "_____-__", "______-_", "_______-", "________", "_______-", "______-_", "_____-__", "____-___", "___-____", "__-_____", "_-______", "-_______", "________"},
+	80: {"|_______", "_/______", "__-_____", "___\\____", "____|___", "_____/__", "______-_", "_______\\", "_______|", "______\\_", "_____-__", "____/___", "___|____", "__\\_____", "_-______"},
+}
+
+func init() {
+	for i := rune(0); i < 12; i++ {
+		CharSets[37] = append(CharSets[37], string([]rune{clockOneOClock + i}))
+		CharSets[38] = append(CharSets[38], string([]rune{clockOneOClock + i}), string([]rune{clockOneThirty + i}))
+	}
+}
diff --git a/vendor/github.com/briandowns/spinner/go.mod b/vendor/github.com/briandowns/spinner/go.mod
new file mode 100644
index 00000000..df62180e
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/go.mod
@@ -0,0 +1,8 @@
+module github.com/briandowns/spinner
+
+go 1.14
+
+require (
+	github.com/fatih/color v1.7.0
+	github.com/mattn/go-colorable v0.1.2 // indirect
+)
diff --git a/vendor/github.com/briandowns/spinner/go.sum b/vendor/github.com/briandowns/spinner/go.sum
new file mode 100644
index 00000000..a0d4725a
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/go.sum
@@ -0,0 +1,8 @@
+github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/github.com/briandowns/spinner/spinner.go b/vendor/github.com/briandowns/spinner/spinner.go
new file mode 100644
index 00000000..3462819d
--- /dev/null
+++ b/vendor/github.com/briandowns/spinner/spinner.go
@@ -0,0 +1,430 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package spinner is a simple package to add a spinner / progress indicator to any terminal application.
+package spinner
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"os"
+	"runtime"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+	"unicode/utf8"
+
+	"github.com/fatih/color"
+)
+
+// errInvalidColor is returned when attempting to set an invalid color
+var errInvalidColor = errors.New("invalid color")
+
+// validColors holds an array of the only colors allowed
+var validColors = map[string]bool{
+	// default colors for backwards compatibility
+	"black":   true,
+	"red":     true,
+	"green":   true,
+	"yellow":  true,
+	"blue":    true,
+	"magenta": true,
+	"cyan":    true,
+	"white":   true,
+
+	// attributes
+	"reset":        true,
+	"bold":         true,
+	"faint":        true,
+	"italic":       true,
+	"underline":    true,
+	"blinkslow":    true,
+	"blinkrapid":   true,
+	"reversevideo": true,
+	"concealed":    true,
+	"crossedout":   true,
+
+	// foreground text
+	"fgBlack":   true,
+	"fgRed":     true,
+	"fgGreen":   true,
+	"fgYellow":  true,
+	"fgBlue":    true,
+	"fgMagenta": true,
+	"fgCyan":    true,
+	"fgWhite":   true,
+
+	// foreground Hi-Intensity text
+	"fgHiBlack":   true,
+	"fgHiRed":     true,
+	"fgHiGreen":   true,
+	"fgHiYellow":  true,
+	"fgHiBlue":    true,
+	"fgHiMagenta": true,
+	"fgHiCyan":    true,
+	"fgHiWhite":   true,
+
+	// background text
+	"bgBlack":   true,
+	"bgRed":     true,
+	"bgGreen":   true,
+	"bgYellow":  true,
+	"bgBlue":    true,
+	"bgMagenta": true,
+	"bgCyan":    true,
+	"bgWhite":   true,
+
+	// background Hi-Intensity text
+	"bgHiBlack":   true,
+	"bgHiRed":     true,
+	"bgHiGreen":   true,
+	"bgHiYellow":  true,
+	"bgHiBlue":    true,
+	"bgHiMagenta": true,
+	"bgHiCyan":    true,
+	"bgHiWhite":   true,
+}
+
+// returns true if the OS is windows and the WT_SESSION env variable is set.
+var isWindowsTerminalOnWindows = len(os.Getenv("WT_SESSION")) > 0 && runtime.GOOS == "windows"
+
+// returns a valid color's foreground text color attribute
+var colorAttributeMap = map[string]color.Attribute{
+	// default colors for backwards compatibility
+	"black":   color.FgBlack,
+	"red":     color.FgRed,
+	"green":   color.FgGreen,
+	"yellow":  color.FgYellow,
+	"blue":    color.FgBlue,
+	"magenta": color.FgMagenta,
+	"cyan":    color.FgCyan,
+	"white":   color.FgWhite,
+
+	// attributes
+	"reset":        color.Reset,
+	"bold":         color.Bold,
+	"faint":        color.Faint,
+	"italic":       color.Italic,
+	"underline":    color.Underline,
+	"blinkslow":    color.BlinkSlow,
+	"blinkrapid":   color.BlinkRapid,
+	"reversevideo": color.ReverseVideo,
+	"concealed":    color.Concealed,
+	"crossedout":   color.CrossedOut,
+
+	// foreground text colors
+	"fgBlack":   color.FgBlack,
+	"fgRed":     color.FgRed,
+	"fgGreen":   color.FgGreen,
+	"fgYellow":  color.FgYellow,
+	"fgBlue":    color.FgBlue,
+	"fgMagenta": color.FgMagenta,
+	"fgCyan":    color.FgCyan,
+	"fgWhite":   color.FgWhite,
+
+	// foreground Hi-Intensity text colors
+	"fgHiBlack":   color.FgHiBlack,
+	"fgHiRed":     color.FgHiRed,
+	"fgHiGreen":   color.FgHiGreen,
+	"fgHiYellow":  color.FgHiYellow,
+	"fgHiBlue":    color.FgHiBlue,
+	"fgHiMagenta": color.FgHiMagenta,
+	"fgHiCyan":    color.FgHiCyan,
+	"fgHiWhite":   color.FgHiWhite,
+
+	// background text colors
+	"bgBlack":   color.BgBlack,
+	"bgRed":     color.BgRed,
+	"bgGreen":   color.BgGreen,
+	"bgYellow":  color.BgYellow,
+	"bgBlue":    color.BgBlue,
+	"bgMagenta": color.BgMagenta,
+	"bgCyan":    color.BgCyan,
+	"bgWhite":   color.BgWhite,
+
+	// background Hi-Intensity text colors
+	"bgHiBlack":   color.BgHiBlack,
+	"bgHiRed":     color.BgHiRed,
+	"bgHiGreen":   color.BgHiGreen,
+	"bgHiYellow":  color.BgHiYellow,
+	"bgHiBlue":    color.BgHiBlue,
+	"bgHiMagenta": color.BgHiMagenta,
+	"bgHiCyan":    color.BgHiCyan,
+	"bgHiWhite":   color.BgHiWhite,
+}
+
+// validColor will make sure the given color is actually allowed.
+func validColor(c string) bool {
+	return validColors[c]
+}
+
+// Spinner struct to hold the provided options.
+type Spinner struct {
+	mu         *sync.RWMutex                 //
+	Delay      time.Duration                 // Delay is the speed of the indicator
+	chars      []string                      // chars holds the chosen character set
+	Prefix     string                        // Prefix is the text preppended to the indicator
+	Suffix     string                        // Suffix is the text appended to the indicator
+	FinalMSG   string                        // string displayed after Stop() is called
+	lastOutput string                        // last character(set) written
+	color      func(a ...interface{}) string // default color is white
+	Writer     io.Writer                     // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
+	active     bool                          // active holds the state of the spinner
+	stopChan   chan struct{}                 // stopChan is a channel used to stop the indicator
+	HideCursor bool                          // hideCursor determines if the cursor is visible
+	PreUpdate  func(s *Spinner)              // will be triggered before every spinner update
+	PostUpdate func(s *Spinner)              // will be triggered after every spinner update
+}
+
+// New provides a pointer to an instance of Spinner with the supplied options.
+func New(cs []string, d time.Duration, options ...Option) *Spinner {
+	s := &Spinner{
+		Delay:    d,
+		chars:    cs,
+		color:    color.New(color.FgWhite).SprintFunc(),
+		mu:       &sync.RWMutex{},
+		Writer:   color.Output,
+		active:   false,
+		stopChan: make(chan struct{}, 1),
+	}
+
+	for _, option := range options {
+		option(s)
+	}
+	return s
+}
+
+// Option is a function that takes a spinner and applies
+// a given configuration.
+type Option func(*Spinner)
+
+// Options contains fields to configure the spinner.
+type Options struct {
+	Color      string
+	Suffix     string
+	FinalMSG   string
+	HideCursor bool
+}
+
+// WithColor adds the given color to the spinner.
+func WithColor(color string) Option {
+	return func(s *Spinner) {
+		s.Color(color)
+	}
+}
+
+// WithSuffix adds the given string to the spinner
+// as the suffix.
+func WithSuffix(suffix string) Option {
+	return func(s *Spinner) {
+		s.Suffix = suffix
+	}
+}
+
+// WithFinalMSG adds the given string ot the spinner
+// as the final message to be written.
+func WithFinalMSG(finalMsg string) Option {
+	return func(s *Spinner) {
+		s.FinalMSG = finalMsg
+	}
+}
+
+// WithHiddenCursor hides the cursor
+// if hideCursor = true given.
+func WithHiddenCursor(hideCursor bool) Option {
+	return func(s *Spinner) {
+		s.HideCursor = hideCursor
+	}
+}
+
+// WithWriter adds the given writer to the spinner. This
+// function should be favored over directly assigning to
+// the struct value.
+func WithWriter(w io.Writer) Option {
+	return func(s *Spinner) {
+		s.mu.Lock()
+		s.Writer = w
+		s.mu.Unlock()
+	}
+}
+
+// Active will return whether or not the spinner is currently active.
+func (s *Spinner) Active() bool {
+	return s.active
+}
+
+// Start will start the indicator.
+func (s *Spinner) Start() {
+	s.mu.Lock()
+	if s.active {
+		s.mu.Unlock()
+		return
+	}
+	if s.HideCursor && !isWindowsTerminalOnWindows {
+		// hides the cursor
+		fmt.Fprint(s.Writer, "\033[?25l")
+	}
+	s.active = true
+	s.mu.Unlock()
+
+	go func() {
+		for {
+			for i := 0; i < len(s.chars); i++ {
+				select {
+				case <-s.stopChan:
+					return
+				default:
+					s.mu.Lock()
+					if !s.active {
+						s.mu.Unlock()
+						return
+					}
+					if !isWindowsTerminalOnWindows {
+						s.erase()
+					}
+
+					if s.PreUpdate != nil {
+						s.PreUpdate(s)
+					}
+
+					var outColor string
+					if runtime.GOOS == "windows" {
+						if s.Writer == os.Stderr {
+							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
+						} else {
+							outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
+						}
+					} else {
+						outColor = fmt.Sprintf("\r%s%s%s ", s.Prefix, s.color(s.chars[i]), s.Suffix)
+					}
+					outPlain := fmt.Sprintf("\r%s%s%s ", s.Prefix, s.chars[i], s.Suffix)
+					fmt.Fprint(s.Writer, outColor)
+					s.lastOutput = outPlain
+					delay := s.Delay
+
+					if s.PostUpdate != nil {
+						s.PostUpdate(s)
+					}
+
+					s.mu.Unlock()
+					time.Sleep(delay)
+				}
+			}
+		}
+	}()
+}
+
+// Stop stops the indicator.
+func (s *Spinner) Stop() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	if s.active {
+		s.active = false
+		if s.HideCursor && !isWindowsTerminalOnWindows {
+			// makes the cursor visible
+			fmt.Fprint(s.Writer, "\033[?25h")
+		}
+		s.erase()
+		if s.FinalMSG != "" {
+			if isWindowsTerminalOnWindows {
+				fmt.Fprint(s.Writer, "\r", s.FinalMSG)
+			} else {
+				fmt.Fprint(s.Writer, s.FinalMSG)
+			}
+		}
+		s.stopChan <- struct{}{}
+	}
+}
+
+// Restart will stop and start the indicator.
+func (s *Spinner) Restart() {
+	s.Stop()
+	s.Start()
+}
+
+// Reverse will reverse the order of the slice assigned to the indicator.
+func (s *Spinner) Reverse() {
+	s.mu.Lock()
+	for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
+		s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
+	}
+	s.mu.Unlock()
+}
+
+// Color will set the struct field for the given color to be used. The spinner
+// will need to be explicitly restarted.
+func (s *Spinner) Color(colors ...string) error {
+	colorAttributes := make([]color.Attribute, len(colors))
+
+	// Verify colours are valid and place the appropriate attribute in the array
+	for index, c := range colors {
+		if !validColor(c) {
+			return errInvalidColor
+		}
+		colorAttributes[index] = colorAttributeMap[c]
+	}
+
+	s.mu.Lock()
+	s.color = color.New(colorAttributes...).SprintFunc()
+	s.mu.Unlock()
+	return nil
+}
+
+// UpdateSpeed will set the indicator delay to the given value.
+func (s *Spinner) UpdateSpeed(d time.Duration) {
+	s.mu.Lock()
+	s.Delay = d
+	s.mu.Unlock()
+}
+
+// UpdateCharSet will change the current character set to the given one.
+func (s *Spinner) UpdateCharSet(cs []string) {
+	s.mu.Lock()
+	s.chars = cs
+	s.mu.Unlock()
+}
+
+// erase deletes written characters.
+// Caller must already hold s.lock.
+func (s *Spinner) erase() {
+	n := utf8.RuneCountInString(s.lastOutput)
+	if runtime.GOOS == "windows" && !isWindowsTerminalOnWindows {
+		clearString := "\r" + strings.Repeat(" ", n) + "\r"
+		fmt.Fprint(s.Writer, clearString)
+		s.lastOutput = ""
+		return
+	}
+	fmt.Fprintf(s.Writer, "\033[K") // erases to end of line
+	s.lastOutput = ""
+}
+
+// Lock allows for manual control to lock the spinner.
+func (s *Spinner) Lock() {
+	s.mu.Lock()
+}
+
+// Unlock allows for manual control to unlock the spinner.
+func (s *Spinner) Unlock() {
+	s.mu.Unlock()
+}
+
+// GenerateNumberSequence will generate a slice of integers at the
+// provided length and convert them each to a string.
+func GenerateNumberSequence(length int) []string {
+	numSeq := make([]string, length)
+	for i := 0; i < length; i++ {
+		numSeq[i] = strconv.Itoa(i)
+	}
+	return numSeq
+}
diff --git a/vendor/github.com/fatih/color/.travis.yml b/vendor/github.com/fatih/color/.travis.yml
new file mode 100644
index 00000000..95f8a1ff
--- /dev/null
+++ b/vendor/github.com/fatih/color/.travis.yml
@@ -0,0 +1,5 @@
+language: go
+go: 
+ - 1.8.x
+ - tip
+
diff --git a/vendor/github.com/fatih/color/Gopkg.lock b/vendor/github.com/fatih/color/Gopkg.lock
new file mode 100644
index 00000000..7d879e9c
--- /dev/null
+++ b/vendor/github.com/fatih/color/Gopkg.lock
@@ -0,0 +1,27 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  name = "github.com/mattn/go-colorable"
+  packages = ["."]
+  revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
+  version = "v0.0.9"
+
+[[projects]]
+  name = "github.com/mattn/go-isatty"
+  packages = ["."]
+  revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
+  version = "v0.0.3"
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/sys"
+  packages = ["unix"]
+  revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  inputs-digest = "e8a50671c3cb93ea935bf210b1cd20702876b9d9226129be581ef646d1565cdc"
+  solver-name = "gps-cdcl"
+  solver-version = 1
diff --git a/vendor/github.com/fatih/color/Gopkg.toml b/vendor/github.com/fatih/color/Gopkg.toml
new file mode 100644
index 00000000..ff1617f7
--- /dev/null
+++ b/vendor/github.com/fatih/color/Gopkg.toml
@@ -0,0 +1,30 @@
+
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#  name = "github.com/x/y"
+#  version = "2.4.0"
+
+
+[[constraint]]
+  name = "github.com/mattn/go-colorable"
+  version = "0.0.9"
+
+[[constraint]]
+  name = "github.com/mattn/go-isatty"
+  version = "0.0.3"
diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md
new file mode 100644
index 00000000..25fdaf63
--- /dev/null
+++ b/vendor/github.com/fatih/color/LICENSE.md
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Fatih Arslan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md
new file mode 100644
index 00000000..3fc95446
--- /dev/null
+++ b/vendor/github.com/fatih/color/README.md
@@ -0,0 +1,179 @@
+# Color [![GoDoc](https://godoc.org/github.com/fatih/color?status.svg)](https://godoc.org/github.com/fatih/color) [![Build Status](https://img.shields.io/travis/fatih/color.svg?style=flat-square)](https://travis-ci.org/fatih/color)
+
+
+
+Color lets you use colorized outputs in terms of [ANSI Escape
+Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
+has support for Windows too! The API can be used in several ways, pick one that
+suits you.
+
+
+![Color](https://i.imgur.com/c1JI0lA.png)
+
+
+## Install
+
+```bash
+go get github.com/fatih/color
+```
+
+Note that the `vendor` folder is here for stability. Remove the folder if you
+already have the dependencies in your GOPATH.
+
+## Examples
+
+### Standard colors
+
+```go
+// Print with default helper functions
+color.Cyan("Prints text in cyan.")
+
+// A newline will be appended automatically
+color.Blue("Prints %s in blue.", "text")
+
+// These are using the default foreground colors
+color.Red("We have red")
+color.Magenta("And many others ..")
+
+```
+
+### Mix and reuse colors
+
+```go
+// Create a new color object
+c := color.New(color.FgCyan).Add(color.Underline)
+c.Println("Prints cyan text with an underline.")
+
+// Or just add them to New()
+d := color.New(color.FgCyan, color.Bold)
+d.Printf("This prints bold cyan %s\n", "too!.")
+
+// Mix up foreground and background colors, create new mixes!
+red := color.New(color.FgRed)
+
+boldRed := red.Add(color.Bold)
+boldRed.Println("This will print text in bold red.")
+
+whiteBackground := red.Add(color.BgWhite)
+whiteBackground.Println("Red text with white background.")
+```
+
+### Use your own output (io.Writer)
+
+```go
+// Use your own io.Writer output
+color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
+
+blue := color.New(color.FgBlue)
+blue.Fprint(writer, "This will print text in blue.")
+```
+
+### Custom print functions (PrintFunc)
+
+```go
+// Create a custom print function for convenience
+red := color.New(color.FgRed).PrintfFunc()
+red("Warning")
+red("Error: %s", err)
+
+// Mix up multiple attributes
+notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
+notice("Don't forget this...")
+```
+
+### Custom fprint functions (FprintFunc)
+
+```go
+blue := color.New(FgBlue).FprintfFunc()
+blue(myWriter, "important notice: %s", stars)
+
+// Mix up with multiple attributes
+success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
+success(myWriter, "Don't forget this...")
+```
+
+### Insert into noncolor strings (SprintFunc)
+
+```go
+// Create SprintXxx functions to mix strings with other non-colorized strings:
+yellow := color.New(color.FgYellow).SprintFunc()
+red := color.New(color.FgRed).SprintFunc()
+fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
+
+info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
+fmt.Printf("This %s rocks!\n", info("package"))
+
+// Use helper functions
+fmt.Println("This", color.RedString("warning"), "should be not neglected.")
+fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
+
+// Windows supported too! Just don't forget to change the output to color.Output
+fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
+```
+
+### Plug into existing code
+
+```go
+// Use handy standard colors
+color.Set(color.FgYellow)
+
+fmt.Println("Existing text will now be in yellow")
+fmt.Printf("This one %s\n", "too")
+
+color.Unset() // Don't forget to unset
+
+// You can mix up parameters
+color.Set(color.FgMagenta, color.Bold)
+defer color.Unset() // Use it in your function
+
+fmt.Println("All text will now be bold magenta.")
+```
+
+### Disable/Enable color
+ 
+There might be a case where you want to explicitly disable/enable color output. the 
+`go-isatty` package will automatically disable color output for non-tty output streams 
+(for example if the output were piped directly to `less`)
+
+`Color` has support to disable/enable colors both globally and for single color 
+definitions. For example suppose you have a CLI app and a `--no-color` bool flag. You 
+can easily disable the color output with:
+
+```go
+
+var flagNoColor = flag.Bool("no-color", false, "Disable color output")
+
+if *flagNoColor {
+	color.NoColor = true // disables colorized output
+}
+```
+
+It also has support for single color definitions (local). You can
+disable/enable color output on the fly:
+
+```go
+c := color.New(color.FgCyan)
+c.Println("Prints cyan text")
+
+c.DisableColor()
+c.Println("This is printed without any color")
+
+c.EnableColor()
+c.Println("This prints again cyan...")
+```
+
+## Todo
+
+* Save/Return previous values
+* Evaluate fmt.Formatter interface
+
+
+## Credits
+
+ * [Fatih Arslan](https://github.com/fatih)
+ * Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
+
+## License
+
+The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
+
diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go
new file mode 100644
index 00000000..91c8e9f0
--- /dev/null
+++ b/vendor/github.com/fatih/color/color.go
@@ -0,0 +1,603 @@
+package color
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/mattn/go-colorable"
+	"github.com/mattn/go-isatty"
+)
+
+var (
+	// NoColor defines if the output is colorized or not. It's dynamically set to
+	// false or true based on the stdout's file descriptor referring to a terminal
+	// or not. This is a global option and affects all colors. For more control
+	// over each color block use the methods DisableColor() individually.
+	NoColor = os.Getenv("TERM") == "dumb" ||
+		(!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
+
+	// Output defines the standard output of the print functions. By default
+	// os.Stdout is used.
+	Output = colorable.NewColorableStdout()
+
+	// Error defines a color supporting writer for os.Stderr.
+	Error = colorable.NewColorableStderr()
+
+	// colorsCache is used to reduce the count of created Color objects and
+	// allows to reuse already created objects with required Attribute.
+	colorsCache   = make(map[Attribute]*Color)
+	colorsCacheMu sync.Mutex // protects colorsCache
+)
+
+// Color defines a custom color object which is defined by SGR parameters.
+type Color struct {
+	params  []Attribute
+	noColor *bool
+}
+
+// Attribute defines a single SGR Code
+type Attribute int
+
+const escape = "\x1b"
+
+// Base attributes
+const (
+	Reset Attribute = iota
+	Bold
+	Faint
+	Italic
+	Underline
+	BlinkSlow
+	BlinkRapid
+	ReverseVideo
+	Concealed
+	CrossedOut
+)
+
+// Foreground text colors
+const (
+	FgBlack Attribute = iota + 30
+	FgRed
+	FgGreen
+	FgYellow
+	FgBlue
+	FgMagenta
+	FgCyan
+	FgWhite
+)
+
+// Foreground Hi-Intensity text colors
+const (
+	FgHiBlack Attribute = iota + 90
+	FgHiRed
+	FgHiGreen
+	FgHiYellow
+	FgHiBlue
+	FgHiMagenta
+	FgHiCyan
+	FgHiWhite
+)
+
+// Background text colors
+const (
+	BgBlack Attribute = iota + 40
+	BgRed
+	BgGreen
+	BgYellow
+	BgBlue
+	BgMagenta
+	BgCyan
+	BgWhite
+)
+
+// Background Hi-Intensity text colors
+const (
+	BgHiBlack Attribute = iota + 100
+	BgHiRed
+	BgHiGreen
+	BgHiYellow
+	BgHiBlue
+	BgHiMagenta
+	BgHiCyan
+	BgHiWhite
+)
+
+// New returns a newly created color object.
+func New(value ...Attribute) *Color {
+	c := &Color{params: make([]Attribute, 0)}
+	c.Add(value...)
+	return c
+}
+
+// Set sets the given parameters immediately. It will change the color of
+// output with the given SGR parameters until color.Unset() is called.
+func Set(p ...Attribute) *Color {
+	c := New(p...)
+	c.Set()
+	return c
+}
+
+// Unset resets all escape attributes and clears the output. Usually should
+// be called after Set().
+func Unset() {
+	if NoColor {
+		return
+	}
+
+	fmt.Fprintf(Output, "%s[%dm", escape, Reset)
+}
+
+// Set sets the SGR sequence.
+func (c *Color) Set() *Color {
+	if c.isNoColorSet() {
+		return c
+	}
+
+	fmt.Fprintf(Output, c.format())
+	return c
+}
+
+func (c *Color) unset() {
+	if c.isNoColorSet() {
+		return
+	}
+
+	Unset()
+}
+
+func (c *Color) setWriter(w io.Writer) *Color {
+	if c.isNoColorSet() {
+		return c
+	}
+
+	fmt.Fprintf(w, c.format())
+	return c
+}
+
+func (c *Color) unsetWriter(w io.Writer) {
+	if c.isNoColorSet() {
+		return
+	}
+
+	if NoColor {
+		return
+	}
+
+	fmt.Fprintf(w, "%s[%dm", escape, Reset)
+}
+
+// Add is used to chain SGR parameters. Use as many as parameters to combine
+// and create custom color objects. Example: Add(color.FgRed, color.Underline).
+func (c *Color) Add(value ...Attribute) *Color {
+	c.params = append(c.params, value...)
+	return c
+}
+
+func (c *Color) prepend(value Attribute) {
+	c.params = append(c.params, 0)
+	copy(c.params[1:], c.params[0:])
+	c.params[0] = value
+}
+
+// Fprint formats using the default formats for its operands and writes to w.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
+	c.setWriter(w)
+	defer c.unsetWriter(w)
+
+	return fmt.Fprint(w, a...)
+}
+
+// Print formats using the default formats for its operands and writes to
+// standard output. Spaces are added between operands when neither is a
+// string. It returns the number of bytes written and any write error
+// encountered. This is the standard fmt.Print() method wrapped with the given
+// color.
+func (c *Color) Print(a ...interface{}) (n int, err error) {
+	c.Set()
+	defer c.unset()
+
+	return fmt.Fprint(Output, a...)
+}
+
+// Fprintf formats according to a format specifier and writes to w.
+// It returns the number of bytes written and any write error encountered.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
+	c.setWriter(w)
+	defer c.unsetWriter(w)
+
+	return fmt.Fprintf(w, format, a...)
+}
+
+// Printf formats according to a format specifier and writes to standard output.
+// It returns the number of bytes written and any write error encountered.
+// This is the standard fmt.Printf() method wrapped with the given color.
+func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
+	c.Set()
+	defer c.unset()
+
+	return fmt.Fprintf(Output, format, a...)
+}
+
+// Fprintln formats using the default formats for its operands and writes to w.
+// Spaces are always added between operands and a newline is appended.
+// On Windows, users should wrap w with colorable.NewColorable() if w is of
+// type *os.File.
+func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
+	c.setWriter(w)
+	defer c.unsetWriter(w)
+
+	return fmt.Fprintln(w, a...)
+}
+
+// Println formats using the default formats for its operands and writes to
+// standard output. Spaces are always added between operands and a newline is
+// appended. It returns the number of bytes written and any write error
+// encountered. This is the standard fmt.Print() method wrapped with the given
+// color.
+func (c *Color) Println(a ...interface{}) (n int, err error) {
+	c.Set()
+	defer c.unset()
+
+	return fmt.Fprintln(Output, a...)
+}
+
+// Sprint is just like Print, but returns a string instead of printing it.
+func (c *Color) Sprint(a ...interface{}) string {
+	return c.wrap(fmt.Sprint(a...))
+}
+
+// Sprintln is just like Println, but returns a string instead of printing it.
+func (c *Color) Sprintln(a ...interface{}) string {
+	return c.wrap(fmt.Sprintln(a...))
+}
+
+// Sprintf is just like Printf, but returns a string instead of printing it.
+func (c *Color) Sprintf(format string, a ...interface{}) string {
+	return c.wrap(fmt.Sprintf(format, a...))
+}
+
+// FprintFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprint().
+func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
+	return func(w io.Writer, a ...interface{}) {
+		c.Fprint(w, a...)
+	}
+}
+
+// PrintFunc returns a new function that prints the passed arguments as
+// colorized with color.Print().
+func (c *Color) PrintFunc() func(a ...interface{}) {
+	return func(a ...interface{}) {
+		c.Print(a...)
+	}
+}
+
+// FprintfFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprintf().
+func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
+	return func(w io.Writer, format string, a ...interface{}) {
+		c.Fprintf(w, format, a...)
+	}
+}
+
+// PrintfFunc returns a new function that prints the passed arguments as
+// colorized with color.Printf().
+func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
+	return func(format string, a ...interface{}) {
+		c.Printf(format, a...)
+	}
+}
+
+// FprintlnFunc returns a new function that prints the passed arguments as
+// colorized with color.Fprintln().
+func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
+	return func(w io.Writer, a ...interface{}) {
+		c.Fprintln(w, a...)
+	}
+}
+
+// PrintlnFunc returns a new function that prints the passed arguments as
+// colorized with color.Println().
+func (c *Color) PrintlnFunc() func(a ...interface{}) {
+	return func(a ...interface{}) {
+		c.Println(a...)
+	}
+}
+
+// SprintFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprint(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output, example:
+//
+//	put := New(FgYellow).SprintFunc()
+//	fmt.Fprintf(color.Output, "This is a %s", put("warning"))
+func (c *Color) SprintFunc() func(a ...interface{}) string {
+	return func(a ...interface{}) string {
+		return c.wrap(fmt.Sprint(a...))
+	}
+}
+
+// SprintfFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprintf(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output.
+func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
+	return func(format string, a ...interface{}) string {
+		return c.wrap(fmt.Sprintf(format, a...))
+	}
+}
+
+// SprintlnFunc returns a new function that returns colorized strings for the
+// given arguments with fmt.Sprintln(). Useful to put into or mix into other
+// string. Windows users should use this in conjunction with color.Output.
+func (c *Color) SprintlnFunc() func(a ...interface{}) string {
+	return func(a ...interface{}) string {
+		return c.wrap(fmt.Sprintln(a...))
+	}
+}
+
+// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
+// an example output might be: "1;36" -> bold cyan
+func (c *Color) sequence() string {
+	format := make([]string, len(c.params))
+	for i, v := range c.params {
+		format[i] = strconv.Itoa(int(v))
+	}
+
+	return strings.Join(format, ";")
+}
+
+// wrap wraps the s string with the colors attributes. The string is ready to
+// be printed.
+func (c *Color) wrap(s string) string {
+	if c.isNoColorSet() {
+		return s
+	}
+
+	return c.format() + s + c.unformat()
+}
+
+func (c *Color) format() string {
+	return fmt.Sprintf("%s[%sm", escape, c.sequence())
+}
+
+func (c *Color) unformat() string {
+	return fmt.Sprintf("%s[%dm", escape, Reset)
+}
+
+// DisableColor disables the color output. Useful to not change any existing
+// code and still being able to output. Can be used for flags like
+// "--no-color". To enable back use EnableColor() method.
+func (c *Color) DisableColor() {
+	c.noColor = boolPtr(true)
+}
+
+// EnableColor enables the color output. Use it in conjunction with
+// DisableColor(). Otherwise this method has no side effects.
+func (c *Color) EnableColor() {
+	c.noColor = boolPtr(false)
+}
+
+func (c *Color) isNoColorSet() bool {
+	// check first if we have user setted action
+	if c.noColor != nil {
+		return *c.noColor
+	}
+
+	// if not return the global option, which is disabled by default
+	return NoColor
+}
+
+// Equals returns a boolean value indicating whether two colors are equal.
+func (c *Color) Equals(c2 *Color) bool {
+	if len(c.params) != len(c2.params) {
+		return false
+	}
+
+	for _, attr := range c.params {
+		if !c2.attrExists(attr) {
+			return false
+		}
+	}
+
+	return true
+}
+
+func (c *Color) attrExists(a Attribute) bool {
+	for _, attr := range c.params {
+		if attr == a {
+			return true
+		}
+	}
+
+	return false
+}
+
+func boolPtr(v bool) *bool {
+	return &v
+}
+
+func getCachedColor(p Attribute) *Color {
+	colorsCacheMu.Lock()
+	defer colorsCacheMu.Unlock()
+
+	c, ok := colorsCache[p]
+	if !ok {
+		c = New(p)
+		colorsCache[p] = c
+	}
+
+	return c
+}
+
+func colorPrint(format string, p Attribute, a ...interface{}) {
+	c := getCachedColor(p)
+
+	if !strings.HasSuffix(format, "\n") {
+		format += "\n"
+	}
+
+	if len(a) == 0 {
+		c.Print(format)
+	} else {
+		c.Printf(format, a...)
+	}
+}
+
+func colorString(format string, p Attribute, a ...interface{}) string {
+	c := getCachedColor(p)
+
+	if len(a) == 0 {
+		return c.SprintFunc()(format)
+	}
+
+	return c.SprintfFunc()(format, a...)
+}
+
+// Black is a convenient helper function to print with black foreground. A
+// newline is appended to format by default.
+func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
+
+// Red is a convenient helper function to print with red foreground. A
+// newline is appended to format by default.
+func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
+
+// Green is a convenient helper function to print with green foreground. A
+// newline is appended to format by default.
+func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
+
+// Yellow is a convenient helper function to print with yellow foreground.
+// A newline is appended to format by default.
+func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
+
+// Blue is a convenient helper function to print with blue foreground. A
+// newline is appended to format by default.
+func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
+
+// Magenta is a convenient helper function to print with magenta foreground.
+// A newline is appended to format by default.
+func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
+
+// Cyan is a convenient helper function to print with cyan foreground. A
+// newline is appended to format by default.
+func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
+
+// White is a convenient helper function to print with white foreground. A
+// newline is appended to format by default.
+func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
+
+// BlackString is a convenient helper function to return a string with black
+// foreground.
+func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
+
+// RedString is a convenient helper function to return a string with red
+// foreground.
+func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
+
+// GreenString is a convenient helper function to return a string with green
+// foreground.
+func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
+
+// YellowString is a convenient helper function to return a string with yellow
+// foreground.
+func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
+
+// BlueString is a convenient helper function to return a string with blue
+// foreground.
+func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
+
+// MagentaString is a convenient helper function to return a string with magenta
+// foreground.
+func MagentaString(format string, a ...interface{}) string {
+	return colorString(format, FgMagenta, a...)
+}
+
+// CyanString is a convenient helper function to return a string with cyan
+// foreground.
+func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
+
+// WhiteString is a convenient helper function to return a string with white
+// foreground.
+func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
+
+// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
+// newline is appended to format by default.
+func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
+
+// HiRed is a convenient helper function to print with hi-intensity red foreground. A
+// newline is appended to format by default.
+func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
+
+// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
+// newline is appended to format by default.
+func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
+
+// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
+// A newline is appended to format by default.
+func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
+
+// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
+// newline is appended to format by default.
+func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
+
+// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
+// A newline is appended to format by default.
+func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
+
+// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
+// newline is appended to format by default.
+func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
+
+// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
+// newline is appended to format by default.
+func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
+
+// HiBlackString is a convenient helper function to return a string with hi-intensity black
+// foreground.
+func HiBlackString(format string, a ...interface{}) string {
+	return colorString(format, FgHiBlack, a...)
+}
+
+// HiRedString is a convenient helper function to return a string with hi-intensity red
+// foreground.
+func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
+
+// HiGreenString is a convenient helper function to return a string with hi-intensity green
+// foreground.
+func HiGreenString(format string, a ...interface{}) string {
+	return colorString(format, FgHiGreen, a...)
+}
+
+// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
+// foreground.
+func HiYellowString(format string, a ...interface{}) string {
+	return colorString(format, FgHiYellow, a...)
+}
+
+// HiBlueString is a convenient helper function to return a string with hi-intensity blue
+// foreground.
+func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
+
+// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
+// foreground.
+func HiMagentaString(format string, a ...interface{}) string {
+	return colorString(format, FgHiMagenta, a...)
+}
+
+// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
+// foreground.
+func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
+
+// HiWhiteString is a convenient helper function to return a string with hi-intensity white
+// foreground.
+func HiWhiteString(format string, a ...interface{}) string {
+	return colorString(format, FgHiWhite, a...)
+}
diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go
new file mode 100644
index 00000000..cf1e9650
--- /dev/null
+++ b/vendor/github.com/fatih/color/doc.go
@@ -0,0 +1,133 @@
+/*
+Package color is an ANSI color package to output colorized or SGR defined
+output to the standard output. The API can be used in several way, pick one
+that suits you.
+
+Use simple and default helper functions with predefined foreground colors:
+
+    color.Cyan("Prints text in cyan.")
+
+    // a newline will be appended automatically
+    color.Blue("Prints %s in blue.", "text")
+
+    // More default foreground colors..
+    color.Red("We have red")
+    color.Yellow("Yellow color too!")
+    color.Magenta("And many others ..")
+
+    // Hi-intensity colors
+    color.HiGreen("Bright green color.")
+    color.HiBlack("Bright black means gray..")
+    color.HiWhite("Shiny white color!")
+
+However there are times where custom color mixes are required. Below are some
+examples to create custom color objects and use the print functions of each
+separate color object.
+
+    // Create a new color object
+    c := color.New(color.FgCyan).Add(color.Underline)
+    c.Println("Prints cyan text with an underline.")
+
+    // Or just add them to New()
+    d := color.New(color.FgCyan, color.Bold)
+    d.Printf("This prints bold cyan %s\n", "too!.")
+
+
+    // Mix up foreground and background colors, create new mixes!
+    red := color.New(color.FgRed)
+
+    boldRed := red.Add(color.Bold)
+    boldRed.Println("This will print text in bold red.")
+
+    whiteBackground := red.Add(color.BgWhite)
+    whiteBackground.Println("Red text with White background.")
+
+    // Use your own io.Writer output
+    color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
+
+    blue := color.New(color.FgBlue)
+    blue.Fprint(myWriter, "This will print text in blue.")
+
+You can create PrintXxx functions to simplify even more:
+
+    // Create a custom print function for convenient
+    red := color.New(color.FgRed).PrintfFunc()
+    red("warning")
+    red("error: %s", err)
+
+    // Mix up multiple attributes
+    notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
+    notice("don't forget this...")
+
+You can also FprintXxx functions to pass your own io.Writer:
+
+    blue := color.New(FgBlue).FprintfFunc()
+    blue(myWriter, "important notice: %s", stars)
+
+    // Mix up with multiple attributes
+    success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
+    success(myWriter, don't forget this...")
+
+
+Or create SprintXxx functions to mix strings with other non-colorized strings:
+
+    yellow := New(FgYellow).SprintFunc()
+    red := New(FgRed).SprintFunc()
+
+    fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
+
+    info := New(FgWhite, BgGreen).SprintFunc()
+    fmt.Printf("this %s rocks!\n", info("package"))
+
+Windows support is enabled by default. All Print functions work as intended.
+However only for color.SprintXXX functions, user should use fmt.FprintXXX and
+set the output to color.Output:
+
+    fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
+
+    info := New(FgWhite, BgGreen).SprintFunc()
+    fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
+
+Using with existing code is possible. Just use the Set() method to set the
+standard output to the given parameters. That way a rewrite of an existing
+code is not required.
+
+    // Use handy standard colors.
+    color.Set(color.FgYellow)
+
+    fmt.Println("Existing text will be now in Yellow")
+    fmt.Printf("This one %s\n", "too")
+
+    color.Unset() // don't forget to unset
+
+    // You can mix up parameters
+    color.Set(color.FgMagenta, color.Bold)
+    defer color.Unset() // use it in your function
+
+    fmt.Println("All text will be now bold magenta.")
+
+There might be a case where you want to disable color output (for example to
+pipe the standard output of your app to somewhere else). `Color` has support to
+disable colors both globally and for single color definition. For example
+suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
+the color output with:
+
+    var flagNoColor = flag.Bool("no-color", false, "Disable color output")
+
+    if *flagNoColor {
+    	color.NoColor = true // disables colorized output
+    }
+
+It also has support for single color definitions (local). You can
+disable/enable color output on the fly:
+
+     c := color.New(color.FgCyan)
+     c.Println("Prints cyan text")
+
+     c.DisableColor()
+     c.Println("This is printed without any color")
+
+     c.EnableColor()
+     c.Println("This prints again cyan...")
+*/
+package color
diff --git a/vendor/github.com/mattn/go-colorable/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml
new file mode 100644
index 00000000..98db8f06
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+go:
+  - tip
+
+before_install:
+  - go get github.com/mattn/goveralls
+  - go get golang.org/x/tools/cmd/cover
+script:
+  - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw
diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE
new file mode 100644
index 00000000..91b5cef3
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Yasuhiro Matsumoto
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md
new file mode 100644
index 00000000..56729a92
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/README.md
@@ -0,0 +1,48 @@
+# go-colorable
+
+[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable)
+[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable)
+[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable)
+
+Colorable writer for windows.
+
+For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
+This package is possible to handle escape sequence for ansi color on windows.
+
+## Too Bad!
+
+![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png)
+
+
+## So Good!
+
+![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png)
+
+## Usage
+
+```go
+logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
+logrus.SetOutput(colorable.NewColorableStdout())
+
+logrus.Info("succeeded")
+logrus.Warn("not correct")
+logrus.Error("something error")
+logrus.Fatal("panic")
+```
+
+You can compile above code on non-windows OSs.
+
+## Installation
+
+```
+$ go get github.com/mattn/go-colorable
+```
+
+# License
+
+MIT
+
+# Author
+
+Yasuhiro Matsumoto (a.k.a mattn)
diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go
new file mode 100644
index 00000000..1f28d773
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go
@@ -0,0 +1,29 @@
+// +build appengine
+
+package colorable
+
+import (
+	"io"
+	"os"
+
+	_ "github.com/mattn/go-isatty"
+)
+
+// NewColorable return new instance of Writer which handle escape sequence.
+func NewColorable(file *os.File) io.Writer {
+	if file == nil {
+		panic("nil passed instead of *os.File to NewColorable()")
+	}
+
+	return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+	return os.Stdout
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+	return os.Stderr
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go
new file mode 100644
index 00000000..887f203d
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_others.go
@@ -0,0 +1,30 @@
+// +build !windows
+// +build !appengine
+
+package colorable
+
+import (
+	"io"
+	"os"
+
+	_ "github.com/mattn/go-isatty"
+)
+
+// NewColorable return new instance of Writer which handle escape sequence.
+func NewColorable(file *os.File) io.Writer {
+	if file == nil {
+		panic("nil passed instead of *os.File to NewColorable()")
+	}
+
+	return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+	return os.Stdout
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+	return os.Stderr
+}
diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go
new file mode 100644
index 00000000..404e10ca
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go
@@ -0,0 +1,980 @@
+// +build windows
+// +build !appengine
+
+package colorable
+
+import (
+	"bytes"
+	"io"
+	"math"
+	"os"
+	"strconv"
+	"strings"
+	"syscall"
+	"unsafe"
+
+	"github.com/mattn/go-isatty"
+)
+
+const (
+	foregroundBlue      = 0x1
+	foregroundGreen     = 0x2
+	foregroundRed       = 0x4
+	foregroundIntensity = 0x8
+	foregroundMask      = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
+	backgroundBlue      = 0x10
+	backgroundGreen     = 0x20
+	backgroundRed       = 0x40
+	backgroundIntensity = 0x80
+	backgroundMask      = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
+)
+
+const (
+	genericRead  = 0x80000000
+	genericWrite = 0x40000000
+)
+
+const (
+	consoleTextmodeBuffer = 0x1
+)
+
+type wchar uint16
+type short int16
+type dword uint32
+type word uint16
+
+type coord struct {
+	x short
+	y short
+}
+
+type smallRect struct {
+	left   short
+	top    short
+	right  short
+	bottom short
+}
+
+type consoleScreenBufferInfo struct {
+	size              coord
+	cursorPosition    coord
+	attributes        word
+	window            smallRect
+	maximumWindowSize coord
+}
+
+type consoleCursorInfo struct {
+	size    dword
+	visible int32
+}
+
+var (
+	kernel32                       = syscall.NewLazyDLL("kernel32.dll")
+	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
+	procSetConsoleTextAttribute    = kernel32.NewProc("SetConsoleTextAttribute")
+	procSetConsoleCursorPosition   = kernel32.NewProc("SetConsoleCursorPosition")
+	procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
+	procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
+	procGetConsoleCursorInfo       = kernel32.NewProc("GetConsoleCursorInfo")
+	procSetConsoleCursorInfo       = kernel32.NewProc("SetConsoleCursorInfo")
+	procSetConsoleTitle            = kernel32.NewProc("SetConsoleTitleW")
+	procCreateConsoleScreenBuffer  = kernel32.NewProc("CreateConsoleScreenBuffer")
+)
+
+// Writer provide colorable Writer to the console
+type Writer struct {
+	out       io.Writer
+	handle    syscall.Handle
+	althandle syscall.Handle
+	oldattr   word
+	oldpos    coord
+	rest      bytes.Buffer
+}
+
+// NewColorable return new instance of Writer which handle escape sequence from File.
+func NewColorable(file *os.File) io.Writer {
+	if file == nil {
+		panic("nil passed instead of *os.File to NewColorable()")
+	}
+
+	if isatty.IsTerminal(file.Fd()) {
+		var csbi consoleScreenBufferInfo
+		handle := syscall.Handle(file.Fd())
+		procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+		return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
+	}
+	return file
+}
+
+// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
+func NewColorableStdout() io.Writer {
+	return NewColorable(os.Stdout)
+}
+
+// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
+func NewColorableStderr() io.Writer {
+	return NewColorable(os.Stderr)
+}
+
+var color256 = map[int]int{
+	0:   0x000000,
+	1:   0x800000,
+	2:   0x008000,
+	3:   0x808000,
+	4:   0x000080,
+	5:   0x800080,
+	6:   0x008080,
+	7:   0xc0c0c0,
+	8:   0x808080,
+	9:   0xff0000,
+	10:  0x00ff00,
+	11:  0xffff00,
+	12:  0x0000ff,
+	13:  0xff00ff,
+	14:  0x00ffff,
+	15:  0xffffff,
+	16:  0x000000,
+	17:  0x00005f,
+	18:  0x000087,
+	19:  0x0000af,
+	20:  0x0000d7,
+	21:  0x0000ff,
+	22:  0x005f00,
+	23:  0x005f5f,
+	24:  0x005f87,
+	25:  0x005faf,
+	26:  0x005fd7,
+	27:  0x005fff,
+	28:  0x008700,
+	29:  0x00875f,
+	30:  0x008787,
+	31:  0x0087af,
+	32:  0x0087d7,
+	33:  0x0087ff,
+	34:  0x00af00,
+	35:  0x00af5f,
+	36:  0x00af87,
+	37:  0x00afaf,
+	38:  0x00afd7,
+	39:  0x00afff,
+	40:  0x00d700,
+	41:  0x00d75f,
+	42:  0x00d787,
+	43:  0x00d7af,
+	44:  0x00d7d7,
+	45:  0x00d7ff,
+	46:  0x00ff00,
+	47:  0x00ff5f,
+	48:  0x00ff87,
+	49:  0x00ffaf,
+	50:  0x00ffd7,
+	51:  0x00ffff,
+	52:  0x5f0000,
+	53:  0x5f005f,
+	54:  0x5f0087,
+	55:  0x5f00af,
+	56:  0x5f00d7,
+	57:  0x5f00ff,
+	58:  0x5f5f00,
+	59:  0x5f5f5f,
+	60:  0x5f5f87,
+	61:  0x5f5faf,
+	62:  0x5f5fd7,
+	63:  0x5f5fff,
+	64:  0x5f8700,
+	65:  0x5f875f,
+	66:  0x5f8787,
+	67:  0x5f87af,
+	68:  0x5f87d7,
+	69:  0x5f87ff,
+	70:  0x5faf00,
+	71:  0x5faf5f,
+	72:  0x5faf87,
+	73:  0x5fafaf,
+	74:  0x5fafd7,
+	75:  0x5fafff,
+	76:  0x5fd700,
+	77:  0x5fd75f,
+	78:  0x5fd787,
+	79:  0x5fd7af,
+	80:  0x5fd7d7,
+	81:  0x5fd7ff,
+	82:  0x5fff00,
+	83:  0x5fff5f,
+	84:  0x5fff87,
+	85:  0x5fffaf,
+	86:  0x5fffd7,
+	87:  0x5fffff,
+	88:  0x870000,
+	89:  0x87005f,
+	90:  0x870087,
+	91:  0x8700af,
+	92:  0x8700d7,
+	93:  0x8700ff,
+	94:  0x875f00,
+	95:  0x875f5f,
+	96:  0x875f87,
+	97:  0x875faf,
+	98:  0x875fd7,
+	99:  0x875fff,
+	100: 0x878700,
+	101: 0x87875f,
+	102: 0x878787,
+	103: 0x8787af,
+	104: 0x8787d7,
+	105: 0x8787ff,
+	106: 0x87af00,
+	107: 0x87af5f,
+	108: 0x87af87,
+	109: 0x87afaf,
+	110: 0x87afd7,
+	111: 0x87afff,
+	112: 0x87d700,
+	113: 0x87d75f,
+	114: 0x87d787,
+	115: 0x87d7af,
+	116: 0x87d7d7,
+	117: 0x87d7ff,
+	118: 0x87ff00,
+	119: 0x87ff5f,
+	120: 0x87ff87,
+	121: 0x87ffaf,
+	122: 0x87ffd7,
+	123: 0x87ffff,
+	124: 0xaf0000,
+	125: 0xaf005f,
+	126: 0xaf0087,
+	127: 0xaf00af,
+	128: 0xaf00d7,
+	129: 0xaf00ff,
+	130: 0xaf5f00,
+	131: 0xaf5f5f,
+	132: 0xaf5f87,
+	133: 0xaf5faf,
+	134: 0xaf5fd7,
+	135: 0xaf5fff,
+	136: 0xaf8700,
+	137: 0xaf875f,
+	138: 0xaf8787,
+	139: 0xaf87af,
+	140: 0xaf87d7,
+	141: 0xaf87ff,
+	142: 0xafaf00,
+	143: 0xafaf5f,
+	144: 0xafaf87,
+	145: 0xafafaf,
+	146: 0xafafd7,
+	147: 0xafafff,
+	148: 0xafd700,
+	149: 0xafd75f,
+	150: 0xafd787,
+	151: 0xafd7af,
+	152: 0xafd7d7,
+	153: 0xafd7ff,
+	154: 0xafff00,
+	155: 0xafff5f,
+	156: 0xafff87,
+	157: 0xafffaf,
+	158: 0xafffd7,
+	159: 0xafffff,
+	160: 0xd70000,
+	161: 0xd7005f,
+	162: 0xd70087,
+	163: 0xd700af,
+	164: 0xd700d7,
+	165: 0xd700ff,
+	166: 0xd75f00,
+	167: 0xd75f5f,
+	168: 0xd75f87,
+	169: 0xd75faf,
+	170: 0xd75fd7,
+	171: 0xd75fff,
+	172: 0xd78700,
+	173: 0xd7875f,
+	174: 0xd78787,
+	175: 0xd787af,
+	176: 0xd787d7,
+	177: 0xd787ff,
+	178: 0xd7af00,
+	179: 0xd7af5f,
+	180: 0xd7af87,
+	181: 0xd7afaf,
+	182: 0xd7afd7,
+	183: 0xd7afff,
+	184: 0xd7d700,
+	185: 0xd7d75f,
+	186: 0xd7d787,
+	187: 0xd7d7af,
+	188: 0xd7d7d7,
+	189: 0xd7d7ff,
+	190: 0xd7ff00,
+	191: 0xd7ff5f,
+	192: 0xd7ff87,
+	193: 0xd7ffaf,
+	194: 0xd7ffd7,
+	195: 0xd7ffff,
+	196: 0xff0000,
+	197: 0xff005f,
+	198: 0xff0087,
+	199: 0xff00af,
+	200: 0xff00d7,
+	201: 0xff00ff,
+	202: 0xff5f00,
+	203: 0xff5f5f,
+	204: 0xff5f87,
+	205: 0xff5faf,
+	206: 0xff5fd7,
+	207: 0xff5fff,
+	208: 0xff8700,
+	209: 0xff875f,
+	210: 0xff8787,
+	211: 0xff87af,
+	212: 0xff87d7,
+	213: 0xff87ff,
+	214: 0xffaf00,
+	215: 0xffaf5f,
+	216: 0xffaf87,
+	217: 0xffafaf,
+	218: 0xffafd7,
+	219: 0xffafff,
+	220: 0xffd700,
+	221: 0xffd75f,
+	222: 0xffd787,
+	223: 0xffd7af,
+	224: 0xffd7d7,
+	225: 0xffd7ff,
+	226: 0xffff00,
+	227: 0xffff5f,
+	228: 0xffff87,
+	229: 0xffffaf,
+	230: 0xffffd7,
+	231: 0xffffff,
+	232: 0x080808,
+	233: 0x121212,
+	234: 0x1c1c1c,
+	235: 0x262626,
+	236: 0x303030,
+	237: 0x3a3a3a,
+	238: 0x444444,
+	239: 0x4e4e4e,
+	240: 0x585858,
+	241: 0x626262,
+	242: 0x6c6c6c,
+	243: 0x767676,
+	244: 0x808080,
+	245: 0x8a8a8a,
+	246: 0x949494,
+	247: 0x9e9e9e,
+	248: 0xa8a8a8,
+	249: 0xb2b2b2,
+	250: 0xbcbcbc,
+	251: 0xc6c6c6,
+	252: 0xd0d0d0,
+	253: 0xdadada,
+	254: 0xe4e4e4,
+	255: 0xeeeeee,
+}
+
+// `\033]0;TITLESTR\007`
+func doTitleSequence(er *bytes.Reader) error {
+	var c byte
+	var err error
+
+	c, err = er.ReadByte()
+	if err != nil {
+		return err
+	}
+	if c != '0' && c != '2' {
+		return nil
+	}
+	c, err = er.ReadByte()
+	if err != nil {
+		return err
+	}
+	if c != ';' {
+		return nil
+	}
+	title := make([]byte, 0, 80)
+	for {
+		c, err = er.ReadByte()
+		if err != nil {
+			return err
+		}
+		if c == 0x07 || c == '\n' {
+			break
+		}
+		title = append(title, c)
+	}
+	if len(title) > 0 {
+		title8, err := syscall.UTF16PtrFromString(string(title))
+		if err == nil {
+			procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
+		}
+	}
+	return nil
+}
+
+// Write write data on console
+func (w *Writer) Write(data []byte) (n int, err error) {
+	var csbi consoleScreenBufferInfo
+	procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
+
+	handle := w.handle
+
+	var er *bytes.Reader
+	if w.rest.Len() > 0 {
+		var rest bytes.Buffer
+		w.rest.WriteTo(&rest)
+		w.rest.Reset()
+		rest.Write(data)
+		er = bytes.NewReader(rest.Bytes())
+	} else {
+		er = bytes.NewReader(data)
+	}
+	var bw [1]byte
+loop:
+	for {
+		c1, err := er.ReadByte()
+		if err != nil {
+			break loop
+		}
+		if c1 != 0x1b {
+			bw[0] = c1
+			w.out.Write(bw[:])
+			continue
+		}
+		c2, err := er.ReadByte()
+		if err != nil {
+			break loop
+		}
+
+		switch c2 {
+		case '>':
+			continue
+		case ']':
+			w.rest.WriteByte(c1)
+			w.rest.WriteByte(c2)
+			er.WriteTo(&w.rest)
+			if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
+				break loop
+			}
+			er = bytes.NewReader(w.rest.Bytes()[2:])
+			err := doTitleSequence(er)
+			if err != nil {
+				break loop
+			}
+			w.rest.Reset()
+			continue
+		// https://github.com/mattn/go-colorable/issues/27
+		case '7':
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			w.oldpos = csbi.cursorPosition
+			continue
+		case '8':
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
+			continue
+		case 0x5b:
+			// execute part after switch
+		default:
+			continue
+		}
+
+		w.rest.WriteByte(c1)
+		w.rest.WriteByte(c2)
+		er.WriteTo(&w.rest)
+
+		var buf bytes.Buffer
+		var m byte
+		for i, c := range w.rest.Bytes()[2:] {
+			if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
+				m = c
+				er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
+				w.rest.Reset()
+				break
+			}
+			buf.Write([]byte(string(c)))
+		}
+		if m == 0 {
+			break loop
+		}
+
+		switch m {
+		case 'A':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.y -= short(n)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'B':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.y += short(n)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'C':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.x += short(n)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'D':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.x -= short(n)
+			if csbi.cursorPosition.x < 0 {
+				csbi.cursorPosition.x = 0
+			}
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'E':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.x = 0
+			csbi.cursorPosition.y += short(n)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'F':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.x = 0
+			csbi.cursorPosition.y -= short(n)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'G':
+			n, err = strconv.Atoi(buf.String())
+			if err != nil {
+				continue
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			csbi.cursorPosition.x = short(n - 1)
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'H', 'f':
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			if buf.Len() > 0 {
+				token := strings.Split(buf.String(), ";")
+				switch len(token) {
+				case 1:
+					n1, err := strconv.Atoi(token[0])
+					if err != nil {
+						continue
+					}
+					csbi.cursorPosition.y = short(n1 - 1)
+				case 2:
+					n1, err := strconv.Atoi(token[0])
+					if err != nil {
+						continue
+					}
+					n2, err := strconv.Atoi(token[1])
+					if err != nil {
+						continue
+					}
+					csbi.cursorPosition.x = short(n2 - 1)
+					csbi.cursorPosition.y = short(n1 - 1)
+				}
+			} else {
+				csbi.cursorPosition.y = 0
+			}
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
+		case 'J':
+			n := 0
+			if buf.Len() > 0 {
+				n, err = strconv.Atoi(buf.String())
+				if err != nil {
+					continue
+				}
+			}
+			var count, written dword
+			var cursor coord
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			switch n {
+			case 0:
+				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
+				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
+			case 1:
+				cursor = coord{x: csbi.window.left, y: csbi.window.top}
+				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
+			case 2:
+				cursor = coord{x: csbi.window.left, y: csbi.window.top}
+				count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
+			}
+			procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+			procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+		case 'K':
+			n := 0
+			if buf.Len() > 0 {
+				n, err = strconv.Atoi(buf.String())
+				if err != nil {
+					continue
+				}
+			}
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			var cursor coord
+			var count, written dword
+			switch n {
+			case 0:
+				cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
+				count = dword(csbi.size.x - csbi.cursorPosition.x)
+			case 1:
+				cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
+				count = dword(csbi.size.x - csbi.cursorPosition.x)
+			case 2:
+				cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
+				count = dword(csbi.size.x)
+			}
+			procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+			procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
+		case 'm':
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			attr := csbi.attributes
+			cs := buf.String()
+			if cs == "" {
+				procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
+				continue
+			}
+			token := strings.Split(cs, ";")
+			for i := 0; i < len(token); i++ {
+				ns := token[i]
+				if n, err = strconv.Atoi(ns); err == nil {
+					switch {
+					case n == 0 || n == 100:
+						attr = w.oldattr
+					case 1 <= n && n <= 5:
+						attr |= foregroundIntensity
+					case n == 7:
+						attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
+					case n == 22 || n == 25:
+						attr |= foregroundIntensity
+					case n == 27:
+						attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
+					case 30 <= n && n <= 37:
+						attr &= backgroundMask
+						if (n-30)&1 != 0 {
+							attr |= foregroundRed
+						}
+						if (n-30)&2 != 0 {
+							attr |= foregroundGreen
+						}
+						if (n-30)&4 != 0 {
+							attr |= foregroundBlue
+						}
+					case n == 38: // set foreground color.
+						if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
+							if n256, err := strconv.Atoi(token[i+2]); err == nil {
+								if n256foreAttr == nil {
+									n256setup()
+								}
+								attr &= backgroundMask
+								attr |= n256foreAttr[n256]
+								i += 2
+							}
+						} else if len(token) == 5 && token[i+1] == "2" {
+							var r, g, b int
+							r, _ = strconv.Atoi(token[i+2])
+							g, _ = strconv.Atoi(token[i+3])
+							b, _ = strconv.Atoi(token[i+4])
+							i += 4
+							if r > 127 {
+								attr |= foregroundRed
+							}
+							if g > 127 {
+								attr |= foregroundGreen
+							}
+							if b > 127 {
+								attr |= foregroundBlue
+							}
+						} else {
+							attr = attr & (w.oldattr & backgroundMask)
+						}
+					case n == 39: // reset foreground color.
+						attr &= backgroundMask
+						attr |= w.oldattr & foregroundMask
+					case 40 <= n && n <= 47:
+						attr &= foregroundMask
+						if (n-40)&1 != 0 {
+							attr |= backgroundRed
+						}
+						if (n-40)&2 != 0 {
+							attr |= backgroundGreen
+						}
+						if (n-40)&4 != 0 {
+							attr |= backgroundBlue
+						}
+					case n == 48: // set background color.
+						if i < len(token)-2 && token[i+1] == "5" {
+							if n256, err := strconv.Atoi(token[i+2]); err == nil {
+								if n256backAttr == nil {
+									n256setup()
+								}
+								attr &= foregroundMask
+								attr |= n256backAttr[n256]
+								i += 2
+							}
+						} else if len(token) == 5 && token[i+1] == "2" {
+							var r, g, b int
+							r, _ = strconv.Atoi(token[i+2])
+							g, _ = strconv.Atoi(token[i+3])
+							b, _ = strconv.Atoi(token[i+4])
+							i += 4
+							if r > 127 {
+								attr |= backgroundRed
+							}
+							if g > 127 {
+								attr |= backgroundGreen
+							}
+							if b > 127 {
+								attr |= backgroundBlue
+							}
+						} else {
+							attr = attr & (w.oldattr & foregroundMask)
+						}
+					case n == 49: // reset foreground color.
+						attr &= foregroundMask
+						attr |= w.oldattr & backgroundMask
+					case 90 <= n && n <= 97:
+						attr = (attr & backgroundMask)
+						attr |= foregroundIntensity
+						if (n-90)&1 != 0 {
+							attr |= foregroundRed
+						}
+						if (n-90)&2 != 0 {
+							attr |= foregroundGreen
+						}
+						if (n-90)&4 != 0 {
+							attr |= foregroundBlue
+						}
+					case 100 <= n && n <= 107:
+						attr = (attr & foregroundMask)
+						attr |= backgroundIntensity
+						if (n-100)&1 != 0 {
+							attr |= backgroundRed
+						}
+						if (n-100)&2 != 0 {
+							attr |= backgroundGreen
+						}
+						if (n-100)&4 != 0 {
+							attr |= backgroundBlue
+						}
+					}
+					procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
+				}
+			}
+		case 'h':
+			var ci consoleCursorInfo
+			cs := buf.String()
+			if cs == "5>" {
+				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+				ci.visible = 0
+				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+			} else if cs == "?25" {
+				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+				ci.visible = 1
+				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+			} else if cs == "?1049" {
+				if w.althandle == 0 {
+					h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
+					w.althandle = syscall.Handle(h)
+					if w.althandle != 0 {
+						handle = w.althandle
+					}
+				}
+			}
+		case 'l':
+			var ci consoleCursorInfo
+			cs := buf.String()
+			if cs == "5>" {
+				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+				ci.visible = 1
+				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+			} else if cs == "?25" {
+				procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+				ci.visible = 0
+				procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
+			} else if cs == "?1049" {
+				if w.althandle != 0 {
+					syscall.CloseHandle(w.althandle)
+					w.althandle = 0
+					handle = w.handle
+				}
+			}
+		case 's':
+			procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
+			w.oldpos = csbi.cursorPosition
+		case 'u':
+			procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
+		}
+	}
+
+	return len(data), nil
+}
+
+type consoleColor struct {
+	rgb       int
+	red       bool
+	green     bool
+	blue      bool
+	intensity bool
+}
+
+func (c consoleColor) foregroundAttr() (attr word) {
+	if c.red {
+		attr |= foregroundRed
+	}
+	if c.green {
+		attr |= foregroundGreen
+	}
+	if c.blue {
+		attr |= foregroundBlue
+	}
+	if c.intensity {
+		attr |= foregroundIntensity
+	}
+	return
+}
+
+func (c consoleColor) backgroundAttr() (attr word) {
+	if c.red {
+		attr |= backgroundRed
+	}
+	if c.green {
+		attr |= backgroundGreen
+	}
+	if c.blue {
+		attr |= backgroundBlue
+	}
+	if c.intensity {
+		attr |= backgroundIntensity
+	}
+	return
+}
+
+var color16 = []consoleColor{
+	{0x000000, false, false, false, false},
+	{0x000080, false, false, true, false},
+	{0x008000, false, true, false, false},
+	{0x008080, false, true, true, false},
+	{0x800000, true, false, false, false},
+	{0x800080, true, false, true, false},
+	{0x808000, true, true, false, false},
+	{0xc0c0c0, true, true, true, false},
+	{0x808080, false, false, false, true},
+	{0x0000ff, false, false, true, true},
+	{0x00ff00, false, true, false, true},
+	{0x00ffff, false, true, true, true},
+	{0xff0000, true, false, false, true},
+	{0xff00ff, true, false, true, true},
+	{0xffff00, true, true, false, true},
+	{0xffffff, true, true, true, true},
+}
+
+type hsv struct {
+	h, s, v float32
+}
+
+func (a hsv) dist(b hsv) float32 {
+	dh := a.h - b.h
+	switch {
+	case dh > 0.5:
+		dh = 1 - dh
+	case dh < -0.5:
+		dh = -1 - dh
+	}
+	ds := a.s - b.s
+	dv := a.v - b.v
+	return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
+}
+
+func toHSV(rgb int) hsv {
+	r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
+		float32((rgb&0x00FF00)>>8)/256.0,
+		float32(rgb&0x0000FF)/256.0
+	min, max := minmax3f(r, g, b)
+	h := max - min
+	if h > 0 {
+		if max == r {
+			h = (g - b) / h
+			if h < 0 {
+				h += 6
+			}
+		} else if max == g {
+			h = 2 + (b-r)/h
+		} else {
+			h = 4 + (r-g)/h
+		}
+	}
+	h /= 6.0
+	s := max - min
+	if max != 0 {
+		s /= max
+	}
+	v := max
+	return hsv{h: h, s: s, v: v}
+}
+
+type hsvTable []hsv
+
+func toHSVTable(rgbTable []consoleColor) hsvTable {
+	t := make(hsvTable, len(rgbTable))
+	for i, c := range rgbTable {
+		t[i] = toHSV(c.rgb)
+	}
+	return t
+}
+
+func (t hsvTable) find(rgb int) consoleColor {
+	hsv := toHSV(rgb)
+	n := 7
+	l := float32(5.0)
+	for i, p := range t {
+		d := hsv.dist(p)
+		if d < l {
+			l, n = d, i
+		}
+	}
+	return color16[n]
+}
+
+func minmax3f(a, b, c float32) (min, max float32) {
+	if a < b {
+		if b < c {
+			return a, c
+		} else if a < c {
+			return a, b
+		} else {
+			return c, b
+		}
+	} else {
+		if a < c {
+			return b, c
+		} else if b < c {
+			return b, a
+		} else {
+			return c, a
+		}
+	}
+}
+
+var n256foreAttr []word
+var n256backAttr []word
+
+func n256setup() {
+	n256foreAttr = make([]word, 256)
+	n256backAttr = make([]word, 256)
+	t := toHSVTable(color16)
+	for i, rgb := range color256 {
+		c := t.find(rgb)
+		n256foreAttr[i] = c.foregroundAttr()
+		n256backAttr[i] = c.backgroundAttr()
+	}
+}
diff --git a/vendor/github.com/mattn/go-colorable/go.mod b/vendor/github.com/mattn/go-colorable/go.mod
new file mode 100644
index 00000000..ef3ca9d4
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/go.mod
@@ -0,0 +1,3 @@
+module github.com/mattn/go-colorable
+
+require github.com/mattn/go-isatty v0.0.8
diff --git a/vendor/github.com/mattn/go-colorable/go.sum b/vendor/github.com/mattn/go-colorable/go.sum
new file mode 100644
index 00000000..2c12960e
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/go.sum
@@ -0,0 +1,4 @@
+github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go
new file mode 100644
index 00000000..9721e16f
--- /dev/null
+++ b/vendor/github.com/mattn/go-colorable/noncolorable.go
@@ -0,0 +1,55 @@
+package colorable
+
+import (
+	"bytes"
+	"io"
+)
+
+// NonColorable hold writer but remove escape sequence.
+type NonColorable struct {
+	out io.Writer
+}
+
+// NewNonColorable return new instance of Writer which remove escape sequence from Writer.
+func NewNonColorable(w io.Writer) io.Writer {
+	return &NonColorable{out: w}
+}
+
+// Write write data on console
+func (w *NonColorable) Write(data []byte) (n int, err error) {
+	er := bytes.NewReader(data)
+	var bw [1]byte
+loop:
+	for {
+		c1, err := er.ReadByte()
+		if err != nil {
+			break loop
+		}
+		if c1 != 0x1b {
+			bw[0] = c1
+			w.out.Write(bw[:])
+			continue
+		}
+		c2, err := er.ReadByte()
+		if err != nil {
+			break loop
+		}
+		if c2 != 0x5b {
+			continue
+		}
+
+		var buf bytes.Buffer
+		for {
+			c, err := er.ReadByte()
+			if err != nil {
+				break loop
+			}
+			if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
+				break
+			}
+			buf.Write([]byte(string(c)))
+		}
+	}
+
+	return len(data), nil
+}
diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml
new file mode 100644
index 00000000..604314dd
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+sudo: false
+go:
+  - 1.13.x
+  - tip
+
+before_install:
+  - go get -t -v ./...
+
+script:
+  - ./go.test.sh
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)
diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE
new file mode 100644
index 00000000..65dc692b
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/LICENSE
@@ -0,0 +1,9 @@
+Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
+
+MIT License (Expat)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md
new file mode 100644
index 00000000..38418353
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/README.md
@@ -0,0 +1,50 @@
+# go-isatty
+
+[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty)
+[![Codecov](https://codecov.io/gh/mattn/go-isatty/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-isatty)
+[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty)
+
+isatty for golang
+
+## Usage
+
+```go
+package main
+
+import (
+	"fmt"
+	"github.com/mattn/go-isatty"
+	"os"
+)
+
+func main() {
+	if isatty.IsTerminal(os.Stdout.Fd()) {
+		fmt.Println("Is Terminal")
+	} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
+		fmt.Println("Is Cygwin/MSYS2 Terminal")
+	} else {
+		fmt.Println("Is Not Terminal")
+	}
+}
+```
+
+## Installation
+
+```
+$ go get github.com/mattn/go-isatty
+```
+
+## License
+
+MIT
+
+## Author
+
+Yasuhiro Matsumoto (a.k.a mattn)
+
+## Thanks
+
+* k-takata: base idea for IsCygwinTerminal
+
+    https://github.com/k-takata/go-iscygpty
diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go
new file mode 100644
index 00000000..17d4f90e
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/doc.go
@@ -0,0 +1,2 @@
+// Package isatty implements interface to isatty
+package isatty
diff --git a/vendor/github.com/mattn/go-isatty/go.mod b/vendor/github.com/mattn/go-isatty/go.mod
new file mode 100644
index 00000000..605c4c22
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/go.mod
@@ -0,0 +1,5 @@
+module github.com/mattn/go-isatty
+
+go 1.12
+
+require golang.org/x/sys v0.0.0-20200116001909-b77594299b42
diff --git a/vendor/github.com/mattn/go-isatty/go.sum b/vendor/github.com/mattn/go-isatty/go.sum
new file mode 100644
index 00000000..912e29cb
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go
new file mode 100644
index 00000000..711f2880
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go
@@ -0,0 +1,18 @@
+// +build darwin freebsd openbsd netbsd dragonfly
+// +build !appengine
+
+package isatty
+
+import "golang.org/x/sys/unix"
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+	_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
+	return err == nil
+}
+
+// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go
new file mode 100644
index 00000000..3eba4cb3
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_others.go
@@ -0,0 +1,15 @@
+// +build appengine js nacl wasm
+
+package isatty
+
+// IsTerminal returns true if the file descriptor is terminal which
+// is always false on js and appengine classic which is a sandboxed PaaS.
+func IsTerminal(fd uintptr) bool {
+	return false
+}
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_plan9.go b/vendor/github.com/mattn/go-isatty/isatty_plan9.go
new file mode 100644
index 00000000..c5b6e0c0
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_plan9.go
@@ -0,0 +1,22 @@
+// +build plan9
+
+package isatty
+
+import (
+	"syscall"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+func IsTerminal(fd uintptr) bool {
+	path, err := syscall.Fd2path(int(fd))
+	if err != nil {
+		return false
+	}
+	return path == "/dev/cons" || path == "/mnt/term/dev/cons"
+}
+
+// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
new file mode 100644
index 00000000..30106707
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go
@@ -0,0 +1,21 @@
+// +build solaris
+// +build !appengine
+
+package isatty
+
+import (
+	"golang.org/x/sys/unix"
+)
+
+// IsTerminal returns true if the given file descriptor is a terminal.
+// see: https://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libc/port/gen/isatty.c
+func IsTerminal(fd uintptr) bool {
+	_, err := unix.IoctlGetTermio(int(fd), unix.TCGETA)
+	return err == nil
+}
+
+// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_tcgets.go b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go
new file mode 100644
index 00000000..4e7b850e
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_tcgets.go
@@ -0,0 +1,18 @@
+// +build linux aix zos
+// +build !appengine
+
+package isatty
+
+import "golang.org/x/sys/unix"
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+	_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
+	return err == nil
+}
+
+// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
+// terminal. This is also always false on this environment.
+func IsCygwinTerminal(fd uintptr) bool {
+	return false
+}
diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go
new file mode 100644
index 00000000..1fa86915
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go
@@ -0,0 +1,125 @@
+// +build windows
+// +build !appengine
+
+package isatty
+
+import (
+	"errors"
+	"strings"
+	"syscall"
+	"unicode/utf16"
+	"unsafe"
+)
+
+const (
+	objectNameInfo uintptr = 1
+	fileNameInfo           = 2
+	fileTypePipe           = 3
+)
+
+var (
+	kernel32                         = syscall.NewLazyDLL("kernel32.dll")
+	ntdll                            = syscall.NewLazyDLL("ntdll.dll")
+	procGetConsoleMode               = kernel32.NewProc("GetConsoleMode")
+	procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
+	procGetFileType                  = kernel32.NewProc("GetFileType")
+	procNtQueryObject                = ntdll.NewProc("NtQueryObject")
+)
+
+func init() {
+	// Check if GetFileInformationByHandleEx is available.
+	if procGetFileInformationByHandleEx.Find() != nil {
+		procGetFileInformationByHandleEx = nil
+	}
+}
+
+// IsTerminal return true if the file descriptor is terminal.
+func IsTerminal(fd uintptr) bool {
+	var st uint32
+	r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
+	return r != 0 && e == 0
+}
+
+// Check pipe name is used for cygwin/msys2 pty.
+// Cygwin/MSYS2 PTY has a name like:
+//   \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
+func isCygwinPipeName(name string) bool {
+	token := strings.Split(name, "-")
+	if len(token) < 5 {
+		return false
+	}
+
+	if token[0] != `\msys` &&
+		token[0] != `\cygwin` &&
+		token[0] != `\Device\NamedPipe\msys` &&
+		token[0] != `\Device\NamedPipe\cygwin` {
+		return false
+	}
+
+	if token[1] == "" {
+		return false
+	}
+
+	if !strings.HasPrefix(token[2], "pty") {
+		return false
+	}
+
+	if token[3] != `from` && token[3] != `to` {
+		return false
+	}
+
+	if token[4] != "master" {
+		return false
+	}
+
+	return true
+}
+
+// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
+// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion
+// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
+// Windows vista to 10
+// see https://stackoverflow.com/a/18792477 for details
+func getFileNameByHandle(fd uintptr) (string, error) {
+	if procNtQueryObject == nil {
+		return "", errors.New("ntdll.dll: NtQueryObject not supported")
+	}
+
+	var buf [4 + syscall.MAX_PATH]uint16
+	var result int
+	r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
+		fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
+	if r != 0 {
+		return "", e
+	}
+	return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
+}
+
+// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
+// terminal.
+func IsCygwinTerminal(fd uintptr) bool {
+	if procGetFileInformationByHandleEx == nil {
+		name, err := getFileNameByHandle(fd)
+		if err != nil {
+			return false
+		}
+		return isCygwinPipeName(name)
+	}
+
+	// Cygwin/msys's pty is a pipe.
+	ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
+	if ft != fileTypePipe || e != 0 {
+		return false
+	}
+
+	var buf [2 + syscall.MAX_PATH]uint16
+	r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
+		4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
+		uintptr(len(buf)*2), 0, 0)
+	if r == 0 || e != 0 {
+		return false
+	}
+
+	l := *(*uint32)(unsafe.Pointer(&buf))
+	return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
+}
diff --git a/vendor/github.com/mgutz/ansi/.gitignore b/vendor/github.com/mgutz/ansi/.gitignore
new file mode 100644
index 00000000..9ed3b07c
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/.gitignore
@@ -0,0 +1,1 @@
+*.test
diff --git a/vendor/github.com/mgutz/ansi/LICENSE b/vendor/github.com/mgutz/ansi/LICENSE
new file mode 100644
index 00000000..06ce0c3b
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/LICENSE
@@ -0,0 +1,9 @@
+The MIT License (MIT)
+Copyright (c) 2013 Mario L. Gutierrez
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/vendor/github.com/mgutz/ansi/README.md b/vendor/github.com/mgutz/ansi/README.md
new file mode 100644
index 00000000..05905abe
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/README.md
@@ -0,0 +1,123 @@
+# ansi
+
+Package ansi is a small, fast library to create ANSI colored strings and codes.
+
+## Install
+
+Get it
+
+```sh
+go get -u github.com/mgutz/ansi
+```
+
+## Example
+
+```go
+import "github.com/mgutz/ansi"
+
+// colorize a string, SLOW
+msg := ansi.Color("foo", "red+b:white")
+
+// create a FAST closure function to avoid computation of ANSI code
+phosphorize := ansi.ColorFunc("green+h:black")
+msg = phosphorize("Bring back the 80s!")
+msg2 := phospohorize("Look, I'm a CRT!")
+
+// cache escape codes and build strings manually
+lime := ansi.ColorCode("green+h:black")
+reset := ansi.ColorCode("reset")
+
+fmt.Println(lime, "Bring back the 80s!", reset)
+```
+
+Other examples
+
+```go
+Color(s, "red")            // red
+Color(s, "red+d")          // red dim
+Color(s, "red+b")          // red bold
+Color(s, "red+B")          // red blinking
+Color(s, "red+u")          // red underline
+Color(s, "red+bh")         // red bold bright
+Color(s, "red:white")      // red on white
+Color(s, "red+b:white+h")  // red bold on white bright
+Color(s, "red+B:white+h")  // red blink on white bright
+Color(s, "off")            // turn off ansi codes
+```
+
+To view color combinations, from project directory in terminal.
+
+```sh
+go test
+```
+
+## Style format
+
+```go
+"foregroundColor+attributes:backgroundColor+attributes"
+```
+
+Colors
+
+* black
+* red
+* green
+* yellow
+* blue
+* magenta
+* cyan
+* white
+* 0...255 (256 colors)
+
+Foreground Attributes
+
+* B = Blink
+* b = bold
+* h = high intensity (bright)
+* d = dim
+* i = inverse
+* s = strikethrough
+* u = underline
+
+Background Attributes
+
+* h = high intensity (bright)
+
+## Constants
+
+* ansi.Reset
+* ansi.DefaultBG
+* ansi.DefaultFG
+* ansi.Black
+* ansi.Red
+* ansi.Green
+* ansi.Yellow
+* ansi.Blue
+* ansi.Magenta
+* ansi.Cyan
+* ansi.White
+* ansi.LightBlack
+* ansi.LightRed
+* ansi.LightGreen
+* ansi.LightYellow
+* ansi.LightBlue
+* ansi.LightMagenta
+* ansi.LightCyan
+* ansi.LightWhite
+
+## References
+
+Wikipedia ANSI escape codes [Colors](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
+
+General [tips and formatting](http://misc.flogisoft.com/bash/tip_colors_and_formatting)
+
+What about support on Windows? Use [colorable by mattn](https://github.com/mattn/go-colorable).
+Ansi and colorable are used by [logxi](https://github.com/mgutz/logxi) to support logging in
+color on Windows.
+
+## MIT License
+
+Copyright (c) 2013 Mario Gutierrez mario@mgutz.com
+
+See the file LICENSE for copying permission.
+
diff --git a/vendor/github.com/mgutz/ansi/ansi.go b/vendor/github.com/mgutz/ansi/ansi.go
new file mode 100644
index 00000000..9ab6979d
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/ansi.go
@@ -0,0 +1,291 @@
+package ansi
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+const (
+	black = iota
+	red
+	green
+	yellow
+	blue
+	magenta
+	cyan
+	white
+	defaultt = 9
+
+	normalIntensityFG = 30
+	highIntensityFG   = 90
+	normalIntensityBG = 40
+	highIntensityBG   = 100
+
+	start         = "\033["
+	normal        = "0;"
+	bold          = "1;"
+	dim           = "2;"
+	underline     = "4;"
+	blink         = "5;"
+	inverse       = "7;"
+	strikethrough = "9;"
+
+	// Reset is the ANSI reset escape sequence
+	Reset = "\033[0m"
+	// DefaultBG is the default background
+	DefaultBG = "\033[49m"
+	// DefaultFG is the default foreground
+	DefaultFG = "\033[39m"
+)
+
+// Black FG
+var Black string
+
+// Red FG
+var Red string
+
+// Green FG
+var Green string
+
+// Yellow FG
+var Yellow string
+
+// Blue FG
+var Blue string
+
+// Magenta FG
+var Magenta string
+
+// Cyan FG
+var Cyan string
+
+// White FG
+var White string
+
+// LightBlack FG
+var LightBlack string
+
+// LightRed FG
+var LightRed string
+
+// LightGreen FG
+var LightGreen string
+
+// LightYellow FG
+var LightYellow string
+
+// LightBlue FG
+var LightBlue string
+
+// LightMagenta FG
+var LightMagenta string
+
+// LightCyan FG
+var LightCyan string
+
+// LightWhite FG
+var LightWhite string
+
+var (
+	plain = false
+	// Colors maps common color names to their ANSI color code.
+	Colors = map[string]int{
+		"black":   black,
+		"red":     red,
+		"green":   green,
+		"yellow":  yellow,
+		"blue":    blue,
+		"magenta": magenta,
+		"cyan":    cyan,
+		"white":   white,
+		"default": defaultt,
+	}
+)
+
+func init() {
+	for i := 0; i < 256; i++ {
+		Colors[strconv.Itoa(i)] = i
+	}
+
+	Black = ColorCode("black")
+	Red = ColorCode("red")
+	Green = ColorCode("green")
+	Yellow = ColorCode("yellow")
+	Blue = ColorCode("blue")
+	Magenta = ColorCode("magenta")
+	Cyan = ColorCode("cyan")
+	White = ColorCode("white")
+	LightBlack = ColorCode("black+h")
+	LightRed = ColorCode("red+h")
+	LightGreen = ColorCode("green+h")
+	LightYellow = ColorCode("yellow+h")
+	LightBlue = ColorCode("blue+h")
+	LightMagenta = ColorCode("magenta+h")
+	LightCyan = ColorCode("cyan+h")
+	LightWhite = ColorCode("white+h")
+}
+
+// ColorCode returns the ANSI color color code for style.
+func ColorCode(style string) string {
+	return colorCode(style).String()
+}
+
+// Gets the ANSI color code for a style.
+func colorCode(style string) *bytes.Buffer {
+	buf := bytes.NewBufferString("")
+	if plain || style == "" {
+		return buf
+	}
+	if style == "reset" {
+		buf.WriteString(Reset)
+		return buf
+	} else if style == "off" {
+		return buf
+	}
+
+	foregroundBackground := strings.Split(style, ":")
+	foreground := strings.Split(foregroundBackground[0], "+")
+	fgKey := foreground[0]
+	fg := Colors[fgKey]
+	fgStyle := ""
+	if len(foreground) > 1 {
+		fgStyle = foreground[1]
+	}
+
+	bg, bgStyle := "", ""
+
+	if len(foregroundBackground) > 1 {
+		background := strings.Split(foregroundBackground[1], "+")
+		bg = background[0]
+		if len(background) > 1 {
+			bgStyle = background[1]
+		}
+	}
+
+	buf.WriteString(start)
+	base := normalIntensityFG
+	buf.WriteString(normal) // reset any previous style
+	if len(fgStyle) > 0 {
+		if strings.Contains(fgStyle, "b") {
+			buf.WriteString(bold)
+		}
+		if strings.Contains(fgStyle, "d") {
+			buf.WriteString(dim)
+		}
+		if strings.Contains(fgStyle, "B") {
+			buf.WriteString(blink)
+		}
+		if strings.Contains(fgStyle, "u") {
+			buf.WriteString(underline)
+		}
+		if strings.Contains(fgStyle, "i") {
+			buf.WriteString(inverse)
+		}
+		if strings.Contains(fgStyle, "s") {
+			buf.WriteString(strikethrough)
+		}
+		if strings.Contains(fgStyle, "h") {
+			base = highIntensityFG
+		}
+	}
+
+	// if 256-color
+	n, err := strconv.Atoi(fgKey)
+	if err == nil {
+		fmt.Fprintf(buf, "38;5;%d;", n)
+	} else {
+		fmt.Fprintf(buf, "%d;", base+fg)
+	}
+
+	base = normalIntensityBG
+	if len(bg) > 0 {
+		if strings.Contains(bgStyle, "h") {
+			base = highIntensityBG
+		}
+		// if 256-color
+		n, err := strconv.Atoi(bg)
+		if err == nil {
+			fmt.Fprintf(buf, "48;5;%d;", n)
+		} else {
+			fmt.Fprintf(buf, "%d;", base+Colors[bg])
+		}
+	}
+
+	// remove last ";"
+	buf.Truncate(buf.Len() - 1)
+	buf.WriteRune('m')
+	return buf
+}
+
+// Color colors a string based on the ANSI color code for style.
+func Color(s, style string) string {
+	if plain || len(style) < 1 {
+		return s
+	}
+	buf := colorCode(style)
+	buf.WriteString(s)
+	buf.WriteString(Reset)
+	return buf.String()
+}
+
+// ColorFunc creates a closure to avoid computation ANSI color code.
+func ColorFunc(style string) func(string) string {
+	if style == "" {
+		return func(s string) string {
+			return s
+		}
+	}
+	color := ColorCode(style)
+	return func(s string) string {
+		if plain || s == "" {
+			return s
+		}
+		buf := bytes.NewBufferString(color)
+		buf.WriteString(s)
+		buf.WriteString(Reset)
+		result := buf.String()
+		return result
+	}
+}
+
+// DisableColors disables ANSI color codes. The default is false (colors are on).
+func DisableColors(disable bool) {
+	plain = disable
+	if plain {
+		Black = ""
+		Red = ""
+		Green = ""
+		Yellow = ""
+		Blue = ""
+		Magenta = ""
+		Cyan = ""
+		White = ""
+		LightBlack = ""
+		LightRed = ""
+		LightGreen = ""
+		LightYellow = ""
+		LightBlue = ""
+		LightMagenta = ""
+		LightCyan = ""
+		LightWhite = ""
+	} else {
+		Black = ColorCode("black")
+		Red = ColorCode("red")
+		Green = ColorCode("green")
+		Yellow = ColorCode("yellow")
+		Blue = ColorCode("blue")
+		Magenta = ColorCode("magenta")
+		Cyan = ColorCode("cyan")
+		White = ColorCode("white")
+		LightBlack = ColorCode("black+h")
+		LightRed = ColorCode("red+h")
+		LightGreen = ColorCode("green+h")
+		LightYellow = ColorCode("yellow+h")
+		LightBlue = ColorCode("blue+h")
+		LightMagenta = ColorCode("magenta+h")
+		LightCyan = ColorCode("cyan+h")
+		LightWhite = ColorCode("white+h")
+	}
+}
diff --git a/vendor/github.com/mgutz/ansi/doc.go b/vendor/github.com/mgutz/ansi/doc.go
new file mode 100644
index 00000000..c93039b8
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/doc.go
@@ -0,0 +1,66 @@
+/*
+Package ansi is a small, fast library to create ANSI colored strings and codes.
+
+Installation
+
+    # this installs the color viewer and the package
+    go get -u github.com/mgutz/ansi/cmd/ansi-mgutz
+
+Example
+
+	// colorize a string, SLOW
+	msg := ansi.Color("foo", "red+b:white")
+
+	// create a closure to avoid recalculating ANSI code compilation
+	phosphorize := ansi.ColorFunc("green+h:black")
+	msg = phosphorize("Bring back the 80s!")
+	msg2 := phospohorize("Look, I'm a CRT!")
+
+	// cache escape codes and build strings manually
+	lime := ansi.ColorCode("green+h:black")
+	reset := ansi.ColorCode("reset")
+
+	fmt.Println(lime, "Bring back the 80s!", reset)
+
+Other examples
+
+	Color(s, "red")            // red
+	Color(s, "red+b")          // red bold
+	Color(s, "red+B")          // red blinking
+	Color(s, "red+u")          // red underline
+	Color(s, "red+bh")         // red bold bright
+	Color(s, "red:white")      // red on white
+	Color(s, "red+b:white+h")  // red bold on white bright
+	Color(s, "red+B:white+h")  // red blink on white bright
+
+To view color combinations, from terminal
+
+	ansi-mgutz
+
+Style format
+
+	"foregroundColor+attributes:backgroundColor+attributes"
+
+Colors
+
+	black
+	red
+	green
+	yellow
+	blue
+	magenta
+	cyan
+	white
+
+Attributes
+
+	b = bold foreground
+	B = Blink foreground
+	u = underline foreground
+	h = high intensity (bright) foreground, background
+	d = dim foreground
+	i = inverse
+
+Wikipedia ANSI escape codes [Colors](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors)
+*/
+package ansi
diff --git a/vendor/github.com/mgutz/ansi/print.go b/vendor/github.com/mgutz/ansi/print.go
new file mode 100644
index 00000000..806f436b
--- /dev/null
+++ b/vendor/github.com/mgutz/ansi/print.go
@@ -0,0 +1,57 @@
+package ansi
+
+import (
+	"fmt"
+	"sort"
+
+	colorable "github.com/mattn/go-colorable"
+)
+
+// PrintStyles prints all style combinations to the terminal.
+func PrintStyles() {
+	// for compatibility with Windows, not needed for *nix
+	stdout := colorable.NewColorableStdout()
+
+	bgColors := []string{
+		"",
+		":black",
+		":red",
+		":green",
+		":yellow",
+		":blue",
+		":magenta",
+		":cyan",
+		":white",
+	}
+
+	keys := make([]string, 0, len(Colors))
+	for k := range Colors {
+		keys = append(keys, k)
+	}
+
+	sort.Sort(sort.StringSlice(keys))
+
+	for _, fg := range keys {
+		for _, bg := range bgColors {
+			fmt.Fprintln(stdout, padColor(fg, []string{"" + bg, "+b" + bg, "+bh" + bg, "+u" + bg}))
+			fmt.Fprintln(stdout, padColor(fg, []string{"+s" + bg, "+i" + bg}))
+			fmt.Fprintln(stdout, padColor(fg, []string{"+uh" + bg, "+B" + bg, "+Bb" + bg /* backgrounds */, "" + bg + "+h"}))
+			fmt.Fprintln(stdout, padColor(fg, []string{"+b" + bg + "+h", "+bh" + bg + "+h", "+u" + bg + "+h", "+uh" + bg + "+h"}))
+		}
+	}
+}
+
+func pad(s string, length int) string {
+	for len(s) < length {
+		s += " "
+	}
+	return s
+}
+
+func padColor(color string, styles []string) string {
+	buffer := ""
+	for _, style := range styles {
+		buffer += Color(pad(color+style, 20), color+style)
+	}
+	return buffer
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 079d36c0..08c69e75 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -61,6 +61,9 @@ github.com/aws/aws-sdk-go/service/sts/stsiface
 github.com/beorn7/perks/quantile
 # github.com/blendle/zapdriver v1.3.1
 github.com/blendle/zapdriver
+# github.com/briandowns/spinner v1.16.0
+## explicit
+github.com/briandowns/spinner
 # github.com/census-instrumentation/opencensus-proto v0.3.0
 github.com/census-instrumentation/opencensus-proto/gen-go/agent/common/v1
 github.com/census-instrumentation/opencensus-proto/gen-go/agent/metrics/v1
@@ -77,6 +80,8 @@ github.com/emicklei/go-restful
 github.com/emicklei/go-restful/log
 # github.com/evanphx/json-patch v4.9.0+incompatible
 github.com/evanphx/json-patch
+# github.com/fatih/color v1.7.0
+github.com/fatih/color
 # github.com/ghodss/yaml v1.0.0
 github.com/ghodss/yaml
 # github.com/go-logr/logr v0.4.0
@@ -170,8 +175,17 @@ github.com/kelseyhightower/envconfig
 github.com/mailru/easyjson/buffer
 github.com/mailru/easyjson/jlexer
 github.com/mailru/easyjson/jwriter
+# github.com/mattn/go-colorable v0.1.2
+## explicit
+github.com/mattn/go-colorable
+# github.com/mattn/go-isatty v0.0.13
+## explicit
+github.com/mattn/go-isatty
 # github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369
 github.com/matttproud/golang_protobuf_extensions/pbutil
+# github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
+## explicit
+github.com/mgutz/ansi
 # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
 github.com/modern-go/concurrent
 # github.com/modern-go/reflect2 v1.0.1
@@ -486,6 +500,7 @@ gopkg.in/yaml.v3
 gotest.tools/v3/assert
 gotest.tools/v3/assert/cmp
 gotest.tools/v3/fs
+gotest.tools/v3/golden
 gotest.tools/v3/internal/assert
 gotest.tools/v3/internal/cleanup
 gotest.tools/v3/internal/difflib


Test patch:
diff --git a/pkg/cmd/repository/describe_test.go b/pkg/cmd/repository/describe_test.go
new file mode 100644
index 00000000..fbe7292d
--- /dev/null
+++ b/pkg/cmd/repository/describe_test.go
@@ -0,0 +1,189 @@
+package repository
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/google/go-github/v35/github"
+	"github.com/jonboulle/clockwork"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli/ui"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/flags"
+	testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
+	"gotest.tools/v3/golden"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	knativeapis "knative.dev/pkg/apis"
+	"knative.dev/pkg/apis/duck/v1beta1"
+	rtesting "knative.dev/pkg/reconciler/testing"
+)
+
+func newIOStream() (*ui.IOStreams, *bytes.Buffer) {
+	in := &bytes.Buffer{}
+	out := &bytes.Buffer{}
+	errOut := &bytes.Buffer{}
+	return &ui.IOStreams{
+		In:     ioutil.NopCloser(in),
+		Out:    out,
+		ErrOut: errOut,
+	}, out
+}
+
+func TestDescribe(t *testing.T) {
+	cw := clockwork.NewFakeClock()
+	type args struct {
+		currentNamespace string
+		repoName         string
+		statuses         []v1alpha1.RepositoryRunStatus
+		opts             *flags.CliOpts
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "Describe a Pipeline with a Single Run",
+			args: args{
+				repoName:         "test-run",
+				currentNamespace: "namespace",
+				opts:             &flags.CliOpts{},
+				statuses: []v1alpha1.RepositoryRunStatus{
+					{
+						Status: v1beta1.Status{
+							Conditions: []knativeapis.Condition{
+								{
+									Reason: "Success",
+								},
+							},
+						},
+						PipelineRunName: "pipelinerun1",
+						StartTime:       &metav1.Time{Time: cw.Now().Add(-16 * time.Minute)},
+						CompletionTime:  &metav1.Time{Time: cw.Now().Add(-15 * time.Minute)},
+						SHA:             github.String("SHA"),
+						Title:           github.String("A title"),
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "Describe a Pipeline with a Single Run - optnamespace",
+			args: args{
+				repoName:         "test-run",
+				currentNamespace: "namespace",
+				opts: &flags.CliOpts{
+					Namespace: "optnamespace",
+				},
+				statuses: []v1alpha1.RepositoryRunStatus{
+					{
+						Status: v1beta1.Status{
+							Conditions: []knativeapis.Condition{
+								{
+									Reason: "Success",
+								},
+							},
+						},
+						PipelineRunName: "pipelinerun1",
+						StartTime:       &metav1.Time{Time: cw.Now().Add(-16 * time.Minute)},
+						CompletionTime:  &metav1.Time{Time: cw.Now().Add(-15 * time.Minute)},
+						SHA:             github.String("SHA"),
+						Title:           github.String("A title"),
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "Describe a Pipeline with a Multiple Run",
+			args: args{
+				opts:             &flags.CliOpts{},
+				repoName:         "test-run",
+				currentNamespace: "namespace",
+				statuses: []v1alpha1.RepositoryRunStatus{
+					{
+						Status: v1beta1.Status{
+							Conditions: []knativeapis.Condition{
+								{
+									Reason: "Success",
+								},
+							},
+						},
+						PipelineRunName: "pipelinerun1",
+						StartTime:       &metav1.Time{Time: cw.Now().Add(-16 * time.Minute)},
+						CompletionTime:  &metav1.Time{Time: cw.Now().Add(-15 * time.Minute)},
+						SHA:             github.String("SHA"),
+						Title:           github.String("A title"),
+					},
+					{
+						Status: v1beta1.Status{
+							Conditions: []knativeapis.Condition{
+								{
+									Reason: "Success",
+								},
+							},
+						},
+						PipelineRunName: "pipelinerun2",
+						StartTime:       &metav1.Time{Time: cw.Now().Add(-18 * time.Minute)},
+						CompletionTime:  &metav1.Time{Time: cw.Now().Add(-17 * time.Minute)},
+						SHA:             github.String("SHA2"),
+						Title:           github.String("Another Update"),
+					},
+				},
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ns := tt.args.currentNamespace
+			if tt.args.opts.Namespace != "" {
+				ns = tt.args.opts.Namespace
+			}
+			repositories := []*v1alpha1.Repository{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      tt.args.repoName,
+						Namespace: ns,
+					},
+					Spec: v1alpha1.RepositorySpec{
+						Namespace: ns,
+						URL:       "https://anurl.com",
+						Branch:    "branch",
+						EventType: "pull_request",
+					},
+					Status: tt.args.statuses,
+				},
+			}
+
+			tdata := testclient.Data{
+				Namespaces: []*corev1.Namespace{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name: tt.args.currentNamespace,
+						},
+					},
+				},
+				Repositories: repositories,
+			}
+			ctx, _ := rtesting.SetupFakeContext(t)
+			stdata, _ := testclient.SeedTestData(t, ctx, tdata)
+			cs := &cli.Clients{
+				PipelineAsCode: stdata.PipelineAsCode,
+			}
+
+			io, out := newIOStream()
+			if err := describe(ctx, cs, cw, tt.args.opts, io, tt.args.currentNamespace,
+				tt.args.repoName); (err != nil) != tt.wantErr {
+				t.Errorf("describe() error = %v, wantErr %v", err, tt.wantErr)
+			} else {
+				golden.Assert(t, out.String(), strings.ReplaceAll(fmt.Sprintf("%s.golden", t.Name()), "/", "-"))
+			}
+		})
+	}
+}
diff --git a/pkg/cmd/repository/list_test.go b/pkg/cmd/repository/list_test.go
new file mode 100644
index 00000000..d168039d
--- /dev/null
+++ b/pkg/cmd/repository/list_test.go
@@ -0,0 +1,155 @@
+package repository
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/google/go-github/v35/github"
+	"github.com/jonboulle/clockwork"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
+	"github.com/openshift-pipelines/pipelines-as-code/pkg/flags"
+	testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
+	"gotest.tools/v3/golden"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	knativeapis "knative.dev/pkg/apis"
+	"knative.dev/pkg/apis/duck/v1beta1"
+	rtesting "knative.dev/pkg/reconciler/testing"
+)
+
+func TestList(t *testing.T) {
+	cw := clockwork.NewFakeClock()
+	namespace1 := &corev1.Namespace{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "namespace1",
+		},
+	}
+	namespace2 := &corev1.Namespace{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "namespace2",
+		},
+	}
+	repoNamespace1 := &v1alpha1.Repository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "repo1",
+			Namespace: namespace1.GetName(),
+		},
+		Spec: v1alpha1.RepositorySpec{
+			Namespace: namespace1.GetName(),
+			URL:       "https://anurl.com/owner/repo",
+			Branch:    "branch",
+			EventType: "pull_request",
+		},
+		Status: []v1alpha1.RepositoryRunStatus{
+			{
+				Status: v1beta1.Status{
+					Conditions: []knativeapis.Condition{
+						{
+							Reason: "Success",
+						},
+					},
+				},
+				PipelineRunName: "pipelinerun1",
+				StartTime:       &metav1.Time{Time: cw.Now().Add(-16 * time.Minute)},
+				CompletionTime:  &metav1.Time{Time: cw.Now().Add(-15 * time.Minute)},
+				SHA:             github.String("SHA"),
+				Title:           github.String("A title"),
+			},
+		},
+	}
+	repoNamespace2 := &v1alpha1.Repository{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "repo2",
+			Namespace: namespace2.GetName(),
+		},
+		Spec: v1alpha1.RepositorySpec{
+			Namespace: namespace2.GetName(),
+			URL:       "https://anurl.com/owner/repo",
+			Branch:    "branch",
+			EventType: "pull_request",
+		},
+		Status: []v1alpha1.RepositoryRunStatus{
+			{
+				Status: v1beta1.Status{
+					Conditions: []knativeapis.Condition{
+						{
+							Reason: "Success",
+						},
+					},
+				},
+				PipelineRunName: "pipelinerun2",
+				StartTime:       &metav1.Time{Time: cw.Now().Add(-16 * time.Minute)},
+				CompletionTime:  &metav1.Time{Time: cw.Now().Add(-15 * time.Minute)},
+				SHA:             github.String("SHA"),
+				Title:           github.String("A title"),
+			},
+		},
+	}
+
+	type args struct {
+		namespaces       []*corev1.Namespace
+		repositories     []*v1alpha1.Repository
+		currentNamespace string
+		opts             *flags.CliOpts
+		selectors        string
+		noheaders        bool
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "Test list repositories",
+			args: args{
+				opts:             &flags.CliOpts{},
+				currentNamespace: namespace1.GetName(),
+				namespaces: []*corev1.Namespace{
+					namespace1,
+				},
+				repositories: []*v1alpha1.Repository{repoNamespace1},
+			},
+		},
+		{
+			name: "Test list repositories all namespaces",
+			args: args{
+				opts:             &flags.CliOpts{AllNameSpaces: true},
+				currentNamespace: "namespace",
+				namespaces:       []*corev1.Namespace{namespace1, namespace2},
+				repositories:     []*v1alpha1.Repository{repoNamespace1, repoNamespace2},
+			},
+		},
+		{
+			name: "Test list repositories specific namespaces",
+			args: args{
+				opts:             &flags.CliOpts{},
+				currentNamespace: namespace2.GetName(),
+				namespaces:       []*corev1.Namespace{namespace1, namespace2},
+				repositories:     []*v1alpha1.Repository{repoNamespace1, repoNamespace2},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tdata := testclient.Data{
+				Namespaces:   tt.args.namespaces,
+				Repositories: tt.args.repositories,
+			}
+			ctx, _ := rtesting.SetupFakeContext(t)
+			stdata, _ := testclient.SeedTestData(t, ctx, tdata)
+			cs := &cli.Clients{
+				PipelineAsCode: stdata.PipelineAsCode,
+			}
+			io, out := newIOStream()
+			if err := list(ctx, cs, tt.args.opts, io, tt.args.currentNamespace,
+				tt.args.selectors, tt.args.noheaders); (err != nil) != tt.wantErr {
+				t.Errorf("describe() error = %v, wantErr %v", err, tt.wantErr)
+			} else {
+				golden.Assert(t, out.String(), strings.ReplaceAll(fmt.Sprintf("%s.golden", t.Name()), "/", "-"))
+			}
+		})
+	}
+}
diff --git a/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Multiple_Run.golden b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Multiple_Run.golden
new file mode 100644
index 00000000..5596ea3f
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Multiple_Run.golden
@@ -0,0 +1,20 @@
+Name:            test-run
+Namespace:       namespace
+URL:             https://anurl.com
+Event Type:      PullRequest
+Target Branch:   branch
+
+Last Run:
+
+PipelineRun:    pipelinerun1
+Status:         Success
+Commit:         https://anurl.com/commit/SHA
+Commit Title:   A title
+StartTime:      16 minutes ago
+Duration:       1 minute
+
+Other Runs:
+
+PIPELINERUN    SHA    START_TIME       DURATION   STATUS
+pipelinerun2   SHA2   18 minutes ago   1 minute   Success
+
diff --git a/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run.golden b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run.golden
new file mode 100644
index 00000000..13b3c789
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run.golden
@@ -0,0 +1,15 @@
+Name:            test-run
+Namespace:       namespace
+URL:             https://anurl.com
+Event Type:      PullRequest
+Target Branch:   branch
+
+Last Run:
+
+PipelineRun:    pipelinerun1
+Status:         Success
+Commit:         https://anurl.com/commit/SHA
+Commit Title:   A title
+StartTime:      16 minutes ago
+Duration:       1 minute
+
diff --git a/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run_-_optnamespace.golden b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run_-_optnamespace.golden
new file mode 100644
index 00000000..ef453da0
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestDescribe-Describe_a_Pipeline_with_a_Single_Run_-_optnamespace.golden
@@ -0,0 +1,15 @@
+Name:            test-run
+Namespace:       optnamespace
+URL:             https://anurl.com
+Event Type:      PullRequest
+Target Branch:   branch
+
+Last Run:
+
+PipelineRun:    pipelinerun1
+Status:         Success
+Commit:         https://anurl.com/commit/SHA
+Commit Title:   A title
+StartTime:      16 minutes ago
+Duration:       1 minute
+
diff --git a/pkg/cmd/repository/testdata/TestList-Test_list_repositories.golden b/pkg/cmd/repository/testdata/TestList-Test_list_repositories.golden
new file mode 100644
index 00000000..c7996f99
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestList-Test_list_repositories.golden
@@ -0,0 +1,2 @@
+NAME    OWNER/REPOSITORY   SHA   EVENT-TYPE     STATUS
+repo1   owner/repo         SHA   pull_request   Success
diff --git a/pkg/cmd/repository/testdata/TestList-Test_list_repositories_all_namespaces.golden b/pkg/cmd/repository/testdata/TestList-Test_list_repositories_all_namespaces.golden
new file mode 100644
index 00000000..c5b025d1
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestList-Test_list_repositories_all_namespaces.golden
@@ -0,0 +1,3 @@
+NAME    OWNER/REPOSITORY   SHA   EVENT-TYPE     NAMESPACE    STATUS
+repo1   owner/repo         SHA   pull_request   namespace1   Success
+repo2   owner/repo         SHA   pull_request   namespace2   Success
diff --git a/pkg/cmd/repository/testdata/TestList-Test_list_repositories_specific_namespaces.golden b/pkg/cmd/repository/testdata/TestList-Test_list_repositories_specific_namespaces.golden
new file mode 100644
index 00000000..efa8f652
--- /dev/null
+++ b/pkg/cmd/repository/testdata/TestList-Test_list_repositories_specific_namespaces.golden
@@ -0,0 +1,2 @@
+NAME    OWNER/REPOSITORY   SHA   EVENT-TYPE     STATUS
+repo2   owner/repo         SHA   pull_request   Success
diff --git a/pkg/test/kubernetestint/kubernetesint.go b/pkg/test/kubernetestint/kubernetesint.go
index 2b1d064c..23717cf3 100644
--- a/pkg/test/kubernetestint/kubernetesint.go
+++ b/pkg/test/kubernetestint/kubernetesint.go
@@ -23,7 +23,7 @@ func (k *KinterfaceTest) GetConsoleUI(ctx context.Context, ns string, pr string)
 
 func (k *KinterfaceTest) GetNamespace(ctx context.Context, ns string) error {
 	if k.NamespaceError {
-		return errors.New("cannot find Namespace")
+		return errors.New("cannot find namespace")
 	}
 	return nil
 }
diff --git a/pkg/test/params/params.go b/pkg/test/params/params.go
index 586c6fb7..99e061bc 100644
--- a/pkg/test/params/params.go
+++ b/pkg/test/params/params.go
@@ -7,6 +7,11 @@ import (
 
 type FakeParams struct {
 	Fakeclients *cli.Clients
+	ns          string
+}
+
+func (p FakeParams) GetNamespace() string {
+	return p.ns
 }
 
 // SetKubeConfigPath uses the kubeconfig path to instantiate tekton
diff --git a/vendor/github.com/mattn/go-isatty/go.test.sh b/vendor/github.com/mattn/go-isatty/go.test.sh
new file mode 100644
index 00000000..012162b0
--- /dev/null
+++ b/vendor/github.com/mattn/go-isatty/go.test.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+echo "" > coverage.txt
+
+for d in $(go list ./... | grep -v vendor); do
+    go test -race -coverprofile=profile.out -covermode=atomic "$d"
+    if [ -f profile.out ]; then
+        cat profile.out >> coverage.txt
+        rm profile.out
+    fi
+done
diff --git a/vendor/gotest.tools/v3/golden/golden.go b/vendor/gotest.tools/v3/golden/golden.go
new file mode 100644
index 00000000..54102dd2
--- /dev/null
+++ b/vendor/gotest.tools/v3/golden/golden.go
@@ -0,0 +1,187 @@
+/*Package golden provides tools for comparing large mutli-line strings.
+
+Golden files are files in the ./testdata/ subdirectory of the package under test.
+Golden files can be automatically updated to match new values by running
+`go test pkgname -test.update-golden`. To ensure the update is correct
+compare the diff of the old expected value to the new expected value.
+*/
+package golden // import "gotest.tools/v3/golden"
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"gotest.tools/v3/assert"
+	"gotest.tools/v3/assert/cmp"
+	"gotest.tools/v3/internal/format"
+)
+
+var flagUpdate = flag.Bool("test.update-golden", false, "update golden file")
+
+type helperT interface {
+	Helper()
+}
+
+// NormalizeCRLFToLF enables end-of-line normalization for actual values passed
+// to Assert and String, as well as the values saved to golden files with
+// -test.update-golden.
+//
+// Defaults to true. If you use the core.autocrlf=true git setting on windows
+// you will need to set this to false.
+//
+// The value may be set to false by setting GOTESTTOOLS_GOLDEN_NormalizeCRLFToLF=false
+// in the environment before running tests.
+//
+// The default value may change in a future major release.
+var NormalizeCRLFToLF = os.Getenv("GOTESTTOOLS_GOLDEN_NormalizeCRLFToLF") != "false"
+
+// FlagUpdate returns true when the -test.update-golden flag has been set.
+func FlagUpdate() bool {
+	return *flagUpdate
+}
+
+// Open opens the file in ./testdata
+func Open(t assert.TestingT, filename string) *os.File {
+	if ht, ok := t.(helperT); ok {
+		ht.Helper()
+	}
+	f, err := os.Open(Path(filename))
+	assert.NilError(t, err)
+	return f
+}
+
+// Get returns the contents of the file in ./testdata
+func Get(t assert.TestingT, filename string) []byte {
+	if ht, ok := t.(helperT); ok {
+		ht.Helper()
+	}
+	expected, err := ioutil.ReadFile(Path(filename))
+	assert.NilError(t, err)
+	return expected
+}
+
+// Path returns the full path to a file in ./testdata
+func Path(filename string) string {
+	if filepath.IsAbs(filename) {
+		return filename
+	}
+	return filepath.Join("testdata", filename)
+}
+
+func removeCarriageReturn(in []byte) []byte {
+	if !NormalizeCRLFToLF {
+		return in
+	}
+	return bytes.Replace(in, []byte("\r\n"), []byte("\n"), -1)
+}
+
+// Assert compares actual to the expected value in the golden file.
+//
+// Running `go test pkgname -test.update-golden` will write the value of actual
+// to the golden file.
+//
+// This is equivalent to assert.Assert(t, String(actual, filename))
+func Assert(t assert.TestingT, actual string, filename string, msgAndArgs ...interface{}) {
+	if ht, ok := t.(helperT); ok {
+		ht.Helper()
+	}
+	assert.Assert(t, String(actual, filename), msgAndArgs...)
+}
+
+// String compares actual to the contents of filename and returns success
+// if the strings are equal.
+//
+// Running `go test pkgname -test.update-golden` will write the value of actual
+// to the golden file.
+//
+// Any \r\n substrings in actual are converted to a single \n character
+// before comparing it to the expected string. When updating the golden file the
+// normalized version will be written to the file. This allows Windows to use
+// the same golden files as other operating systems.
+func String(actual string, filename string) cmp.Comparison {
+	return func() cmp.Result {
+		actualBytes := removeCarriageReturn([]byte(actual))
+		result, expected := compare(actualBytes, filename)
+		if result != nil {
+			return result
+		}
+		diff := format.UnifiedDiff(format.DiffConfig{
+			A:    string(expected),
+			B:    string(actualBytes),
+			From: "expected",
+			To:   "actual",
+		})
+		return cmp.ResultFailure("\n" + diff + failurePostamble(filename))
+	}
+}
+
+func failurePostamble(filename string) string {
+	return fmt.Sprintf(`
+
+You can run 'go test . -test.update-golden' to automatically update %s to the new expected value.'
+`, Path(filename))
+}
+
+// AssertBytes compares actual to the expected value in the golden.
+//
+// Running `go test pkgname -test.update-golden` will write the value of actual
+// to the golden file.
+//
+// This is equivalent to assert.Assert(t, Bytes(actual, filename))
+func AssertBytes(
+	t assert.TestingT,
+	actual []byte,
+	filename string,
+	msgAndArgs ...interface{},
+) {
+	if ht, ok := t.(helperT); ok {
+		ht.Helper()
+	}
+	assert.Assert(t, Bytes(actual, filename), msgAndArgs...)
+}
+
+// Bytes compares actual to the contents of filename and returns success
+// if the bytes are equal.
+//
+// Running `go test pkgname -test.update-golden` will write the value of actual
+// to the golden file.
+func Bytes(actual []byte, filename string) cmp.Comparison {
+	return func() cmp.Result {
+		result, expected := compare(actual, filename)
+		if result != nil {
+			return result
+		}
+		msg := fmt.Sprintf("%v (actual) != %v (expected)", actual, expected)
+		return cmp.ResultFailure(msg + failurePostamble(filename))
+	}
+}
+
+func compare(actual []byte, filename string) (cmp.Result, []byte) {
+	if err := update(filename, actual); err != nil {
+		return cmp.ResultFromError(err), nil
+	}
+	expected, err := ioutil.ReadFile(Path(filename))
+	if err != nil {
+		return cmp.ResultFromError(err), nil
+	}
+	if bytes.Equal(expected, actual) {
+		return cmp.ResultSuccess, nil
+	}
+	return nil, expected
+}
+
+func update(filename string, actual []byte) error {
+	if dir := filepath.Dir(filename); dir != "." {
+		if err := os.MkdirAll(dir, 0755); err != nil {
+			return err
+		}
+	}
+	if *flagUpdate {
+		return ioutil.WriteFile(Path(filename), actual, 0644)
+	}
+	return nil
+}