From ba9aa093c1b9230a59fb91b14d749a1abc39762b Mon Sep 17 00:00:00 2001 From: schurchleycci Date: Fri, 22 May 2026 15:27:54 -0400 Subject: [PATCH 1/2] Add chunk telemetry command and config set telemetry support Users can now opt out of telemetry persistently via: chunk telemetry disable (stores NoTelemetry=true in config) chunk config set telemetry false CHUNK_NO_TELEMETRY=1 (env var, already wired in core) Also adds README telemetry disclosure section and registers the telemetry subcommand in the root command tree. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 30 ++++++++++++++++ internal/cmd/config.go | 19 +++++++--- internal/cmd/root.go | 2 ++ internal/cmd/telemetry.go | 76 +++++++++++++++++++++++++++++++++++++++ internal/config/config.go | 3 +- 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 internal/cmd/telemetry.go diff --git a/README.md b/README.md index e9a00597..577ba6fd 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,36 @@ See [docs/CLI.md](docs/CLI.md) for the full command and flag reference. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the complete environment variable reference. +## Telemetry + +chunk collects anonymous usage statistics to help improve the tool. + +**What we collect:** +- Which commands and subcommands are used +- Whether commands succeed or fail (no error messages or details) +- chunk version, OS, and architecture + +**What we do NOT collect:** +- Command arguments or flag values +- File or directory names +- IP addresses or hostnames +- Any personally identifiable information + +**To disable telemetry:** + +```bash +# Permanently via the telemetry command +chunk telemetry disable + +# Environment variable (persists for the session or CI run) +CHUNK_NO_TELEMETRY=1 chunk + +# Per-invocation flag +chunk --no-telemetry +``` + +The standard `NO_ANALYTICS=1`, `DO_NOT_TRACK=1`, and `CI=true` environment variables are also respected. + ## Platform Support | Platform | Status | diff --git a/internal/cmd/config.go b/internal/cmd/config.go index 30cbee30..c1817d3f 100644 --- a/internal/cmd/config.go +++ b/internal/cmd/config.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "os" + "strconv" "github.com/spf13/cobra" @@ -96,7 +97,7 @@ func newConfigSetCmd() *cobra.Command { return &cobra.Command{ Use: "set ", Short: "Set a config value", - Long: "Set a config value. Use 'chunk auth set ' to store credentials with validation.\n\nUser keys: model\nProject keys: orgID, validation.sidecarImage", + Long: "Set a config value. Use 'chunk auth set ' to store credentials with validation.\n\nUser keys: model, telemetry\nProject keys: orgID, validation.sidecarImage", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { io := iostream.FromCmd(cmd) @@ -132,7 +133,7 @@ func newConfigSetCmd() *cobra.Command { if !config.ValidConfigKeys[key] { return &userError{ msg: fmt.Sprintf("Unknown config key: %q.", key), - detail: "Supported keys: model, orgID, validation.sidecarImage.", + detail: "Supported keys: model, telemetry, orgID, validation.sidecarImage.", errMsg: fmt.Sprintf("unknown config key %q", key), } } @@ -142,12 +143,22 @@ func newConfigSetCmd() *cobra.Command { return &userError{msg: msgCouldNotLoadConfig, suggestion: configFilePermHint, err: err} } - if key == "model" { + switch key { + case "model": cfg.Model = value + case "telemetry": + enabled, parseErr := strconv.ParseBool(value) + if parseErr != nil { + return &userError{ + msg: fmt.Sprintf("Invalid value for telemetry: %q. Use true or false.", value), + errMsg: fmt.Sprintf("invalid telemetry value %q", value), + } + } + cfg.NoTelemetry = !enabled } if err := config.Save(cfg); err != nil { - return &userError{msg: "Could not save configuration.", suggestion: configFilePermHint, err: err} + return &userError{msg: msgCouldNotSaveConfig, suggestion: configFilePermHint, err: err} } io.Printf("%s\n", ui.Success(fmt.Sprintf("Set %s to %s", key, value))) diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 149a1bbe..cad9012b 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -81,6 +81,7 @@ Telemetry: To disable telemetry: Set CHUNK_NO_TELEMETRY=1 in your environment + Run: chunk telemetry disable Pass --no-telemetry to disable for a single invocation Configuration: @@ -101,6 +102,7 @@ Configuration: rootCmd.AddCommand(newHookCmd()) rootCmd.AddCommand(newUpgradeCmd()) rootCmd.AddCommand(newCommandsCmd()) + rootCmd.AddCommand(newTelemetryCmd()) recordTelemetryForSubcommands(rootCmd, telem) diff --git a/internal/cmd/telemetry.go b/internal/cmd/telemetry.go new file mode 100644 index 00000000..adcfa8f1 --- /dev/null +++ b/internal/cmd/telemetry.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/CircleCI-Public/chunk-cli/internal/config" + "github.com/CircleCI-Public/chunk-cli/internal/iostream" + "github.com/CircleCI-Public/chunk-cli/internal/ui" +) + +func newTelemetryCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "telemetry", + Short: "Manage telemetry settings", + RunE: groupRunE, + } + + enableCmd := newTelemetryEnableCmd() + disableCmd := newTelemetryDisableCmd() + + disableTelemetry(enableCmd) + disableTelemetry(disableCmd) + + cmd.AddCommand(enableCmd) + cmd.AddCommand(disableCmd) + + return cmd +} + +func newTelemetryEnableCmd() *cobra.Command { + return &cobra.Command{ + Use: "enable", + Short: "Enable anonymous usage telemetry", + RunE: func(cmd *cobra.Command, _ []string) error { + io := iostream.FromCmd(cmd) + cfg, err := config.Load() + if err != nil { + return &userError{msg: msgCouldNotLoadConfig, suggestion: configFilePermHint, err: err} + } + cfg.NoTelemetry = false + if err := config.Save(cfg); err != nil { + return &userError{msg: msgCouldNotSaveConfig, suggestion: configFilePermHint, err: err} + } + io.Printf("%s\n", ui.Success("Telemetry enabled.")) + return nil + }, + } +} + +func newTelemetryDisableCmd() *cobra.Command { + return &cobra.Command{ + Use: "disable", + Short: "Disable anonymous usage telemetry", + Long: fmt.Sprintf(`Disable anonymous usage telemetry. + +You can also set %s=1 in your environment, or pass --no-telemetry +to disable telemetry for a single invocation. + +Run 'chunk telemetry enable' to re-enable.`, config.EnvChunkNoTelemetry), + RunE: func(cmd *cobra.Command, _ []string) error { + io := iostream.FromCmd(cmd) + cfg, err := config.Load() + if err != nil { + return &userError{msg: msgCouldNotLoadConfig, suggestion: configFilePermHint, err: err} + } + cfg.NoTelemetry = true + if err := config.Save(cfg); err != nil { + return &userError{msg: msgCouldNotSaveConfig, suggestion: configFilePermHint, err: err} + } + io.Printf("%s\n", ui.Success("Telemetry disabled.")) + return nil + }, + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 6b5721a7..c42fef50 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -308,7 +308,8 @@ func MaskKey(key string) string { // Credentials (anthropicAPIKey, circleCIToken) are intentionally excluded — // users should use "auth set" which validates before storing. var ValidConfigKeys = map[string]bool{ - "model": true, + "model": true, + "telemetry": true, } // ValidProjectConfigKeys are the keys accepted by "config set" that write to From c88f7934a94ce8e24ac59db7d37ca6ebb8fb73a1 Mon Sep 17 00:00:00 2001 From: schurchleycci Date: Fri, 22 May 2026 16:10:59 -0400 Subject: [PATCH 2/2] Promote spf13/pflag to direct dependency go mod tidy moves it from indirect to direct since root.go imports it directly. Co-Authored-By: Claude Sonnet 4.6 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4b8351c8..cbe78007 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/segmentio/analytics-go/v3 v3.3.0 github.com/sethvargo/go-envconfig v1.3.0 github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 github.com/zalando/go-keyring v0.2.8 golang.org/x/crypto v0.51.0 golang.org/x/term v0.43.0 @@ -224,7 +225,6 @@ require ( github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect