Ask for permission in the CLI (#6475)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2026-01-13 16:29:54 -05:00
committed by GitHub
parent 1c0895e6b4
commit 7af88d21a9
3 changed files with 135 additions and 12 deletions
+14 -1
View File
@@ -2,6 +2,7 @@ use anyhow::Result;
use clap::{Args, CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell as ClapShell};
use goose::config::{Config, ExtensionConfig};
use goose::posthog::get_telemetry_choice;
use goose_mcp::mcp_server_runner::{serve, McpCommand};
use goose_mcp::{
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
@@ -9,7 +10,7 @@ use goose_mcp::{
use crate::commands::acp::run_acp_agent;
use crate::commands::bench::agent_generator;
use crate::commands::configure::handle_configure;
use crate::commands::configure::{configure_telemetry_consent_dialog, handle_configure};
use crate::commands::info::handle_info;
use crate::commands::project::{handle_project_default, handle_projects_interactive};
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
@@ -1039,6 +1040,10 @@ async fn handle_interactive_session(
session_opts: SessionOptions,
extension_opts: ExtensionOptions,
) -> Result<()> {
if get_telemetry_choice().is_none() {
configure_telemetry_consent_dialog()?;
}
let session_start = std::time::Instant::now();
let session_type = if resume { "resumed" } else { "new" };
@@ -1247,6 +1252,10 @@ async fn handle_run_command(
output_opts: OutputOptions,
model_opts: ModelOptions,
) -> Result<()> {
if run_behavior.interactive && get_telemetry_choice().is_none() {
configure_telemetry_consent_dialog()?;
}
let parsed = parse_run_input(&input_opts, output_opts.quiet)?;
let Some((input_config, recipe_info)) = parsed else {
@@ -1397,6 +1406,10 @@ async fn handle_default_session() -> Result<()> {
return handle_configure().await;
}
if get_telemetry_choice().is_none() {
configure_telemetry_consent_dialog()?;
}
let session_id = get_or_create_session_id(None, false, false).await?;
let mut session = build_session(SessionBuilderConfig {
+104 -2
View File
@@ -19,6 +19,7 @@ use goose::config::{
};
use goose::conversation::message::Message;
use goose::model::ModelConfig;
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
use goose::providers::provider_test::test_provider_configuration;
use goose::providers::{create, providers, retry_operation, RetryConfig};
use goose::session::{SessionManager, SessionType};
@@ -39,16 +40,78 @@ pub async fn handle_configure() -> anyhow::Result<()> {
}
}
async fn handle_first_time_setup(config: &Config) -> anyhow::Result<()> {
pub fn configure_telemetry_consent_dialog() -> anyhow::Result<bool> {
let config = Config::global();
println!();
println!("{}", style("Help improve goose").bold());
println!();
println!(
"{}",
style("Welcome to goose! Let's get you set up with a provider.").dim()
style("Would you like to help improve goose by sharing anonymous usage data?").dim()
);
println!(
"{}",
style("This helps us understand how goose is used and identify areas for improvement.")
.dim()
);
println!();
println!("{}", style("What we collect:").dim());
println!(
"{}",
style(" • Operating system, version, and architecture").dim()
);
println!("{}", style(" • goose version and install method").dim());
println!("{}", style(" • Provider and model used").dim());
println!(
"{}",
style(" • Extensions and tool usage counts (names only)").dim()
);
println!(
"{}",
style(" • Session metrics (duration, interaction count, token usage)").dim()
);
println!(
"{}",
style(" • Error types (e.g., \"rate_limit\", \"auth\" - no details)").dim()
);
println!();
println!(
"{}",
style("We never collect your conversations, code, tool arguments, error messages,").dim()
);
println!(
"{}",
style("or any personal data. You can change this anytime with 'goose configure'.").dim()
);
println!();
let enabled = cliclack::confirm("Share anonymous usage data to help improve goose?")
.initial_value(true)
.interact()?;
config.set_param(TELEMETRY_ENABLED_KEY, enabled)?;
if enabled {
let _ = cliclack::log::success("Thank you for helping improve goose!");
} else {
let _ = cliclack::log::info("Telemetry disabled. You can enable it anytime in settings.");
}
Ok(enabled)
}
async fn handle_first_time_setup(config: &Config) -> anyhow::Result<()> {
println!();
println!("{}", style("Welcome to goose! Let's get you set up.").dim());
println!(
"{}",
style(" you can rerun this command later to update your configuration").dim()
);
println!();
configure_telemetry_consent_dialog()?;
println!();
cliclack::intro(style(" goose-configure ").on_cyan().black())?;
@@ -1031,6 +1094,11 @@ pub fn remove_extension_dialog() -> anyhow::Result<()> {
pub async fn configure_settings_dialog() -> anyhow::Result<()> {
let setting_type = cliclack::select("What setting would you like to configure?")
.item("goose_mode", "goose mode", "Configure goose mode")
.item(
"telemetry",
"Telemetry",
"Enable or disable anonymous usage data collection",
)
.item(
"tool_permission",
"Tool Permission",
@@ -1069,6 +1137,9 @@ pub async fn configure_settings_dialog() -> anyhow::Result<()> {
"goose_mode" => {
configure_goose_mode_dialog()?;
}
"telemetry" => {
configure_telemetry_dialog()?;
}
"tool_permission" => {
configure_tool_permissions_dialog().await.and(Ok(()))?;
// No need to print config file path since it's already handled.
@@ -1140,6 +1211,37 @@ pub fn configure_goose_mode_dialog() -> anyhow::Result<()> {
Ok(())
}
pub fn configure_telemetry_dialog() -> anyhow::Result<()> {
let config = Config::global();
if std::env::var("GOOSE_TELEMETRY_OFF").is_ok() {
let _ = cliclack::log::info("Notice: GOOSE_TELEMETRY_OFF environment variable is set and will override the configuration here.");
}
let current_choice = get_telemetry_choice();
let current_status = match current_choice {
Some(true) => "Enabled",
Some(false) => "Disabled",
None => "Not set",
};
let _ = cliclack::log::info(format!("Current telemetry status: {}", current_status));
let enabled = cliclack::confirm("Share anonymous usage data to help improve goose?")
.initial_value(current_choice.unwrap_or(true))
.interact()?;
config.set_param(TELEMETRY_ENABLED_KEY, enabled)?;
if enabled {
cliclack::outro("Telemetry enabled - thank you for helping improve goose!")?;
} else {
cliclack::outro("Telemetry disabled")?;
}
Ok(())
}
pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
let config = Config::global();
+17 -9
View File
@@ -24,22 +24,30 @@ static TELEMETRY_DISABLED_BY_ENV: Lazy<AtomicBool> = Lazy::new(|| {
.into()
});
/// Check if the user has made a telemetry choice.
///
/// Returns Some(true) if telemetry is enabled, Some(false) if disabled,
/// or None if the user hasn't made a choice yet.
pub fn get_telemetry_choice() -> Option<bool> {
// If disabled by env var, treat as explicit choice to disable
if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
return Some(false);
}
let config = Config::global();
config.get_param::<bool>(TELEMETRY_ENABLED_KEY).ok()
}
/// Check if telemetry is enabled.
///
/// Returns false if:
/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
/// - GOOSE_TELEMETRY_ENABLED config value is set to false
/// - User has not made a telemetry choice yet (opt-in required)
///
/// Returns true otherwise (telemetry is opt-out, enabled by default)
/// Returns true only if the user has explicitly opted in.
pub fn is_telemetry_enabled() -> bool {
if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
return false;
}
let config = Config::global();
config
.get_param::<bool>(TELEMETRY_ENABLED_KEY)
.unwrap_or(true)
get_telemetry_choice().unwrap_or(false)
}
// ============================================================================