Ask for permission in the CLI (#6475)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -2,6 +2,7 @@ use anyhow::Result;
|
|||||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||||
use clap_complete::{generate, Shell as ClapShell};
|
use clap_complete::{generate, Shell as ClapShell};
|
||||||
use goose::config::{Config, ExtensionConfig};
|
use goose::config::{Config, ExtensionConfig};
|
||||||
|
use goose::posthog::get_telemetry_choice;
|
||||||
use goose_mcp::mcp_server_runner::{serve, McpCommand};
|
use goose_mcp::mcp_server_runner::{serve, McpCommand};
|
||||||
use goose_mcp::{
|
use goose_mcp::{
|
||||||
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
AutoVisualiserRouter, ComputerControllerServer, DeveloperServer, MemoryServer, TutorialServer,
|
||||||
@@ -9,7 +10,7 @@ use goose_mcp::{
|
|||||||
|
|
||||||
use crate::commands::acp::run_acp_agent;
|
use crate::commands::acp::run_acp_agent;
|
||||||
use crate::commands::bench::agent_generator;
|
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::info::handle_info;
|
||||||
use crate::commands::project::{handle_project_default, handle_projects_interactive};
|
use crate::commands::project::{handle_project_default, handle_projects_interactive};
|
||||||
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
|
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
|
||||||
@@ -1039,6 +1040,10 @@ async fn handle_interactive_session(
|
|||||||
session_opts: SessionOptions,
|
session_opts: SessionOptions,
|
||||||
extension_opts: ExtensionOptions,
|
extension_opts: ExtensionOptions,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
if get_telemetry_choice().is_none() {
|
||||||
|
configure_telemetry_consent_dialog()?;
|
||||||
|
}
|
||||||
|
|
||||||
let session_start = std::time::Instant::now();
|
let session_start = std::time::Instant::now();
|
||||||
let session_type = if resume { "resumed" } else { "new" };
|
let session_type = if resume { "resumed" } else { "new" };
|
||||||
|
|
||||||
@@ -1247,6 +1252,10 @@ async fn handle_run_command(
|
|||||||
output_opts: OutputOptions,
|
output_opts: OutputOptions,
|
||||||
model_opts: ModelOptions,
|
model_opts: ModelOptions,
|
||||||
) -> Result<()> {
|
) -> 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 parsed = parse_run_input(&input_opts, output_opts.quiet)?;
|
||||||
|
|
||||||
let Some((input_config, recipe_info)) = parsed else {
|
let Some((input_config, recipe_info)) = parsed else {
|
||||||
@@ -1397,6 +1406,10 @@ async fn handle_default_session() -> Result<()> {
|
|||||||
return handle_configure().await;
|
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 session_id = get_or_create_session_id(None, false, false).await?;
|
||||||
|
|
||||||
let mut session = build_session(SessionBuilderConfig {
|
let mut session = build_session(SessionBuilderConfig {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use goose::config::{
|
|||||||
};
|
};
|
||||||
use goose::conversation::message::Message;
|
use goose::conversation::message::Message;
|
||||||
use goose::model::ModelConfig;
|
use goose::model::ModelConfig;
|
||||||
|
use goose::posthog::{get_telemetry_choice, TELEMETRY_ENABLED_KEY};
|
||||||
use goose::providers::provider_test::test_provider_configuration;
|
use goose::providers::provider_test::test_provider_configuration;
|
||||||
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
use goose::providers::{create, providers, retry_operation, RetryConfig};
|
||||||
use goose::session::{SessionManager, SessionType};
|
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!();
|
||||||
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!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
style(" you can rerun this command later to update your configuration").dim()
|
style(" you can rerun this command later to update your configuration").dim()
|
||||||
);
|
);
|
||||||
|
println!();
|
||||||
|
|
||||||
|
configure_telemetry_consent_dialog()?;
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
cliclack::intro(style(" goose-configure ").on_cyan().black())?;
|
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<()> {
|
pub async fn configure_settings_dialog() -> anyhow::Result<()> {
|
||||||
let setting_type = cliclack::select("What setting would you like to configure?")
|
let setting_type = cliclack::select("What setting would you like to configure?")
|
||||||
.item("goose_mode", "goose mode", "Configure goose mode")
|
.item("goose_mode", "goose mode", "Configure goose mode")
|
||||||
|
.item(
|
||||||
|
"telemetry",
|
||||||
|
"Telemetry",
|
||||||
|
"Enable or disable anonymous usage data collection",
|
||||||
|
)
|
||||||
.item(
|
.item(
|
||||||
"tool_permission",
|
"tool_permission",
|
||||||
"Tool Permission",
|
"Tool Permission",
|
||||||
@@ -1069,6 +1137,9 @@ pub async fn configure_settings_dialog() -> anyhow::Result<()> {
|
|||||||
"goose_mode" => {
|
"goose_mode" => {
|
||||||
configure_goose_mode_dialog()?;
|
configure_goose_mode_dialog()?;
|
||||||
}
|
}
|
||||||
|
"telemetry" => {
|
||||||
|
configure_telemetry_dialog()?;
|
||||||
|
}
|
||||||
"tool_permission" => {
|
"tool_permission" => {
|
||||||
configure_tool_permissions_dialog().await.and(Ok(()))?;
|
configure_tool_permissions_dialog().await.and(Ok(()))?;
|
||||||
// No need to print config file path since it's already handled.
|
// 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(())
|
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<()> {
|
pub fn configure_tool_output_dialog() -> anyhow::Result<()> {
|
||||||
let config = Config::global();
|
let config = Config::global();
|
||||||
|
|
||||||
|
|||||||
@@ -24,22 +24,30 @@ static TELEMETRY_DISABLED_BY_ENV: Lazy<AtomicBool> = Lazy::new(|| {
|
|||||||
.into()
|
.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.
|
/// Check if telemetry is enabled.
|
||||||
///
|
///
|
||||||
/// Returns false if:
|
/// Returns false if:
|
||||||
/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
|
/// - GOOSE_TELEMETRY_OFF environment variable is set to "1" or "true"
|
||||||
/// - GOOSE_TELEMETRY_ENABLED config value is set to false
|
/// - 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 {
|
pub fn is_telemetry_enabled() -> bool {
|
||||||
if TELEMETRY_DISABLED_BY_ENV.load(Ordering::Relaxed) {
|
get_telemetry_choice().unwrap_or(false)
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = Config::global();
|
|
||||||
config
|
|
||||||
.get_param::<bool>(TELEMETRY_ENABLED_KEY)
|
|
||||||
.unwrap_or(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user