Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command>

# Per-invocation flag
chunk --no-telemetry <command>
```

The standard `NO_ANALYTICS=1`, `DO_NOT_TRACK=1`, and `CI=true` environment variables are also respected.

## Platform Support

| Platform | Status |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 15 additions & 4 deletions internal/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmd
import (
"fmt"
"os"
"strconv"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -96,7 +97,7 @@ func newConfigSetCmd() *cobra.Command {
return &cobra.Command{
Use: "set <key> <value>",
Short: "Set a config value",
Long: "Set a config value. Use 'chunk auth set <provider>' to store credentials with validation.\n\nUser keys: model\nProject keys: orgID, validation.sidecarImage",
Long: "Set a config value. Use 'chunk auth set <provider>' 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)
Expand Down Expand Up @@ -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),
}
}
Expand All @@ -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)))
Expand Down
2 changes: 2 additions & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -101,6 +102,7 @@ Configuration:
rootCmd.AddCommand(newHookCmd())
rootCmd.AddCommand(newUpgradeCmd())
rootCmd.AddCommand(newCommandsCmd())
rootCmd.AddCommand(newTelemetryCmd())

recordTelemetryForSubcommands(rootCmd, telem)

Expand Down
76 changes: 76 additions & 0 deletions internal/cmd/telemetry.go
Original file line number Diff line number Diff line change
@@ -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
},
}
}
3 changes: 2 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down