diff --git a/.circleci/config.yml b/.circleci/config.yml
index edd87a19d..8a1b5456c 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -137,6 +137,10 @@ jobs:
           working_directory: integration_tests
           environment:
             TESTING: "true"
+      - run:
+          name: "Make sure simple command do not cause any timeout"
+          command: circleci version
+
   test:
     executor: go
     steps:
@@ -378,12 +382,14 @@ workflows:
             - devex-release
       - deploy:
           requires:
+            - cucumber
             - test
             - test_mac
             - coverage
             - lint
             - deploy-test
             - shellcheck/check
+            - vulnerability-scan
           filters:
             branches:
               only: main
diff --git a/Makefile b/Makefile
index 604d50148..a51a58c4a 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ GOOS=$(shell go env GOOS)
 GOARCH=$(shell go env GOARCH)
 
 build: always
-	go build -o build/$(GOOS)/$(GOARCH)/circleci
+	go build -o build/$(GOOS)/$(GOARCH)/circleci -ldflags='-X github.com/CircleCI-Public/circleci-cli/telemetry.SegmentEndpoint=https://api.segment.io'
 
 build-all: build/linux/amd64/circleci build/darwin/amd64/circleci
 
diff --git a/README.md b/README.md
index a955cff70..b57b73873 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,20 @@ Please see the [documentation](https://circleci-public.github.io/circleci-cli) o
 
 ## Server compatibility
 
-There are some difference of behavior depending on the version you use:
- - config validation will use the GraphQL API until **Server v4.0.5, v4.1.3, v4.2.0**. The above versions will use the new route `compile-config-with-defaults`
- - `circleci orb validate` will only allow you to validate orbs using other private orbs with the option `--org-slug` from version **Server v4.2.0**
+| Functionality | Impacted commands | Change description | Compatibility with Server |
+| --- | --- | --- | --- |
+| Config compilation and validation | <ul><li>`circleci config validate`</li><li>`circleci config process`</li><li>`circleci local execute`</li> | The config validation has been moved from the GraphQL API to a specific API endpoint | <ul><li>**Server v4.0.5, v4.1.3, v4.2.0 and above**: Commands use the new specific endpoint</li><li>**Previous version**: Commands use the GraphQL API</li></ul> |
+| Orb compilation and validation of orb using private orbs | <ul><li>`circleci orb process`</li><li>`circleci orb validate`</li></ul> | To support the validation of orbs requesting private orbs (see [issue](https://github.com/CircleCI-Public/circleci-cli/issues/751)). A field `ownerId` has been added to the GraphQL orb validation endpoint. Thus allowing the `Impacted commands` to use the `--org-id` parameter to enable the orb compilation / validation  | <ul><li>**Server v4.2.0 and above**: The field is accessible so you can use the parameter</li><li>**Previous versions**: The field does not exist making the functionality unavailable</li></ul> |
+
+## Telemetry
+
+The CircleCI CLI includes a telemetry feature that collects basic errors and feature usage data in order to help us improve the experience for everyone.
+
+Telemetry works on an opt-in basis: when running a command for the first time, you will be asked for consent to enable telemetry. For non-TTY STDIN, telemetry is disabled by default, ensuring that scripts that use the CLI run smoothly.
+
+You can disable or enable telemetry anytime in one of the following ways:
+
+* Run the commands `circleci telemetry enable` or `circleci telemetry disable`
+
+* Set the `CIRCLECI_CLI_TELEMETRY_OPTOUT` environment variable to `1` or `true` to disable it
+
diff --git a/Taskfile.yml b/Taskfile.yml
index ad2d19df9..3cb8bfffc 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -47,14 +47,16 @@ tasks:
   build:
     desc: Build main
     cmds:
-      - go build -v -o build/darwin/amd64/circleci .
-  
+      # LDFlags sets the segment endpoint to an empty string thus letting the analytics library set the default endpoint on its own
+      # Not setting the `SegmentEndpoint` variable would let the value in the code ie "http://localhost"
+      - go build -v -o build/$(go env GOOS)/$(go env GOARCH)/circleci -ldflags='-X github.com/CircleCI-Public/circleci-cli/telemetry.SegmentEndpoint=https://api.segment.io' .
+
   build-linux:
     desc: Build main
     cmds:
       - go build -v -o build/linux/amd64/circleci .
- 
+
   cover:
     desc: tests and generates a cover profile
     cmds:
-      - TESTING=true go test -race -coverprofile=coverage.txt ./...
\ No newline at end of file
+      - TESTING=true go test -race -coverprofile=coverage.txt ./...
diff --git a/api/api.go b/api/api.go
index 2dbaca0bd..846626465 100644
--- a/api/api.go
+++ b/api/api.go
@@ -6,11 +6,13 @@ import (
 	"io"
 	"log"
 	"net/http"
+	"net/url"
 	"os"
 	"sort"
 	"strings"
 
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
+	"github.com/CircleCI-Public/circleci-cli/api/rest"
 	"github.com/CircleCI-Public/circleci-cli/references"
 	"github.com/CircleCI-Public/circleci-cli/settings"
 	"github.com/Masterminds/semver"
@@ -1818,3 +1820,20 @@ func FollowProject(config settings.Config, vcs string, owner string, projectName
 
 	return fr, nil
 }
+
+type Me struct {
+	ID    string `json:"id"`
+	Login string `json:"login"`
+	Name  string `json:"name"`
+}
+
+func GetMe(client *rest.Client) (Me, error) {
+	req, err := client.NewRequest("GET", &url.URL{Path: "me"}, nil)
+	if err != nil {
+		return Me{}, errors.Wrap(err, "Unable to get user info")
+	}
+
+	var me Me
+	_, err = client.DoRequest(req, &me)
+	return me, err
+}
diff --git a/clitest/clitest.go b/clitest/clitest.go
index 6a0010d5c..24b0b25bc 100644
--- a/clitest/clitest.go
+++ b/clitest/clitest.go
@@ -11,9 +11,11 @@ import (
 	"runtime"
 
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
+	"github.com/CircleCI-Public/circleci-cli/settings"
 	"github.com/onsi/gomega/gexec"
 	"github.com/onsi/gomega/ghttp"
 	"github.com/onsi/gomega/types"
+	"gopkg.in/yaml.v3"
 
 	"github.com/onsi/gomega"
 )
@@ -30,10 +32,12 @@ func ShouldFail() types.GomegaMatcher {
 
 // TempSettings contains useful settings for testing the CLI
 type TempSettings struct {
-	Home       string
-	TestServer *ghttp.Server
-	Config     *TmpFile
-	Update     *TmpFile
+	Home              string
+	TestServer        *ghttp.Server
+	Config            *TmpFile
+	Update            *TmpFile
+	Telemetry         *TmpFile
+	TelemetryDestPath string
 }
 
 // Close should be called in an AfterEach and cleans up the temp directory and server process
@@ -68,6 +72,16 @@ func WithTempSettings() *TempSettings {
 	gomega.Expect(os.Mkdir(settingsPath, 0700)).To(gomega.Succeed())
 
 	tempSettings.Config = OpenTmpFile(settingsPath, "cli.yml")
+	tempSettings.Telemetry = OpenTmpFile(settingsPath, "telemetry.yml")
+	content, err := yaml.Marshal(settings.TelemetrySettings{
+		IsEnabled:         false,
+		HasAnsweredPrompt: true,
+	})
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+	_, err = tempSettings.Telemetry.File.Write(content)
+	gomega.Expect(err).ToNot(gomega.HaveOccurred())
+	tempSettings.TelemetryDestPath = filepath.Join(tempSettings.Home, "telemetry-content")
+
 	tempSettings.Update = OpenTmpFile(settingsPath, "update_check.yml")
 
 	tempSettings.TestServer = ghttp.NewServer()
diff --git a/clitest/telemetry.go b/clitest/telemetry.go
new file mode 100644
index 000000000..fc21f85ce
--- /dev/null
+++ b/clitest/telemetry.go
@@ -0,0 +1,19 @@
+package clitest
+
+import (
+	"encoding/json"
+	"os"
+
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/onsi/gomega"
+)
+
+func CompareTelemetryEvent(settings *TempSettings, expected []telemetry.Event) {
+	content, err := os.ReadFile(settings.TelemetryDestPath)
+	gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+
+	result := []telemetry.Event{}
+	err = json.Unmarshal(content, &result)
+	gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
+	gomega.Expect(result).To(gomega.Equal(expected))
+}
diff --git a/cmd/build.go b/cmd/build.go
index c32ed969c..df4d110c4 100644
--- a/cmd/build.go
+++ b/cmd/build.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"github.com/CircleCI-Public/circleci-cli/local"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/spf13/cobra"
 )
 
@@ -16,7 +17,14 @@ func newLocalExecuteCommand(config *settings.Config) *cobra.Command {
 			return nil
 		},
 		RunE: func(cmd *cobra.Command, _ []string) error {
-			return local.Execute(cmd.Flags(), config, args)
+			err := local.Execute(cmd.Flags(), config, args)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateLocalExecuteEvent(telemetry.GetCommandInformation(cmd, true)))
+			}
+
+			return err
 		},
 		Args: cobra.MinimumNArgs(1),
 	}
diff --git a/cmd/cmd_suite_test.go b/cmd/cmd_suite_test.go
index 5f126d986..5e54304ca 100644
--- a/cmd/cmd_suite_test.go
+++ b/cmd/cmd_suite_test.go
@@ -5,6 +5,7 @@ import (
 	"os"
 	"os/exec"
 	"testing"
+	"time"
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -14,6 +15,8 @@ import (
 var pathCLI string
 
 var _ = BeforeSuite(func() {
+	SetDefaultEventuallyTimeout(time.Second * 30)
+
 	var err error
 	pathCLI, err = gexec.Build("github.com/CircleCI-Public/circleci-cli")
 	Ω(err).ShouldNot(HaveOccurred())
diff --git a/cmd/completion.go b/cmd/completion.go
index bc0ff6d53..edeaa58d4 100644
--- a/cmd/completion.go
+++ b/cmd/completion.go
@@ -3,13 +3,21 @@ package cmd
 import (
 	"os"
 
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/spf13/cobra"
 )
 
-func newCompletionCommand() *cobra.Command {
+func newCompletionCommand(config *settings.Config) *cobra.Command {
 	completionCmd := &cobra.Command{
 		Use:   "completion",
 		Short: "Generate shell completion scripts",
+		PersistentPreRun: func(cmd *cobra.Command, _ []string) {
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateCompletionCommand(telemetry.GetCommandInformation(cmd, true)))
+			}
+		},
 		Run: func(cmd *cobra.Command, _ []string) {
 			err := cmd.Help()
 			if err != nil {
diff --git a/cmd/config.go b/cmd/config.go
index 690fc78d1..8fdba6194 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -8,14 +8,14 @@ import (
 	"github.com/CircleCI-Public/circleci-config/labeling"
 	"github.com/CircleCI-Public/circleci-config/labeling/codebase"
 
-	"github.com/pkg/errors"
-	"github.com/spf13/cobra"
-	"gopkg.in/yaml.v3"
-
 	"github.com/CircleCI-Public/circleci-cli/config"
 	"github.com/CircleCI-Public/circleci-cli/filetree"
 	"github.com/CircleCI-Public/circleci-cli/proxy"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+	"gopkg.in/yaml.v3"
 )
 
 // Path to the config.yml file to operate on.
@@ -37,8 +37,14 @@ func newConfigCommand(globalConfig *settings.Config) *cobra.Command {
 	packCommand := &cobra.Command{
 		Use:   "pack <path>",
 		Short: "Pack up your CircleCI configuration into a single file.",
-		RunE: func(_ *cobra.Command, args []string) error {
-			return packConfig(args)
+		RunE: func(cmd *cobra.Command, args []string) error {
+			err := packConfig(args)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateConfigEvent(telemetry.GetCommandInformation(cmd, true), err))
+			}
+			return err
 		},
 		Args:        cobra.ExactArgs(1),
 		Annotations: make(map[string]string),
@@ -64,13 +70,20 @@ func newConfigCommand(globalConfig *settings.Config) *cobra.Command {
 			if len(args) == 1 {
 				path = args[0]
 			}
-			return compiler.ValidateConfig(config.ValidateConfigOpts{
+
+			err = compiler.ValidateConfig(config.ValidateConfigOpts{
 				ConfigPath:             path,
 				OrgID:                  orgID,
 				OrgSlug:                orgSlug,
 				IgnoreDeprecatedImages: ignoreDeprecatedImages,
 				VerboseOutput:          verboseOutput,
 			})
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateConfigEvent(telemetry.GetCommandInformation(cmd, true), err))
+			}
+
+			return err
 		},
 		Args:        cobra.MaximumNArgs(1),
 		Annotations: make(map[string]string),
@@ -112,6 +125,10 @@ func newConfigCommand(globalConfig *settings.Config) *cobra.Command {
 				PipelineParamsFilePath: pipelineParamsFilePath,
 				VerboseOutput:          verboseOutput,
 			})
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateConfigEvent(telemetry.GetCommandInformation(cmd, true), err))
+			}
 			if err != nil {
 				return err
 			}
diff --git a/cmd/config_test.go b/cmd/config_test.go
index 9168b4146..86c5e8f6d 100644
--- a/cmd/config_test.go
+++ b/cmd/config_test.go
@@ -7,6 +7,7 @@ import (
 	"path/filepath"
 
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gexec"
@@ -29,12 +30,41 @@ var _ = Describe("Config", func() {
 			tempSettings.Close()
 		})
 
+		Describe("telemetry", func() {
+			BeforeEach(func() {
+				tempSettings = clitest.WithTempSettings()
+				command = commandWithHome(pathCLI, tempSettings.Home,
+					"config", "pack",
+					"--skip-update-check",
+					filepath.Join("testdata", "hugo-pack", ".circleci"),
+				)
+				command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+			})
+
+			AfterEach(func() {
+				tempSettings.Close()
+			})
+
+			It("should send telemetry event", func() {
+				session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+				Expect(err).ShouldNot(HaveOccurred())
+
+				Eventually(session).Should(gexec.Exit(0))
+				clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+					telemetry.CreateConfigEvent(telemetry.CommandInfo{
+						Name:      "pack",
+						LocalArgs: map[string]string{"help": "false"},
+					}, nil),
+				})
+			})
+		})
+
 		Describe("a .circleci folder with config.yml and local orbs folder containing the hugo orb", func() {
 			BeforeEach(func() {
 				command = exec.Command(pathCLI,
 					"config", "pack",
 					"--skip-update-check",
-					"testdata/hugo-pack/.circleci")
+					filepath.Join("testdata", "hugo-pack", ".circleci"))
 				results = golden.Get(GinkgoT(), filepath.FromSlash("hugo-pack/result.yml"))
 			})
 
diff --git a/cmd/create_telemetry.go b/cmd/create_telemetry.go
new file mode 100644
index 000000000..645dfe675
--- /dev/null
+++ b/cmd/create_telemetry.go
@@ -0,0 +1,206 @@
+package cmd
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+
+	"github.com/CircleCI-Public/circleci-cli/api"
+	"github.com/CircleCI-Public/circleci-cli/api/rest"
+	"github.com/CircleCI-Public/circleci-cli/prompt"
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/CircleCI-Public/circleci-cli/version"
+	"github.com/google/uuid"
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+	"golang.org/x/term"
+)
+
+var (
+	CreateUUID    = func() string { return uuid.New().String() }
+	isStdinATTY   = term.IsTerminal(int(os.Stdin.Fd()))
+	isStdoutATTY  = term.IsTerminal(int(os.Stdout.Fd()))
+	anonymousUser = telemetry.User{
+		UniqueID: "cli-anonymous-telemetry",
+	}
+)
+
+type telemetryUI interface {
+	AskUserToApproveTelemetry(message string) bool
+}
+
+type telemetryInteractiveUI struct{}
+
+func (telemetryInteractiveUI) AskUserToApproveTelemetry(message string) bool {
+	return prompt.AskUserToConfirmWithDefault(message, true)
+}
+
+type TelemetryAPIClient interface {
+	GetMyUserId() (string, error)
+}
+
+func CreateAPIClient(config *settings.Config) TelemetryAPIClient {
+	return telemetryCircleCIAPI{
+		cli: rest.NewFromConfig(config.Host, config),
+	}
+}
+
+type telemetryCircleCIAPI struct {
+	cli *rest.Client
+}
+
+func (client telemetryCircleCIAPI) GetMyUserId() (string, error) {
+	me, err := api.GetMe(client.cli)
+	if err != nil {
+		return "", err
+	}
+	return me.ID, nil
+}
+
+type nullTelemetryAPIClient struct{}
+
+func (client nullTelemetryAPIClient) GetMyUserId() (string, error) {
+	panic("Should not be called")
+}
+
+// Make sure the user gave their approval for the telemetry and
+func CreateTelemetry(config *settings.Config) telemetry.Client {
+	mockTelemetry := config.MockTelemetry
+	if mockTelemetry == "" {
+		mockTelemetry = os.Getenv("MOCK_TELEMETRY")
+	}
+	if mockTelemetry != "" {
+		return telemetry.CreateFileTelemetry(mockTelemetry)
+	}
+
+	if config.IsTelemetryDisabled || len(os.Getenv("CIRCLECI_CLI_TELEMETRY_OPTOUT")) != 0 {
+		return telemetry.CreateClient(telemetry.User{}, false)
+	}
+
+	var apiClient TelemetryAPIClient = nullTelemetryAPIClient{}
+	if config.HTTPClient != nil {
+		apiClient = telemetryCircleCIAPI{
+			cli: rest.NewFromConfig(config.Host, config),
+		}
+	}
+	ui := telemetryInteractiveUI{}
+
+	telemetrySettings := settings.TelemetrySettings{}
+	user := telemetry.User{
+		IsSelfHosted: config.Host != defaultHost,
+		OS:           runtime.GOOS,
+		Version:      version.Version,
+		TeamName:     "devex",
+	}
+
+	loadTelemetrySettings(&telemetrySettings, &user, apiClient, ui)
+	client := telemetry.CreateClient(user, telemetrySettings.IsEnabled)
+
+	return client
+}
+
+func loadTelemetrySettings(settings *settings.TelemetrySettings, user *telemetry.User, apiClient TelemetryAPIClient, ui telemetryUI) {
+	err := settings.Load()
+	if err != nil && !os.IsNotExist(err) {
+		fmt.Printf("Error loading telemetry configuration: %s\n", err)
+	}
+
+	user.UniqueID = settings.UniqueID
+	user.UserID = settings.UserID
+
+	// If we already have telemetry information or that telemetry is explicitly disabled, skip
+	if settings.HasAnsweredPrompt {
+		// If we have no user id, we try requesting the user id again
+		if settings.UserID == "" && settings.IsEnabled {
+			myID, err := apiClient.GetMyUserId()
+			if err == nil {
+				settings.UserID = myID
+				user.UserID = myID
+				if err := settings.Write(); err != nil {
+					fmt.Printf("Error writing telemetry settings to disk: %s\n", err)
+				}
+			}
+		}
+
+		return
+	}
+
+	// If stdin is not a TTY, stdout is not a TTY or we are in a CI environment, send telemetry event, disable telemetry and return
+	isDisabledByDefault := !isStdinATTY || !isStdoutATTY || os.Getenv("CI") == trueString
+	if isDisabledByDefault {
+		settings.IsEnabled = false
+		err := telemetry.SendTelemetryApproval(anonymousUser, telemetry.NoStdin)
+		if err != nil {
+			fmt.Printf("Error while sending telemetry approval %s\n", err)
+		}
+		return
+	}
+
+	// Else ask user for telemetry approval
+	fmt.Println("CircleCI would like to collect CLI usage data for product improvement purposes.")
+	fmt.Println("")
+	fmt.Println("Participation is voluntary, and your choice can be changed at any time through the command `cli telemetry enable` and `cli telemetry disable`.")
+	fmt.Println("For more information, please see our privacy policy at https://circleci.com/legal/privacy/.")
+	fmt.Println("")
+	settings.IsEnabled = ui.AskUserToApproveTelemetry("Enable telemetry?")
+	settings.HasAnsweredPrompt = true
+
+	// Make sure we have user info and set them
+	if settings.IsEnabled {
+		if settings.UniqueID == "" {
+			settings.UniqueID = CreateUUID()
+		}
+		user.UniqueID = settings.UniqueID
+
+		if settings.UserID == "" {
+			myID, err := apiClient.GetMyUserId()
+			if err == nil {
+				settings.UserID = myID
+			}
+		}
+		user.UserID = settings.UserID
+	} else {
+		*user = anonymousUser
+	}
+
+	// Send telemetry approval event
+	approval := telemetry.Enabled
+	if !settings.IsEnabled {
+		approval = telemetry.Disabled
+	}
+
+	if err := telemetry.SendTelemetryApproval(*user, approval); err != nil {
+		fmt.Printf("Unable to send approval telemetry event: %s\n", err)
+	}
+
+	// Write telemetry
+	if err := settings.Write(); err != nil {
+		fmt.Printf("Error writing telemetry settings to disk: %s\n", err)
+	}
+}
+
+// Utility function used when creating telemetry events.
+// It takes a cobra Command and creates a telemetry.CommandInfo of it
+// If getParent is true, puts both the command's args in `LocalArgs` and the parent's args
+// Else only put the command's args
+// Note: child flags overwrite parent flags with same name
+func GetCommandInformation(cmd *cobra.Command, getParent bool) telemetry.CommandInfo {
+	localArgs := map[string]string{}
+
+	parent := cmd.Parent()
+	if getParent && parent != nil {
+		parent.LocalFlags().VisitAll(func(flag *pflag.Flag) {
+			localArgs[flag.Name] = flag.Value.String()
+		})
+	}
+
+	cmd.LocalFlags().VisitAll(func(flag *pflag.Flag) {
+		localArgs[flag.Name] = flag.Value.String()
+	})
+
+	return telemetry.CommandInfo{
+		Name:      cmd.Name(),
+		LocalArgs: localArgs,
+	}
+}
diff --git a/cmd/create_telemetry_test.go b/cmd/create_telemetry_test.go
new file mode 100644
index 000000000..92b5b9e9f
--- /dev/null
+++ b/cmd/create_telemetry_test.go
@@ -0,0 +1,321 @@
+package cmd
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/spf13/afero"
+	"gotest.tools/v3/assert"
+)
+
+func TestLoadTelemetrySettings(t *testing.T) {
+	// Delete env.CI variable
+	oldEnvCIValue := os.Getenv("CI")
+	os.Setenv("CI", "")
+	defer os.Setenv("CI", oldEnvCIValue)
+
+	// Mock HTTP
+	userId := "id"
+	uniqueId := "unique-id"
+
+	// Mock create UUID
+	oldUUIDCreate := CreateUUID
+	CreateUUID = func() string { return uniqueId }
+	defer (func() { CreateUUID = oldUUIDCreate })()
+
+	// Create test cases
+	type args struct {
+		closeStdin     bool
+		closeStdout    bool
+		env            map[string]string
+		promptApproval bool
+		settings       settings.TelemetrySettings
+	}
+	type want struct {
+		settings        settings.TelemetrySettings
+		fileNotCreated  bool
+		telemetryEvents []telemetry.Event
+	}
+	type testCase struct {
+		name string
+		args args
+		want want
+	}
+
+	testCases := []testCase{
+		{
+			name: "Prompt approval should be saved in settings",
+			args: args{
+				promptApproval: true,
+				settings:       settings.TelemetrySettings{},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					IsEnabled:         true,
+					HasAnsweredPrompt: true,
+					UserID:            userId,
+					UniqueID:          uniqueId,
+				},
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "enabled",
+						Properties: map[string]interface{}{
+							"UUID":           uniqueId,
+							"user_id":        userId,
+							"is_self_hosted": false,
+						}},
+				},
+			},
+		},
+		{
+			name: "Prompt disapproval should be saved in settings",
+			args: args{
+				promptApproval: false,
+				settings:       settings.TelemetrySettings{},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					IsEnabled:         false,
+					HasAnsweredPrompt: true,
+				},
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "disabled", Properties: map[string]interface{}{
+						"UUID":           "cli-anonymous-telemetry",
+						"is_self_hosted": false,
+					}},
+				},
+			},
+		},
+		{
+			name: "Does not recreate a unique ID if there is one",
+			args: args{
+				promptApproval: true,
+				settings: settings.TelemetrySettings{
+					UniqueID: "other-id",
+				},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					IsEnabled:         true,
+					HasAnsweredPrompt: true,
+					UserID:            userId,
+					UniqueID:          "other-id",
+				},
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "enabled", Properties: map[string]interface{}{
+						"UUID":           "other-id",
+						"user_id":        userId,
+						"is_self_hosted": false,
+					}},
+				},
+			},
+		},
+		{
+			name: "Does not change telemetry settings if user already answered prompt",
+			args: args{
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+				},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+				},
+				fileNotCreated: true,
+			},
+		},
+		{
+			name: "Does not change telemetry settings if stdin is not a TTY",
+			args: args{closeStdin: true},
+			want: want{
+				fileNotCreated: true,
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "disabled_default", Properties: map[string]interface{}{
+						"UUID":           "cli-anonymous-telemetry",
+						"is_self_hosted": false,
+					}},
+				},
+			},
+		},
+		{
+			name: "Does not change telemetry settings if stdout is not a TTY",
+			args: args{closeStdout: true},
+			want: want{
+				fileNotCreated: true,
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "disabled_default", Properties: map[string]interface{}{
+						"UUID":           "cli-anonymous-telemetry",
+						"is_self_hosted": false,
+					}},
+				},
+			},
+		},
+		{
+			name: "Does not change telemetry settings if env.CI == true",
+			args: args{env: map[string]string{"CI": "true"}},
+			want: want{
+				fileNotCreated: true,
+				telemetryEvents: []telemetry.Event{
+					{Object: "cli-telemetry", Action: "disabled_default", Properties: map[string]interface{}{
+						"UUID":           "cli-anonymous-telemetry",
+						"is_self_hosted": false,
+					}},
+				},
+			},
+		},
+		{
+			name: "Should try loading user id if user already answered prompt but has no user id",
+			args: args{
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+					IsEnabled:         true,
+				},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+					IsEnabled:         true,
+					UserID:            userId,
+				},
+			},
+		},
+		{
+			name: "Should consider has_answered_prompt before disabled-default scenarios",
+			args: args{
+				env: map[string]string{"CI": "true"},
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+					IsEnabled:         true,
+				},
+			},
+			want: want{
+				settings: settings.TelemetrySettings{
+					HasAnsweredPrompt: true,
+					IsEnabled:         true,
+					UserID:            userId,
+				},
+			},
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			// Mock FS
+			oldFS := settings.FS.Fs
+			settings.FS.Fs = afero.NewMemMapFs()
+			defer (func() { settings.FS.Fs = oldFS })()
+
+			// Mock stdin
+			oldIsStdinOpen := isStdinATTY
+			isStdinATTY = !tt.args.closeStdin
+			defer (func() { isStdinATTY = oldIsStdinOpen })()
+
+			// Mock stdout
+			oldIsStdoutOpen := isStdoutATTY
+			isStdoutATTY = !tt.args.closeStdout
+			defer (func() { isStdoutATTY = oldIsStdoutOpen })()
+
+			// Mock telemetry
+			telemetryClient := testTelemetry{events: make([]telemetry.Event, 0)}
+			oldCreateActiveTelemetry := telemetry.CreateActiveTelemetry
+			telemetry.CreateActiveTelemetry = func(user telemetry.User) telemetry.Client {
+				telemetryClient.User = user
+				return &telemetryClient
+			}
+			defer (func() { telemetry.CreateActiveTelemetry = oldCreateActiveTelemetry })()
+
+			// Mock env
+			if tt.args.env != nil {
+				for k, v := range tt.args.env {
+					oldEnvValue := os.Getenv(k)
+					os.Setenv(k, v)
+					defer os.Setenv(k, oldEnvValue)
+				}
+			}
+
+			// Run tested function
+			loadTelemetrySettings(&tt.args.settings, &telemetry.User{}, telemetryTestAPIClient{userId, nil}, telemetryTestUI{tt.args.promptApproval})
+			assert.DeepEqual(t, &tt.args.settings, &tt.want.settings)
+
+			// Verify good telemetry events were sent
+			expectedEvents := tt.want.telemetryEvents
+			if expectedEvents == nil {
+				expectedEvents = []telemetry.Event{}
+			}
+			assert.DeepEqual(t, telemetryClient.events, expectedEvents)
+
+			// Verify if settings file exist
+			exist, err := settings.FS.Exists(filepath.Join(settings.SettingsPath(), "telemetry.yml"))
+			assert.NilError(t, err)
+			assert.Equal(t, exist, !tt.want.fileNotCreated)
+			if tt.want.fileNotCreated {
+				return
+			}
+
+			// Verify settings file content
+			loaded := settings.TelemetrySettings{}
+			err = loaded.Load()
+			assert.NilError(t, err)
+			assert.DeepEqual(t, &loaded, &tt.want.settings)
+		})
+	}
+}
+
+type testTelemetry struct {
+	events []telemetry.Event
+	User   telemetry.User
+}
+
+func (cli *testTelemetry) Close() error { return nil }
+
+func (cli *testTelemetry) Track(event telemetry.Event) error {
+	newEvent := event
+	properties := map[string]interface{}{}
+	if cli.User.UniqueID != "" {
+		properties["UUID"] = cli.User.UniqueID
+	}
+
+	if cli.User.UserID != "" {
+		properties["user_id"] = cli.User.UserID
+	}
+
+	properties["is_self_hosted"] = cli.User.IsSelfHosted
+
+	if cli.User.OS != "" {
+		properties["os"] = cli.User.OS
+	}
+
+	if cli.User.Version != "" {
+		properties["cli_version"] = cli.User.Version
+	}
+
+	if cli.User.TeamName != "" {
+		properties["team_name"] = cli.User.TeamName
+	}
+
+	if len(properties) > 0 {
+		newEvent.Properties = properties
+	}
+
+	cli.events = append(cli.events, newEvent)
+	return nil
+}
+
+type telemetryTestUI struct {
+	Approved bool
+}
+
+func (ui telemetryTestUI) AskUserToApproveTelemetry(message string) bool {
+	return ui.Approved
+}
+
+type telemetryTestAPIClient struct {
+	id  string
+	err error
+}
+
+func (me telemetryTestAPIClient) GetMyUserId() (string, error) {
+	return me.id, me.err
+}
diff --git a/cmd/diagnostic.go b/cmd/diagnostic.go
index 071ceb257..b8053d63f 100644
--- a/cmd/diagnostic.go
+++ b/cmd/diagnostic.go
@@ -7,6 +7,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api"
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/spf13/cobra"
 )
 
@@ -28,8 +29,15 @@ func newDiagnosticCommand(config *settings.Config) *cobra.Command {
 			opts.args = args
 			opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
 		},
-		RunE: func(_ *cobra.Command, _ []string) error {
-			return diagnostic(opts)
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := diagnostic(opts)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateDiagnosticEvent(err))
+			}
+
+			return err
 		},
 	}
 
diff --git a/cmd/diagnostic_test.go b/cmd/diagnostic_test.go
index f3d48e5d5..21071d3e3 100644
--- a/cmd/diagnostic_test.go
+++ b/cmd/diagnostic_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gbytes"
@@ -79,6 +80,24 @@ var _ = Describe("Diagnostic", func() {
 		tempSettings.Close()
 	})
 
+	Describe("telemetry", func() {
+		It("should send telemetry event", func() {
+			command = commandWithHome(pathCLI, tempSettings.Home,
+				"diagnostic",
+				"--skip-update-check",
+				"--host", tempSettings.TestServer.URL())
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+			tempSettings.Config.Write([]byte(`token: mytoken`))
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+
+			Eventually(session).Should(gexec.Exit(0))
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateDiagnosticEvent(nil),
+			})
+		})
+	})
+
 	Describe("existing config file", func() {
 		Describe("token set in config file", func() {
 			BeforeEach(func() {
diff --git a/cmd/follow.go b/cmd/follow.go
index 31eb3bbae..9512423ee 100644
--- a/cmd/follow.go
+++ b/cmd/follow.go
@@ -6,6 +6,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api"
 	"github.com/CircleCI-Public/circleci-cli/git"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 )
@@ -53,8 +54,15 @@ func followProjectCommand(config *settings.Config) *cobra.Command {
 	followCommand := &cobra.Command{
 		Use:   "follow",
 		Short: "Attempt to follow the project for the current git repository.",
-		RunE: func(_ *cobra.Command, _ []string) error {
-			return followProject(opts)
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := followProject(opts)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateFollowEvent(err))
+			}
+
+			return err
 		},
 	}
 	return followCommand
diff --git a/cmd/info/info.go b/cmd/info/info.go
index 1ed3028fe..fce0bad59 100644
--- a/cmd/info/info.go
+++ b/cmd/info/info.go
@@ -4,6 +4,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api/info"
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/olekukonko/tablewriter"
 
 	"github.com/spf13/cobra"
@@ -41,7 +42,14 @@ func orgInfoCommand(client info.InfoClient, opts infoOptions) *cobra.Command {
 		Long:    `View your Organizations' names and ids.`,
 		PreRunE: opts.validator,
 		RunE: func(cmd *cobra.Command, _ []string) error {
-			return getOrgInformation(cmd, client)
+			err := getOrgInformation(cmd, client)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateInfoEvent(telemetry.GetCommandInformation(cmd, true), err))
+			}
+
+			return err
 		},
 		Annotations: make(map[string]string),
 		Example:     `circleci info org`,
diff --git a/cmd/info/info_test.go b/cmd/info/info_test.go
index 19bda1417..7b49c8784 100644
--- a/cmd/info/info_test.go
+++ b/cmd/info/info_test.go
@@ -2,6 +2,7 @@ package info
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"net/http"
 	"net/http/httptest"
@@ -9,6 +10,7 @@ import (
 
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/spf13/cobra"
 	"gotest.tools/v3/assert"
 )
@@ -106,6 +108,55 @@ func TestFailedValidator(t *testing.T) {
 	assert.Error(t, err, errorMessage)
 }
 
+type testTelemetryClient struct {
+	events []telemetry.Event
+}
+
+func (cli *testTelemetryClient) Track(event telemetry.Event) error {
+	cli.events = append(cli.events, event)
+	return nil
+}
+
+func (cli *testTelemetryClient) Close() error { return nil }
+
+func TestTelemetry(t *testing.T) {
+	telemetryClient := testTelemetryClient{make([]telemetry.Event, 0)}
+	// Test server
+	var serverHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusOK)
+		_, _ = w.Write([]byte(`[{"id":"id", "name":"name"}]`))
+	}
+	server := httptest.NewServer(serverHandler)
+	defer server.Close()
+
+	// Test command
+	config := &settings.Config{
+		Token:      "testtoken",
+		HTTPClient: http.DefaultClient,
+		Host:       server.URL,
+	}
+	cmd := NewInfoCommand(config, nil)
+	cmd.SetArgs([]string{"org"})
+	ctx := cmd.Context()
+	if ctx == nil {
+		ctx = context.Background()
+	}
+	cmd.SetContext(telemetry.NewContext(ctx, &telemetryClient))
+
+	// Execute
+	err := cmd.Execute()
+
+	assert.NilError(t, err)
+
+	// Read the telemetry events and compare them
+	assert.DeepEqual(t, telemetryClient.events, []telemetry.Event{
+		telemetry.CreateInfoEvent(telemetry.CommandInfo{
+			Name:      "org",
+			LocalArgs: map[string]string{"help": "false"},
+		}, nil),
+	})
+}
+
 func defaultValidator(cmd *cobra.Command, args []string) error {
 	return nil
 }
@@ -115,9 +166,10 @@ func scaffoldCMD(
 	validator validator.Validator,
 ) (*cobra.Command, *bytes.Buffer, *bytes.Buffer) {
 	config := &settings.Config{
-		Token:      "testtoken",
-		HTTPClient: http.DefaultClient,
-		Host:       baseURL,
+		Token:               "testtoken",
+		HTTPClient:          http.DefaultClient,
+		Host:                baseURL,
+		IsTelemetryDisabled: true,
 	}
 	cmd := NewInfoCommand(config, validator)
 
diff --git a/cmd/namespace.go b/cmd/namespace.go
index 0e5fb8cc5..37cf6cbab 100644
--- a/cmd/namespace.go
+++ b/cmd/namespace.go
@@ -8,6 +8,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
 	"github.com/CircleCI-Public/circleci-cli/prompt"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/google/uuid"
 	"github.com/spf13/cobra"
 )
@@ -74,7 +75,14 @@ Please note that at this time all namespaces created in the registry are world-r
 				}
 			}
 
-			return createNamespace(cmd, opts)
+			err := createNamespace(cmd, opts)
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateNamespaceEvent(telemetry.GetCommandInformation(cmd, true)))
+			}
+
+			return err
 		},
 		Args:        cobra.RangeArgs(1, 3),
 		Annotations: make(map[string]string),
diff --git a/cmd/namespace_test.go b/cmd/namespace_test.go
index 189e4901e..216817c96 100644
--- a/cmd/namespace_test.go
+++ b/cmd/namespace_test.go
@@ -6,6 +6,7 @@ import (
 	"os/exec"
 
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gbytes"
@@ -27,6 +28,42 @@ var _ = Describe("Namespace integration tests", func() {
 		tempSettings.Close()
 	})
 
+	Describe("telemetry", func() {
+		It("sends expected event", func() {
+			command = exec.Command(pathCLI,
+				"namespace", "create",
+				"--skip-update-check",
+				"--token", token,
+				"--host", tempSettings.TestServer.URL(),
+				"--integration-testing",
+				"foo-ns",
+				"--org-id", `"bb604b45-b6b0-4b81-ad80-796f15eddf87"`,
+			)
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+
+			tempSettings.TestServer.AppendHandlers(func(res http.ResponseWriter, req *http.Request) {
+				res.WriteHeader(http.StatusOK)
+				_, _ = res.Write([]byte(`{"data":{"organization":{"name":"test-org","id":"bb604b45-b6b0-4b81-ad80-796f15eddf87"}}}`))
+			})
+
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+			Eventually(session).Should(gexec.Exit(0))
+
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateNamespaceEvent(telemetry.CommandInfo{
+					Name: "create",
+					LocalArgs: map[string]string{
+						"help":                "false",
+						"integration-testing": "true",
+						"no-prompt":           "false",
+						"org-id":              "\"bb604b45-b6b0-4b81-ad80-796f15eddf87\"",
+					},
+				}),
+			})
+		})
+	})
+
 	Context("create, with interactive prompts", func() {
 		Describe("registering a namespace with orgID", func() {
 			BeforeEach(func() {
diff --git a/cmd/open.go b/cmd/open.go
index 6f1772b1d..2d9183550 100644
--- a/cmd/open.go
+++ b/cmd/open.go
@@ -6,6 +6,8 @@ import (
 	"strings"
 
 	"github.com/CircleCI-Public/circleci-cli/git"
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/pkg/browser"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
@@ -13,7 +15,7 @@ import (
 
 // errorMessage string containing the error message displayed in both the open command and the follow command
 var errorMessage = `
-This command is intended to be run from a git repository with a remote named 'origin' that is hosted on Github or Bitbucket only. 
+This command is intended to be run from a git repository with a remote named 'origin' that is hosted on Github or Bitbucket only.
 We are not currently supporting any other hosts.`
 
 // projectUrl uses the provided values to create the url to open
@@ -39,12 +41,19 @@ func openProjectInBrowser() error {
 }
 
 // newOpenCommand creates the cli command open
-func newOpenCommand() *cobra.Command {
+func newOpenCommand(config *settings.Config) *cobra.Command {
 	openCommand := &cobra.Command{
 		Use:   "open",
 		Short: "Open the current project in the browser.",
-		RunE: func(_ *cobra.Command, _ []string) error {
-			return openProjectInBrowser()
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := openProjectInBrowser()
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateOpenEvent(err))
+			}
+
+			return err
 		},
 	}
 	return openCommand
diff --git a/cmd/orb.go b/cmd/orb.go
index d7a011eb3..8a82fe60a 100644
--- a/cmd/orb.go
+++ b/cmd/orb.go
@@ -26,6 +26,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/prompt"
 	"github.com/CircleCI-Public/circleci-cli/references"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/CircleCI-Public/circleci-cli/version"
 	"github.com/fatih/color"
 	"github.com/pkg/errors"
@@ -389,6 +390,11 @@ Please note that at this time all orbs created in the registry are world-readabl
 			opts.args = args
 			opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
 
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateOrbEvent(telemetry.GetCommandInformation(cmd, true)))
+			}
+
 			// PersistentPreRunE overwrites the inherited persistent hook from rootCmd
 			// So we explicitly call it here to retain that behavior.
 			// As of writing this comment, that is only for daily update checks.
diff --git a/cmd/orb_test.go b/cmd/orb_test.go
index 4804d1d6a..9d26392d7 100644
--- a/cmd/orb_test.go
+++ b/cmd/orb_test.go
@@ -16,6 +16,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api"
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
 	. "github.com/onsi/gomega"
@@ -23,6 +24,57 @@ import (
 	"github.com/onsi/gomega/gexec"
 )
 
+var _ = Describe("Orb telemetry", func() {
+	var (
+		command      *exec.Cmd
+		orb          *clitest.TmpFile
+		tempSettings *clitest.TempSettings
+	)
+
+	BeforeEach(func() {
+		tempSettings = clitest.WithTempSettings()
+		orb = clitest.OpenTmpFile(tempSettings.Home, "orb.yml")
+		command = exec.Command(pathCLI,
+			"orb", "validate", orb.Path,
+			"--skip-update-check",
+			"--token", "token",
+			"--host", tempSettings.TestServer.URL(),
+		)
+		command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+	})
+
+	AfterEach(func() {
+		orb.Close()
+		tempSettings.Close()
+	})
+
+	It("works", func() {
+		orb.Write([]byte(`{}`))
+
+		mockOrbIntrospection(true, "", tempSettings)
+
+		tempSettings.TestServer.AppendHandlers(func(res http.ResponseWriter, req *http.Request) {
+			res.WriteHeader(http.StatusOK)
+			_, _ = res.Write([]byte(`{"orbConfig": {"sourceYaml": "{}", "valid": true, "errors": []} }`))
+		})
+
+		session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+		Expect(err).ShouldNot(HaveOccurred())
+		Eventually(session).Should(gexec.Exit(0))
+
+		clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+			telemetry.CreateOrbEvent(telemetry.CommandInfo{
+				Name: "validate",
+				LocalArgs: map[string]string{
+					"org-slug": "",
+					"help":     "false",
+					"org-id":   "",
+				},
+			}),
+		})
+	})
+})
+
 var _ = Describe("Orb integration tests", func() {
 	Describe("Orb help text", func() {
 		It("shows a link to the docs", func() {
diff --git a/cmd/policy/policy.go b/cmd/policy/policy.go
index 6ccab181c..19147fbc3 100644
--- a/cmd/policy/policy.go
+++ b/cmd/policy/policy.go
@@ -26,15 +26,27 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api/rest"
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
 	"github.com/CircleCI-Public/circleci-cli/config"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+
 	"github.com/CircleCI-Public/circleci-cli/settings"
 )
 
 // NewCommand creates the root policy command with all policy subcommands attached.
 func NewCommand(globalConfig *settings.Config, preRunE validator.Validator) *cobra.Command {
 	cmd := &cobra.Command{
-		Use:               "policy",
-		PersistentPreRunE: preRunE,
-		Short:             "Manage security policies",
+		Use: "policy",
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreatePolicyEvent(telemetry.GetCommandInformation(cmd, true)))
+			}
+
+			if preRunE != nil {
+				return preRunE(cmd, args)
+			}
+			return nil
+		},
+		Short: "Manage security policies",
 		Long: `Policies ensures security of build configs via security policy management framework.
 This group of commands allows the management of polices to be verified against build configs.`,
 	}
diff --git a/cmd/policy/policy_test.go b/cmd/policy/policy_test.go
index 227a6a66d..d4c503afc 100644
--- a/cmd/policy/policy_test.go
+++ b/cmd/policy/policy_test.go
@@ -57,7 +57,7 @@ func TestPushPolicyWithPrompt(t *testing.T) {
 	}))
 	defer svr.Close()
 
-	config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient}
+	config := &settings.Config{Token: "testtoken", HTTPClient: http.DefaultClient, IsTelemetryDisabled: true}
 	cmd := NewCommand(config, nil)
 
 	buffer := makeSafeBuffer()
@@ -1389,10 +1389,11 @@ func TestTestRunner(t *testing.T) {
 
 func makeCMD(circleHost string, token string) (*cobra.Command, *bytes.Buffer, *bytes.Buffer) {
 	config := &settings.Config{
-		Host:         circleHost,
-		Token:        token,
-		RestEndpoint: "/api/v2",
-		HTTPClient:   http.DefaultClient,
+		Host:                circleHost,
+		Token:               token,
+		RestEndpoint:        "/api/v2",
+		HTTPClient:          http.DefaultClient,
+		IsTelemetryDisabled: true,
 	}
 
 	cmd := NewCommand(config, nil)
diff --git a/cmd/root.go b/cmd/root.go
index 942278c69..05e028a99 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"context"
 	"fmt"
 	"log"
 	"os"
@@ -14,6 +15,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/data"
 	"github.com/CircleCI-Public/circleci-cli/md_docs"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/CircleCI-Public/circleci-cli/version"
 	"github.com/charmbracelet/lipgloss"
 	"github.com/spf13/cobra"
@@ -36,16 +38,23 @@ var rootOptions *settings.Config
 // rootTokenFromFlag stores the value passed in through the flag --token
 var rootTokenFromFlag string
 
-// Execute adds all child commands to rootCmd and
-// sets flags appropriately. This function is called
-// by main.main(). It only needs to happen once to
-// the rootCmd.
-func Execute() {
+// Execute adds all child commands to rootCmd, sets the flags appropriately
+// and put the telemetry client in the command context. This function is
+// called by main.main(). It only needs to happen once to the rootCmd
+func Execute() error {
 	header.SetCommandStr(CommandStr())
 	command := MakeCommands()
-	if err := command.Execute(); err != nil {
-		os.Exit(-1)
+
+	telemetryClient := CreateTelemetry(rootOptions)
+	defer telemetryClient.Close()
+
+	cmdContext := command.Context()
+	if cmdContext == nil {
+		cmdContext = context.Background()
 	}
+	command.SetContext(telemetry.NewContext(cmdContext, telemetryClient))
+
+	return command.Execute()
 }
 
 // Returns a string (e.g. "circleci context list") indicating what
@@ -123,7 +132,7 @@ func MakeCommands() *cobra.Command {
 		Use:   "circleci",
 		Long:  longHelp,
 		Short: rootHelpShort(rootOptions),
-		PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
+		PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
 			return rootCmdPreRun(rootOptions)
 		},
 	}
@@ -144,7 +153,7 @@ func MakeCommands() *cobra.Command {
 		return validateToken(rootOptions)
 	}
 
-	rootCmd.AddCommand(newOpenCommand())
+	rootCmd.AddCommand(newOpenCommand(rootOptions))
 	rootCmd.AddCommand(newTestsCommand())
 	rootCmd.AddCommand(newContextCommand(rootOptions))
 	rootCmd.AddCommand(project.NewProjectCommand(rootOptions, validator))
@@ -173,8 +182,9 @@ func MakeCommands() *cobra.Command {
 	rootCmd.AddCommand(newStepCommand(rootOptions))
 	rootCmd.AddCommand(newSwitchCommand(rootOptions))
 	rootCmd.AddCommand(newAdminCommand(rootOptions))
-	rootCmd.AddCommand(newCompletionCommand())
+	rootCmd.AddCommand(newCompletionCommand(rootOptions))
 	rootCmd.AddCommand(newEnvCmd())
+	rootCmd.AddCommand(newTelemetryCommand(rootOptions))
 
 	flags := rootCmd.PersistentFlags()
 
@@ -184,8 +194,9 @@ func MakeCommands() *cobra.Command {
 	flags.StringVar(&rootOptions.Endpoint, "endpoint", rootOptions.Endpoint, "URI to your CircleCI GraphQL API endpoint")
 	flags.StringVar(&rootOptions.GitHubAPI, "github-api", "https://api.github.com/", "Change the default endpoint to GitHub API for retrieving updates")
 	flags.BoolVar(&rootOptions.SkipUpdateCheck, "skip-update-check", skipUpdateByDefault(), "Skip the check for updates check run before every command.")
+	flags.StringVar(&rootOptions.MockTelemetry, "mock-telemetry", "", "The path where telemetry must be written")
 
-	hidden := []string{"github-api", "debug", "endpoint"}
+	hidden := []string{"github-api", "debug", "endpoint", "mock-telemetry"}
 
 	for _, f := range hidden {
 		if err := flags.MarkHidden(f); err != nil {
@@ -227,6 +238,7 @@ func rootCmdPreRun(rootOptions *settings.Config) error {
 		fmt.Printf("Error checking for updates: %s\n", err)
 		fmt.Printf("Please contact support.\n\n")
 	}
+
 	return nil
 }
 
diff --git a/cmd/root_test.go b/cmd/root_test.go
index c23982b18..d05a85419 100644
--- a/cmd/root_test.go
+++ b/cmd/root_test.go
@@ -16,7 +16,7 @@ var _ = Describe("Root", func() {
 	Describe("subcommands", func() {
 		It("can create commands", func() {
 			commands := cmd.MakeCommands()
-			Expect(len(commands.Commands())).To(Equal(24))
+			Expect(len(commands.Commands())).To(Equal(25))
 		})
 	})
 
@@ -52,8 +52,7 @@ var _ = Describe("Root", func() {
 
 			Eventually(session.Err.Contents()).Should(BeEmpty())
 
-			Eventually(session.Out).Should(gbytes.Say("update      This command is unavailable on your platform"))
-
+			Eventually(session.Out).Should(gbytes.Say("update\\s+This command is unavailable on your platform"))
 			Eventually(session).Should(gexec.Exit(0))
 		})
 
@@ -92,8 +91,7 @@ var _ = Describe("Root", func() {
 
 			Eventually(session.Err.Contents()).Should(BeEmpty())
 
-			Eventually(session.Out).Should(gbytes.Say("update      Update the tool to the latest version"))
-
+			Eventually(session.Out).Should(gbytes.Say("update\\s+Update the tool to the latest version"))
 			Eventually(session).Should(gexec.Exit(0))
 		})
 	})
diff --git a/cmd/runner/instance.go b/cmd/runner/instance.go
index 28f260937..a4da71338 100644
--- a/cmd/runner/instance.go
+++ b/cmd/runner/instance.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/CircleCI-Public/circleci-cli/api/runner"
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 )
 
 func newRunnerInstanceCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command {
@@ -25,7 +26,17 @@ func newRunnerInstanceCommand(o *runnerOpts, preRunE validator.Validator) *cobra
 		Aliases: []string{"ls"},
 		Args:    cobra.ExactArgs(1),
 		PreRunE: preRunE,
-		RunE: func(_ *cobra.Command, args []string) error {
+		RunE: func(cmd *cobra.Command, args []string) error {
+			var err error
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				// We defer the call to be sure the `err` has been filled
+				defer (func() {
+					_ = telemetryClient.Track(telemetry.CreateRunnerInstanceEvent(telemetry.GetCommandInformation(cmd, true), err))
+				})()
+			}
+
 			runners, err := o.r.GetRunnerInstances(args[0])
 			if err != nil {
 				return err
diff --git a/cmd/runner/resource_class.go b/cmd/runner/resource_class.go
index 1a727737e..749428a74 100644
--- a/cmd/runner/resource_class.go
+++ b/cmd/runner/resource_class.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/CircleCI-Public/circleci-cli/api/runner"
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 )
 
 func newResourceClassCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command {
@@ -16,12 +17,23 @@ func newResourceClassCommand(o *runnerOpts, preRunE validator.Validator) *cobra.
 		Short: "Operate on runner resource-classes",
 	}
 
+	telemetryWrappedPreRunE := func(cmd *cobra.Command, args []string) error {
+		telemetryClient, ok := telemetry.FromContext(cmd.Context())
+		if ok {
+			_ = telemetryClient.Track(telemetry.CreateRunnerResourceClassEvent(telemetry.GetCommandInformation(cmd, true)))
+		}
+		if preRunE != nil {
+			return preRunE(cmd, args)
+		}
+		return nil
+	}
+
 	genToken := false
 	createCmd := &cobra.Command{
 		Use:     "create <resource-class> <description>",
 		Short:   "Create a resource-class",
 		Args:    cobra.ExactArgs(2),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			cmd.PrintErr(terms)
 
@@ -54,7 +66,7 @@ func newResourceClassCommand(o *runnerOpts, preRunE validator.Validator) *cobra.
 		Short:   "Delete a resource-class",
 		Aliases: []string{"rm"},
 		Args:    cobra.ExactArgs(1),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			rc, err := o.r.GetResourceClassByName(args[0])
 			if err != nil {
@@ -72,7 +84,7 @@ func newResourceClassCommand(o *runnerOpts, preRunE validator.Validator) *cobra.
 		Short:   "List resource-classes for a namespace",
 		Aliases: []string{"ls"},
 		Args:    cobra.ExactArgs(1),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			rcs, err := o.r.GetResourceClassesByNamespace(args[0])
 			if err != nil {
diff --git a/cmd/runner/telemetry_test.go b/cmd/runner/telemetry_test.go
new file mode 100644
index 000000000..dcd12874c
--- /dev/null
+++ b/cmd/runner/telemetry_test.go
@@ -0,0 +1,62 @@
+package runner
+
+import (
+	"bytes"
+	"context"
+	"testing"
+
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"gotest.tools/v3/assert"
+)
+
+type testTelemetryClient struct {
+	events []telemetry.Event
+}
+
+func (c *testTelemetryClient) Track(event telemetry.Event) error {
+	c.events = append(c.events, event)
+	return nil
+}
+
+func (c *testTelemetryClient) Close() error { return nil }
+
+func Test_RunnerTelemetry(t *testing.T) {
+	t.Run("resource-class", func(t *testing.T) {
+		telemetryClient := &testTelemetryClient{make([]telemetry.Event, 0)}
+		runner := runnerMock{}
+		cmd := newResourceClassCommand(&runnerOpts{r: &runner}, nil)
+		stdout := new(bytes.Buffer)
+		stderr := new(bytes.Buffer)
+		cmd.SetOut(stdout)
+		cmd.SetErr(stderr)
+		ctx := cmd.Context()
+		if ctx == nil {
+			ctx = context.Background()
+		}
+		cmd.SetContext(telemetry.NewContext(ctx, telemetryClient))
+
+		defer runner.reset()
+		defer stdout.Reset()
+		defer stderr.Reset()
+
+		cmd.SetArgs([]string{
+			"create",
+			"my-namespace/my-other-resource-class",
+			"my-description",
+			"--generate-token",
+		})
+
+		err := cmd.Execute()
+		assert.NilError(t, err)
+
+		assert.DeepEqual(t, telemetryClient.events, []telemetry.Event{
+			telemetry.CreateRunnerResourceClassEvent(telemetry.CommandInfo{
+				Name: "create",
+				LocalArgs: map[string]string{
+					"generate-token": "true",
+					"help":           "false",
+				},
+			}),
+		})
+	})
+}
diff --git a/cmd/runner/token.go b/cmd/runner/token.go
index 4771b37e9..32cb11741 100644
--- a/cmd/runner/token.go
+++ b/cmd/runner/token.go
@@ -4,6 +4,7 @@ import (
 	"time"
 
 	"github.com/CircleCI-Public/circleci-cli/cmd/validator"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/olekukonko/tablewriter"
 	"github.com/spf13/cobra"
 )
@@ -14,11 +15,23 @@ func newTokenCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command
 		Short: "Operate on runner tokens",
 	}
 
+	telemetryWrappedPreRunE := func(cmd *cobra.Command, args []string) error {
+		telemetryClient, ok := telemetry.FromContext(cmd.Context())
+		if ok {
+			_ = telemetryClient.Track(telemetry.CreateRunnerTokenEvent(telemetry.GetCommandInformation(cmd, true)))
+		}
+
+		if preRunE != nil {
+			return preRunE(cmd, args)
+		}
+		return nil
+	}
+
 	cmd.AddCommand(&cobra.Command{
 		Use:     "create <resource-class> <nickname>",
 		Short:   "Create a token for a resource-class",
 		Args:    cobra.ExactArgs(2),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			token, err := o.r.CreateToken(args[0], args[1])
 			if err != nil {
@@ -33,7 +46,7 @@ func newTokenCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command
 		Short:   "Delete a token",
 		Aliases: []string{"rm"},
 		Args:    cobra.ExactArgs(1),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			return o.r.DeleteToken(args[0])
 		},
@@ -44,7 +57,7 @@ func newTokenCommand(o *runnerOpts, preRunE validator.Validator) *cobra.Command
 		Aliases: []string{"ls"},
 		Short:   "List tokens for a resource-class",
 		Args:    cobra.ExactArgs(1),
-		PreRunE: preRunE,
+		PreRunE: telemetryWrappedPreRunE,
 		RunE: func(_ *cobra.Command, args []string) error {
 			tokens, err := o.r.GetRunnerTokensByResourceClass(args[0])
 			if err != nil {
diff --git a/cmd/setup.go b/cmd/setup.go
index 6a2d068b5..efc30288a 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -7,6 +7,7 @@ import (
 	"github.com/CircleCI-Public/circleci-cli/api/graphql"
 	"github.com/CircleCI-Public/circleci-cli/prompt"
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
 )
@@ -123,7 +124,13 @@ func newSetupCommand(config *settings.Config) *cobra.Command {
 			opts.args = args
 			opts.cl = graphql.NewClient(config.HTTPClient, config.Host, config.Endpoint, config.Token, config.Debug)
 		},
-		RunE: func(_ *cobra.Command, _ []string) error {
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				// We defer the call to make sure the `opts.cfg.Host` has been filled
+				defer (func() { _ = telemetryClient.Track(telemetry.CreateSetupEvent(opts.cfg.Host != defaultHost)) })()
+			}
+
 			if opts.integrationTesting {
 				opts.tty = setupTestUI{
 					host:            "boondoggle",
diff --git a/cmd/setup_test.go b/cmd/setup_test.go
index 591a6197d..8ae09473c 100644
--- a/cmd/setup_test.go
+++ b/cmd/setup_test.go
@@ -9,12 +9,44 @@ import (
 	"runtime"
 
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gbytes"
 	"github.com/onsi/gomega/gexec"
 )
 
+var _ = Describe("Setup telemetry", func() {
+	var (
+		command      *exec.Cmd
+		tempSettings *clitest.TempSettings
+	)
+
+	BeforeEach(func() {
+		tempSettings = clitest.WithTempSettings()
+		command = commandWithHome(pathCLI, tempSettings.Home,
+			"setup",
+			"--integration-testing",
+			"--skip-update-check",
+		)
+		command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+	})
+
+	AfterEach(func() {
+		tempSettings.Close()
+	})
+
+	It("should send telemetry event", func() {
+		session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+		Expect(err).ShouldNot(HaveOccurred())
+
+		Eventually(session).Should(gexec.Exit(0))
+		clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+			telemetry.CreateSetupEvent(true),
+		})
+	})
+})
+
 var _ = Describe("Setup with prompts", func() {
 	var (
 		command      *exec.Cmd
diff --git a/cmd/telemetry.go b/cmd/telemetry.go
new file mode 100644
index 000000000..85e231f74
--- /dev/null
+++ b/cmd/telemetry.go
@@ -0,0 +1,79 @@
+package cmd
+
+import (
+	"os"
+
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+)
+
+func newTelemetryCommand(config *settings.Config) *cobra.Command {
+	apiClient := CreateAPIClient(config)
+
+	telemetryEnable := &cobra.Command{
+		Use:   "enable",
+		Short: "Allow telemetry events to be sent to CircleCI servers",
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := setIsTelemetryActive(apiClient, true)
+			if telemetryClient, ok := telemetry.FromContext(cmd.Context()); ok {
+				_ = telemetryClient.Track(telemetry.CreateChangeTelemetryStatusEvent("enabled", "telemetry-command", err))
+			}
+			return err
+		},
+		Args: cobra.ExactArgs(0),
+	}
+
+	telemetryDisable := &cobra.Command{
+		Use:   "disable",
+		Short: "Make sure no telemetry events is sent to CircleCI servers",
+		RunE: func(cmd *cobra.Command, _ []string) error {
+			err := setIsTelemetryActive(apiClient, false)
+			if telemetryClient, ok := telemetry.FromContext(cmd.Context()); ok {
+				_ = telemetryClient.Track(telemetry.CreateChangeTelemetryStatusEvent("disabled", "telemetry-command", err))
+			}
+			return err
+		},
+		Args: cobra.ExactArgs(0),
+	}
+
+	telemetryCommand := &cobra.Command{
+		Use:   "telemetry",
+		Short: "Configure telemetry preferences",
+		Long: `Configure telemetry preferences.
+
+Note: If you have not configured your telemetry preferences and call the CLI with a closed stdin, telemetry will be disabled`,
+	}
+
+	telemetryCommand.AddCommand(telemetryEnable)
+	telemetryCommand.AddCommand(telemetryDisable)
+
+	return telemetryCommand
+}
+
+func setIsTelemetryActive(apiClient TelemetryAPIClient, isActive bool) error {
+	settings := settings.TelemetrySettings{}
+	if err := settings.Load(); err != nil && !os.IsNotExist(err) {
+		return errors.Wrap(err, "Loading telemetry configuration")
+	}
+
+	settings.HasAnsweredPrompt = true
+	settings.IsEnabled = isActive
+
+	if settings.UniqueID == "" {
+		settings.UniqueID = CreateUUID()
+	}
+
+	if settings.UserID == "" {
+		if myID, err := apiClient.GetMyUserId(); err == nil {
+			settings.UserID = myID
+		}
+	}
+
+	if err := settings.Write(); err != nil {
+		return errors.Wrap(err, "Writing telemetry configuration")
+	}
+
+	return nil
+}
diff --git a/cmd/telemetry_test.go b/cmd/telemetry_test.go
new file mode 100644
index 000000000..fb8d334db
--- /dev/null
+++ b/cmd/telemetry_test.go
@@ -0,0 +1,58 @@
+package cmd_test
+
+import (
+	"fmt"
+	"os/exec"
+
+	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	"github.com/onsi/gomega/gexec"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Telemetry events on telemetry commands", func() {
+	var (
+		tempSettings *clitest.TempSettings
+		command      *exec.Cmd
+	)
+
+	BeforeEach(func() {
+		tempSettings = clitest.WithTempSettings()
+	})
+
+	AfterEach(func() {
+		tempSettings.Close()
+	})
+
+	Describe("telemetry enable", func() {
+		It("should send an event", func() {
+			command = exec.Command(pathCLI, "telemetry", "enable")
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+			Eventually(session).Should(gexec.Exit(0))
+
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateChangeTelemetryStatusEvent("enabled", "telemetry-command", nil),
+			})
+		})
+	})
+
+	Describe("telemetry disable", func() {
+		It("should send an event", func() {
+			command = exec.Command(pathCLI, "telemetry", "disable")
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+			Eventually(session).Should(gexec.Exit(0))
+
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateChangeTelemetryStatusEvent("disabled", "telemetry-command", nil),
+			})
+		})
+	})
+})
diff --git a/cmd/telemetry_unit_test.go b/cmd/telemetry_unit_test.go
new file mode 100644
index 000000000..468e1ce9d
--- /dev/null
+++ b/cmd/telemetry_unit_test.go
@@ -0,0 +1,143 @@
+package cmd
+
+import (
+	"path/filepath"
+	"testing"
+
+	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/spf13/afero"
+	"gotest.tools/v3/assert"
+)
+
+func TestSetIsTelemetryActive(t *testing.T) {
+	type args struct {
+		apiClient TelemetryAPIClient
+		isActive  bool
+		settings  *settings.TelemetrySettings
+	}
+	type want struct {
+		settings *settings.TelemetrySettings
+	}
+
+	type testCase struct {
+		name string
+		args args
+		want want
+	}
+
+	userId := "user-id"
+	uniqueId := "unique-id"
+
+	testCases := []testCase{
+		{
+			name: "Enabling telemetry with settings should just update the is active field",
+			args: args{
+				apiClient: telemetryTestAPIClient{},
+				isActive:  true,
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         false,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+			want: want{
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         true,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+		},
+		{
+			name: "Enabling telemetry without settings should fill the settings fields",
+			args: args{
+				apiClient: telemetryTestAPIClient{id: userId, err: nil},
+				isActive:  true,
+				settings:  nil,
+			},
+			want: want{
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         true,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+		},
+		{
+			name: "Disabling telemetry with settings should just update the is active field",
+			args: args{
+				apiClient: telemetryTestAPIClient{},
+				isActive:  false,
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         true,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+			want: want{
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         false,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+		},
+		{
+			name: "Enabling telemetry without settings should fill the settings fields",
+			args: args{
+				apiClient: telemetryTestAPIClient{id: userId, err: nil},
+				isActive:  false,
+				settings:  nil,
+			},
+			want: want{
+				settings: &settings.TelemetrySettings{
+					IsEnabled:         false,
+					HasAnsweredPrompt: true,
+					UniqueID:          uniqueId,
+					UserID:            userId,
+				},
+			},
+		},
+	}
+
+	// Mock create UUID
+	oldUUIDCreate := CreateUUID
+	CreateUUID = func() string { return uniqueId }
+	defer (func() { CreateUUID = oldUUIDCreate })()
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			// Mock FS
+			oldFS := settings.FS.Fs
+			settings.FS.Fs = afero.NewMemMapFs()
+			defer (func() { settings.FS.Fs = oldFS })()
+
+			if tt.args.settings != nil {
+				err := tt.args.settings.Write()
+				assert.NilError(t, err)
+			}
+
+			err := setIsTelemetryActive(tt.args.apiClient, tt.args.isActive)
+			assert.NilError(t, err)
+
+			exist, err := settings.FS.Exists(filepath.Join(settings.SettingsPath(), "telemetry.yml"))
+			assert.NilError(t, err)
+			if tt.want.settings == nil {
+				assert.Equal(t, exist, false)
+			} else {
+				assert.Equal(t, exist, true)
+
+				loadedSettings := &settings.TelemetrySettings{}
+				err := loadedSettings.Load()
+				assert.NilError(t, err)
+
+				assert.DeepEqual(t, tt.want.settings, loadedSettings)
+			}
+		})
+	}
+}
diff --git a/cmd/update.go b/cmd/update.go
index c43bf33d4..5ef2bcc44 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -5,6 +5,7 @@ import (
 	"time"
 
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/CircleCI-Public/circleci-cli/update"
 	"github.com/CircleCI-Public/circleci-cli/version"
 
@@ -28,8 +29,13 @@ func newUpdateCommand(config *settings.Config) *cobra.Command {
 	update := &cobra.Command{
 		Use:   "update",
 		Short: "Update the tool to the latest version",
-		PersistentPreRun: func(_ *cobra.Command, _ []string) {
+		PersistentPreRun: func(cmd *cobra.Command, _ []string) {
 			opts.cfg.SkipUpdateCheck = true
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateUpdateEvent(telemetry.GetCommandInformation(cmd, cmd.Name() != "update")))
+			}
 		},
 		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.args = args
@@ -43,9 +49,6 @@ func newUpdateCommand(config *settings.Config) *cobra.Command {
 		Use:    "check",
 		Hidden: true,
 		Short:  "Check if there are any updates available",
-		PersistentPreRun: func(_ *cobra.Command, _ []string) {
-			opts.cfg.SkipUpdateCheck = true
-		},
 		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.args = args
 			opts.dryRun = true
@@ -59,9 +62,6 @@ func newUpdateCommand(config *settings.Config) *cobra.Command {
 		Use:    "install",
 		Hidden: true,
 		Short:  "Update the tool to the latest version",
-		PersistentPreRun: func(_ *cobra.Command, _ []string) {
-			opts.cfg.SkipUpdateCheck = true
-		},
 		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.args = args
 		},
@@ -74,9 +74,6 @@ func newUpdateCommand(config *settings.Config) *cobra.Command {
 		Use:    "build-agent",
 		Hidden: true,
 		Short:  "This command has no effect, and is kept for backwards compatibility",
-		PersistentPreRun: func(_ *cobra.Command, _ []string) {
-			opts.cfg.SkipUpdateCheck = true
-		},
 		PreRun: func(cmd *cobra.Command, args []string) {
 			opts.args = args
 		},
diff --git a/cmd/update_test.go b/cmd/update_test.go
index 431987119..6ed7aa5dd 100644
--- a/cmd/update_test.go
+++ b/cmd/update_test.go
@@ -1,11 +1,13 @@
 package cmd_test
 
 import (
+	"fmt"
 	"net/http"
 	"os/exec"
 	"path/filepath"
 
 	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/onsi/gomega/gbytes"
@@ -70,6 +72,69 @@ var _ = Describe("Update", func() {
 		tempSettings.Close()
 	})
 
+	Describe("telemetry", func() {
+		It("should send telemetry event when calling parent command", func() {
+			updateCLI, err := gexec.Build("github.com/CircleCI-Public/circleci-cli")
+			Expect(err).ShouldNot(HaveOccurred())
+
+			command = exec.Command(updateCLI,
+				"update",
+				"--github-api", tempSettings.TestServer.URL(),
+			)
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+
+			assetBytes := golden.Get(GinkgoT(), filepath.FromSlash("update/foo.zip"))
+			assetResponse := string(assetBytes)
+
+			tempSettings.TestServer.AppendHandlers(
+				ghttp.CombineHandlers(
+					ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases"),
+					ghttp.RespondWith(http.StatusOK, response),
+				),
+				ghttp.CombineHandlers(
+					ghttp.VerifyRequest(http.MethodGet, "/repos/CircleCI-Public/circleci-cli/releases/assets/1"),
+					ghttp.RespondWith(http.StatusOK, assetResponse),
+				),
+			)
+
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+
+			Eventually(session).Should(gexec.Exit(0))
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateUpdateEvent(telemetry.CommandInfo{
+					Name: "update",
+					LocalArgs: map[string]string{
+						"check": "false",
+						"help":  "false",
+					},
+				}),
+			})
+		})
+
+		It("should send telemetry event when calling child command", func() {
+			command = exec.Command(pathCLI,
+				"update",
+				"check",
+				"--github-api", tempSettings.TestServer.URL(),
+			)
+			command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+			session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+			Expect(err).ShouldNot(HaveOccurred())
+
+			Eventually(session).Should(gexec.Exit(0))
+			clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+				telemetry.CreateUpdateEvent(telemetry.CommandInfo{
+					Name: "check",
+					LocalArgs: map[string]string{
+						"check": "false",
+						"help":  "false",
+					},
+				}),
+			})
+		})
+	})
+
 	Describe("update --check", func() {
 		BeforeEach(func() {
 			command = exec.Command(pathCLI,
diff --git a/cmd/version.go b/cmd/version.go
index 756e5e6ce..e2cd566ce 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/CircleCI-Public/circleci-cli/settings"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
 	"github.com/CircleCI-Public/circleci-cli/version"
 	"github.com/spf13/cobra"
 )
@@ -24,11 +25,18 @@ func newVersionCommand(config *settings.Config) *cobra.Command {
 		PersistentPreRun: func(_ *cobra.Command, _ []string) {
 			opts.cfg.SkipUpdateCheck = true
 		},
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRun: func(_ *cobra.Command, args []string) {
 			opts.args = args
 		},
-		Run: func(_ *cobra.Command, _ []string) {
-			fmt.Printf("%s+%s (%s)\n", version.Version, version.Commit, version.PackageManager())
+		Run: func(cmd *cobra.Command, _ []string) {
+			version := fmt.Sprintf("%s+%s (%s)", version.Version, version.Commit, version.PackageManager())
+
+			telemetryClient, ok := telemetry.FromContext(cmd.Context())
+			if ok {
+				_ = telemetryClient.Track(telemetry.CreateVersionEvent(version))
+			}
+
+			fmt.Printf("%s\n", version)
 		},
 	}
 }
diff --git a/cmd/version_test.go b/cmd/version_test.go
new file mode 100644
index 000000000..e64cf051e
--- /dev/null
+++ b/cmd/version_test.go
@@ -0,0 +1,39 @@
+package cmd_test
+
+import (
+	"fmt"
+	"os/exec"
+
+	"github.com/CircleCI-Public/circleci-cli/clitest"
+	"github.com/CircleCI-Public/circleci-cli/telemetry"
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Version telemetry", func() {
+	var (
+		command      *exec.Cmd
+		tempSettings *clitest.TempSettings
+	)
+
+	BeforeEach(func() {
+		tempSettings = clitest.WithTempSettings()
+		command = commandWithHome(pathCLI, tempSettings.Home, "version")
+		command.Env = append(command.Env, fmt.Sprintf("MOCK_TELEMETRY=%s", tempSettings.TelemetryDestPath))
+	})
+
+	AfterEach(func() {
+		tempSettings.Close()
+	})
+
+	It("should send a telemetry event", func() {
+		session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+		Expect(err).ShouldNot(HaveOccurred())
+
+		Eventually(session).Should(gexec.Exit(0))
+		clitest.CompareTelemetryEvent(tempSettings, []telemetry.Event{
+			telemetry.CreateVersionEvent("0.0.0-dev+dirty-local-tree (source)"),
+		})
+	})
+})
diff --git a/go.mod b/go.mod
index f305f8565..710386a9d 100644
--- a/go.mod
+++ b/go.mod
@@ -37,6 +37,8 @@ require (
 	github.com/charmbracelet/lipgloss v0.5.0
 	github.com/erikgeiser/promptkit v0.7.0
 	github.com/hexops/gotextdiff v1.0.3
+	github.com/segmentio/analytics-go v3.1.0+incompatible
+	github.com/spf13/afero v1.9.5
 	github.com/stretchr/testify v1.8.3
 	golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
 	golang.org/x/term v0.10.0
@@ -50,6 +52,7 @@ require (
 	github.com/agnivade/levenshtein v1.1.1 // indirect
 	github.com/atotto/clipboard v0.1.4 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/charmbracelet/bubbles v0.11.0 // indirect
 	github.com/charmbracelet/bubbletea v0.21.0 // indirect
@@ -94,6 +97,7 @@ require (
 	github.com/prometheus/procfs v0.11.0 // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
+	github.com/segmentio/backo-go v1.0.1 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/sirupsen/logrus v1.9.3 // indirect
 	github.com/skeema/knownhosts v1.1.1 // indirect
@@ -101,6 +105,7 @@ require (
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	github.com/yashtewari/glob-intersection v0.2.0 // indirect
 	go.opentelemetry.io/otel v1.16.0 // indirect
 	go.opentelemetry.io/otel/metric v1.16.0 // indirect
diff --git a/go.sum b/go.sum
index 7ab560a5e..4d5d49640 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,45 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI=
 github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/CircleCI-Public/circle-policy-agent v0.0.683 h1:EzZaLy9mUGl4dwDNWceBHeDb3X0KAAjV4eFOk3C7lts=
 github.com/CircleCI-Public/circle-policy-agent v0.0.683/go.mod h1:72U4Q4OtvAGRGGo/GqlCCO0tARg1cSG9xwxWyz3ktQI=
 github.com/CircleCI-Public/circleci-config v0.0.0-20230629192034-c469d9e9936b h1:emg7uU3bRjVMlwSpOATBiybaBPXNWUIiFE/qbQQXZtE=
@@ -32,11 +72,14 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
 github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY=
 github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU=
 github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
 github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -47,9 +90,16 @@ github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K
 github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
 github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
 github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
 github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
 github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
 github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -65,6 +115,12 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
 github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/erikgeiser/promptkit v0.7.0 h1:Yi28iN6JRs8/0x+wjQRPfWb+vWz1pFmZ5fu2uoFipD8=
 github.com/erikgeiser/promptkit v0.7.0/go.mod h1:Jj9bhN+N8RbMjB1jthkr9A4ydmczZ1WZJ8xTXnP12dg=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -87,6 +143,9 @@ github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw4
 github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
 github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
 github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
 github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -98,26 +157,51 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78
 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -125,17 +209,39 @@ github.com/google/go-github v15.0.0+incompatible h1:jlPg2Cpsxb/FyEV/MFiIE9tW/2RA
 github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0=
 github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
 github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY=
 github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
 github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8=
@@ -144,11 +250,15 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -220,10 +330,12 @@ github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
 github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
 github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
@@ -237,10 +349,15 @@ github.com/rhysd/go-github-selfupdate v0.0.0-20180520142321-41c1bbb0804a/go.mod
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
+github.com/segmentio/analytics-go v3.1.0+incompatible h1:IyiOfUgQFVHvsykKKbdI7ZsH374uv3/DfZUo9+G0Z80=
+github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48=
+github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4=
+github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -248,6 +365,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
 github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
+github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
+github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
 github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
 github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -273,10 +392,21 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMc
 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
+github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
 github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg=
 github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM=
 go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
@@ -292,26 +422,87 @@ go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZE
 go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
 go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
 golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -320,31 +511,73 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
 golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -368,46 +601,180 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
 golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
 golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20230724170836-66ad5b6ff146 h1:0yJBPCSj/Hy/vQsNSrYtRLuJSNKoDzDXMu1q1ePGdus=
 google.golang.org/genproto v0.0.0-20230724170836-66ad5b6ff146/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108=
 google.golang.org/genproto/googleapis/api v0.0.0-20230724170836-66ad5b6ff146 h1:P60zJj7Yxq1VhZIxpRO7A5lDFyy07D6Dqa+HCixuFBM=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20230724170836-66ad5b6ff146 h1:0PjALPu/U/4OVXKQM2P8b8NJGd4V+xbZSP+uuBJpGm0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -423,3 +790,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/main.go b/main.go
index 8948a918d..9a733aa63 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,14 @@
 package main
 
 import (
+	"os"
+
 	"github.com/CircleCI-Public/circleci-cli/cmd"
 )
 
 func main() {
 	// See cmd/root.go for Execute()
-	cmd.Execute()
+	if err := cmd.Execute(); err != nil {
+		os.Exit(-1)
+	}
 }
diff --git a/prompt/prompt.go b/prompt/prompt.go
index cb2bd52dd..c9613386c 100644
--- a/prompt/prompt.go
+++ b/prompt/prompt.go
@@ -36,3 +36,14 @@ func AskUserToConfirm(message string) bool {
 	result, err := input.RunPrompt()
 	return err == nil && result
 }
+
+func AskUserToConfirmWithDefault(message string, defaultValue bool) bool {
+	def := confirmation.No
+	if defaultValue {
+		def = confirmation.Yes
+	}
+
+	input := confirmation.New(message, def)
+	result, err := input.RunPrompt()
+	return err == nil && result
+}
diff --git a/settings/settings.go b/settings/settings.go
index 36928fac5..7a9e7f0e4 100644
--- a/settings/settings.go
+++ b/settings/settings.go
@@ -17,25 +17,37 @@ import (
 	yaml "gopkg.in/yaml.v3"
 
 	"github.com/CircleCI-Public/circleci-cli/data"
+	"github.com/spf13/afero"
+)
+
+var (
+	FS afero.Afero = afero.Afero{
+		Fs: afero.NewOsFs(),
+	}
 )
 
 // Config is used to represent the current state of a CLI instance.
 type Config struct {
-	Host            string            `yaml:"host"`
-	DlHost          string            `yaml:"-"`
-	Endpoint        string            `yaml:"endpoint"`
-	Token           string            `yaml:"token"`
-	RestEndpoint    string            `yaml:"rest_endpoint"`
-	TLSCert         string            `yaml:"tls_cert"`
-	TLSInsecure     bool              `yaml:"tls_insecure"`
-	HTTPClient      *http.Client      `yaml:"-"`
-	Data            *data.DataBag     `yaml:"-"`
-	Debug           bool              `yaml:"-"`
-	Address         string            `yaml:"-"`
-	FileUsed        string            `yaml:"-"`
-	GitHubAPI       string            `yaml:"-"`
-	SkipUpdateCheck bool              `yaml:"-"`
-	OrbPublishing   OrbPublishingInfo `yaml:"orb_publishing"`
+	Host            string        `yaml:"host"`
+	DlHost          string        `yaml:"-"`
+	Endpoint        string        `yaml:"endpoint"`
+	Token           string        `yaml:"token"`
+	RestEndpoint    string        `yaml:"rest_endpoint"`
+	TLSCert         string        `yaml:"tls_cert"`
+	TLSInsecure     bool          `yaml:"tls_insecure"`
+	HTTPClient      *http.Client  `yaml:"-"`
+	Data            *data.DataBag `yaml:"-"`
+	Debug           bool          `yaml:"-"`
+	Address         string        `yaml:"-"`
+	FileUsed        string        `yaml:"-"`
+	GitHubAPI       string        `yaml:"-"`
+	SkipUpdateCheck bool          `yaml:"-"`
+	// Parameter used to disable telemetry from tests
+	IsTelemetryDisabled bool `yaml:"-"`
+	// If this value is defined, the telemetry will write all its events a file
+	// The value of this field is the path where the telemetry will be written
+	MockTelemetry string            `yaml:"-"`
+	OrbPublishing OrbPublishingInfo `yaml:"orb_publishing"`
 }
 
 type OrbPublishingInfo struct {
@@ -50,6 +62,14 @@ type UpdateCheck struct {
 	FileUsed        string    `yaml:"-"`
 }
 
+// TelemetrySettings is used to represent telemetry related settings
+type TelemetrySettings struct {
+	IsEnabled         bool   `yaml:"is_enabled"`
+	HasAnsweredPrompt bool   `yaml:"has_answered_prompt"`
+	UniqueID          string `yaml:"unique_id"`
+	UserID            string `yaml:"user_id"`
+}
+
 // Load will read the update check settings from the user's disk and then deserialize it into the current instance.
 func (upd *UpdateCheck) Load() error {
 	path := filepath.Join(SettingsPath(), updateCheckFilename())
@@ -80,6 +100,35 @@ func (upd *UpdateCheck) WriteToDisk() error {
 	return err
 }
 
+// Load will read the telemetry settings from the user's disk and then deserialize it into the current instance.
+func (tel *TelemetrySettings) Load() error {
+	path := filepath.Join(SettingsPath(), telemetryFilename())
+
+	if err := ensureSettingsFileExists(path); err != nil {
+		return err
+	}
+
+	content, err := FS.ReadFile(path) // #nosec
+	if err != nil {
+		return err
+	}
+
+	err = yaml.Unmarshal(content, &tel)
+	return err
+}
+
+// WriteToDisk will write the telemetry settings to disk by serializing the YAML
+func (tel *TelemetrySettings) Write() error {
+	enc, err := yaml.Marshal(&tel)
+	if err != nil {
+		return err
+	}
+
+	path := filepath.Join(SettingsPath(), telemetryFilename())
+	err = FS.WriteFile(path, enc, 0600)
+	return err
+}
+
 // Load will read the config from the user's disk and then evaluate possible configuration from the environment.
 func (cfg *Config) Load() error {
 	if err := cfg.LoadFromDisk(); err != nil {
@@ -161,6 +210,11 @@ func configFilename() string {
 	return "cli.yml"
 }
 
+// telemetryFilename returns the name of the cli telemetry file
+func telemetryFilename() string {
+	return "telemetry.yml"
+}
+
 // settingsPath returns the path of the CLI settings directory
 func SettingsPath() string {
 	// TODO: Make this configurable
diff --git a/telemetry/context.go b/telemetry/context.go
new file mode 100644
index 000000000..4e245f101
--- /dev/null
+++ b/telemetry/context.go
@@ -0,0 +1,16 @@
+package telemetry
+
+import "context"
+
+type contextKey string
+
+const telemetryClientContextKey contextKey = "telemetryClientContextKey"
+
+func NewContext(ctx context.Context, client Client) context.Context {
+	return context.WithValue(ctx, telemetryClientContextKey, client)
+}
+
+func FromContext(ctx context.Context) (Client, bool) {
+	client, ok := ctx.Value(telemetryClientContextKey).(Client)
+	return client, ok
+}
diff --git a/telemetry/events.go b/telemetry/events.go
new file mode 100644
index 000000000..652b111ac
--- /dev/null
+++ b/telemetry/events.go
@@ -0,0 +1,149 @@
+package telemetry
+
+import "fmt"
+
+// This file contains all the telemetry event constructors
+// All the events are referenced in the following file:
+// https://circleci.atlassian.net/wiki/spaces/DE/pages/6760694125/CLI+segment+event+tracking
+// If you want to add an event, first make sure it appears in this file
+
+type CommandInfo struct {
+	Name      string
+	LocalArgs map[string]string
+}
+
+func createEventFromCommandInfo(name string, cmdInfo CommandInfo) Event {
+	properties := map[string]interface{}{}
+	for key, value := range cmdInfo.LocalArgs {
+		properties[fmt.Sprintf("cmd.flag.%s", key)] = value
+	}
+	properties["has_been_executed"] = false
+
+	return Event{
+		Object:     fmt.Sprintf("cli-%s", name),
+		Action:     cmdInfo.Name,
+		Properties: properties,
+	}
+}
+
+func errorToProperties(err error) map[string]interface{} {
+	properties := map[string]interface{}{
+		"has_been_executed": true,
+	}
+
+	if err != nil {
+		properties["error"] = err.Error()
+	}
+	return properties
+}
+
+func CreateSetupEvent(isServerCustomer bool) Event {
+	return Event{
+		Object: "cli-setup",
+		Action: "setup",
+		Properties: map[string]interface{}{
+			"is_server_customer": isServerCustomer,
+			"has_been_executed":  true,
+		},
+	}
+}
+
+func CreateVersionEvent(version string) Event {
+	return Event{
+		Object: "cli-version",
+		Action: "version",
+		Properties: map[string]interface{}{
+			"version":           version,
+			"has_been_executed": true,
+		},
+	}
+}
+
+func CreateUpdateEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("update", cmdInfo)
+}
+
+func CreateDiagnosticEvent(err error) Event {
+	return Event{
+		Object: "cli-diagnostic", Action: "diagnostic", Properties: errorToProperties(err),
+	}
+}
+
+func CreateFollowEvent(err error) Event {
+	return Event{
+		Object: "cli-follow", Action: "follow", Properties: errorToProperties(err),
+	}
+}
+
+func CreateOpenEvent(err error) Event {
+	return Event{Object: "cli-open", Action: "open", Properties: errorToProperties(err)}
+}
+
+func CreateCompletionCommand(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("completion", cmdInfo)
+}
+
+func CreateConfigEvent(cmdInfo CommandInfo, err error) Event {
+	event := createEventFromCommandInfo("config", cmdInfo)
+	if err != nil {
+		event.Properties["error"] = err.Error()
+		event.Properties["has_been_executed"] = true
+	}
+	return event
+}
+
+func CreateLocalExecuteEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("local", cmdInfo)
+}
+
+func CreateNamespaceEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("namespace", cmdInfo)
+}
+
+func CreateOrbEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("orb", cmdInfo)
+}
+
+func CreatePolicyEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("policy", cmdInfo)
+}
+
+func CreateRunnerInstanceEvent(cmdInfo CommandInfo, err error) Event {
+	event := createEventFromCommandInfo("runner-instance", cmdInfo)
+	if err != nil {
+		event.Properties["error"] = err.Error()
+		event.Properties["has_been_executed"] = true
+	}
+	return event
+}
+
+func CreateRunnerResourceClassEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("runner-resource-class", cmdInfo)
+}
+
+func CreateRunnerTokenEvent(cmdInfo CommandInfo) Event {
+	return createEventFromCommandInfo("runner-token", cmdInfo)
+}
+
+func CreateInfoEvent(cmdInfo CommandInfo, err error) Event {
+	event := createEventFromCommandInfo("info", cmdInfo)
+	if err != nil {
+		event.Properties["error"] = err.Error()
+		event.Properties["has_been_executed"] = true
+	}
+	return event
+}
+
+func CreateChangeTelemetryStatusEvent(action string, origin string, err error) Event {
+	event := Event{
+		Object: "cli-telemetry",
+		Action: action,
+		Properties: map[string]interface{}{
+			"origin": origin,
+		},
+	}
+	if err != nil {
+		event.Properties["error"] = err.Error()
+	}
+	return event
+}
diff --git a/telemetry/telemetry.go b/telemetry/telemetry.go
new file mode 100644
index 000000000..928e59dd8
--- /dev/null
+++ b/telemetry/telemetry.go
@@ -0,0 +1,197 @@
+package telemetry
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/segmentio/analytics-go"
+)
+
+var (
+	// Overwrite this function for tests
+	CreateActiveTelemetry = newSegmentClient
+
+	SegmentEndpoint = "http://localhost:33457"
+	segmentKey      = "AbgkrgN4cbRhAVEwlzMkHbwvrXnxHh35"
+)
+
+type Approval string
+
+const (
+	Enabled  Approval = "enabled"
+	Disabled Approval = "disabled"
+	NoStdin  Approval = "disabled_default"
+)
+
+type Client interface {
+	io.Closer
+	// Send a telemetry event. This method is not to be called directly. Use config.Track instead
+	Track(event Event) error
+}
+
+// A segment event to be sent to the telemetry
+// Important: this is not meant to be constructed directly apart in tests
+// If you want to create a new event, add its constructor in ./events.go
+type Event struct {
+	Object     string                 `json:"object"`
+	Action     string                 `json:"action"`
+	Properties map[string]interface{} `json:"properties"`
+}
+
+type User struct {
+	UniqueID     string
+	UserID       string
+	IsSelfHosted bool
+	OS           string
+	Version      string
+	TeamName     string
+}
+
+// Create a telemetry client to be used to send telemetry events
+func CreateClient(user User, enabled bool) Client {
+	if !enabled {
+		return nullClient{}
+	}
+
+	return CreateActiveTelemetry(user)
+}
+
+// Sends the user's approval event
+func SendTelemetryApproval(user User, approval Approval) error {
+	client := CreateActiveTelemetry(user)
+	defer client.Close()
+
+	return client.Track(CreateChangeTelemetryStatusEvent(string(approval), "first-time-prompt", nil))
+}
+
+// Null client
+// Used when telemetry is disabled
+
+func CreateNullClient() Client {
+	return nullClient{}
+}
+
+type nullClient struct{}
+
+func (cli nullClient) Close() error { return nil }
+
+func (cli nullClient) Track(_ Event) error { return nil }
+
+// Segment client
+// Used when telemetry is enabled
+
+// Nil segment logger
+type nilSegmentEmptyLogger struct{}
+
+func (nilSegmentEmptyLogger) Logf(format string, args ...interface{})   {}
+func (nilSegmentEmptyLogger) Errorf(format string, args ...interface{}) {}
+
+type segmentClient struct {
+	analyticsClient analytics.Client
+	user            User
+}
+
+func newSegmentClient(user User) Client {
+	cli, err := analytics.NewWithConfig(segmentKey, analytics.Config{
+		Endpoint: SegmentEndpoint,
+		Logger:   nilSegmentEmptyLogger{},
+	})
+
+	if err != nil {
+		return CreateNullClient()
+	}
+
+	if len(user.UniqueID) == 0 {
+		return CreateNullClient()
+	}
+
+	err = cli.Enqueue(
+		analytics.Identify{
+			UserId: user.UniqueID,
+			Traits: analytics.NewTraits().Set("os", user.OS),
+		},
+	)
+	if err != nil {
+		fmt.Printf("Error while identifying with telemetry: %s\n", err)
+	}
+
+	return &segmentClient{cli, user}
+}
+
+func (segment *segmentClient) Track(event Event) error {
+	if event.Properties == nil {
+		event.Properties = make(map[string]interface{})
+	}
+	if event.Action != "" {
+		event.Properties["action"] = event.Action
+	}
+
+	if segment.user.UniqueID != "" {
+		event.Properties["anonymous_id"] = segment.user.UniqueID
+	}
+
+	if segment.user.UserID != "" {
+		event.Properties["cci_user_id"] = segment.user.UserID
+	}
+
+	event.Properties["is_self_hosted"] = segment.user.IsSelfHosted
+
+	if segment.user.OS != "" {
+		event.Properties["os"] = segment.user.OS
+	}
+
+	if segment.user.Version != "" {
+		event.Properties["cli_version"] = segment.user.Version
+	}
+
+	if segment.user.TeamName != "" {
+		event.Properties["team_name"] = segment.user.TeamName
+	}
+
+	return segment.analyticsClient.Enqueue(analytics.Track{
+		UserId:     segment.user.UniqueID,
+		Event:      event.Object,
+		Properties: event.Properties,
+	})
+}
+
+func (segment *segmentClient) Close() error {
+	return segment.analyticsClient.Close()
+}
+
+// File telemetry
+// Used for E2E tests
+
+type fileTelemetry struct {
+	filePath string
+	events   []Event
+}
+
+func CreateFileTelemetry(filePath string) Client {
+	return &fileTelemetry{filePath, make([]Event, 0)}
+}
+
+func (cli *fileTelemetry) Track(event Event) error {
+	cli.events = append(cli.events, event)
+	return nil
+}
+
+func (cli *fileTelemetry) Close() error {
+	file, err := os.OpenFile(cli.filePath, os.O_CREATE|os.O_WRONLY, 0666)
+	if err != nil {
+		return err
+	}
+
+	content, err := json.Marshal(&cli.events)
+	if err != nil {
+		return err
+	}
+
+	if _, err = file.Write(content); err != nil {
+		return err
+	}
+
+	return file.Close()
+}
diff --git a/telemetry/utils.go b/telemetry/utils.go
new file mode 100644
index 000000000..2201887ad
--- /dev/null
+++ b/telemetry/utils.go
@@ -0,0 +1,31 @@
+package telemetry
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
+)
+
+// Utility function used when creating telemetry events.
+// It takes a cobra Command and creates a telemetry.CommandInfo of it
+// If getParent is true, puts both the command's args in `LocalArgs` and the parent's args
+// Else only put the command's args
+// Note: child flags overwrite parent flags with same name
+func GetCommandInformation(cmd *cobra.Command, getParent bool) CommandInfo {
+	localArgs := map[string]string{}
+
+	parent := cmd.Parent()
+	if getParent && parent != nil {
+		parent.LocalFlags().VisitAll(func(flag *pflag.Flag) {
+			localArgs[flag.Name] = flag.Value.String()
+		})
+	}
+
+	cmd.LocalFlags().VisitAll(func(flag *pflag.Flag) {
+		localArgs[flag.Name] = flag.Value.String()
+	})
+
+	return CommandInfo{
+		Name:      cmd.Name(),
+		LocalArgs: localArgs,
+	}
+}