diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index a8214acc..c89b452b 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -1,7 +1,11 @@ use cliclack::spinner; use console::style; +use goose::agents::extension::ToolInfo; +use goose::agents::extension_manager::get_parameter_names; +use goose::agents::Agent; use goose::agents::{extension::Envs, ExtensionConfig}; use goose::config::extensions::name_to_key; +use goose::config::permission::PermissionLevel; use goose::config::{ Config, ConfigError, ExperimentManager, ExtensionConfigManager, ExtensionEntry, PermissionManager, @@ -186,7 +190,7 @@ pub async fn handle_configure() -> Result<(), Box> { .item( "settings", "Goose Settings", - "Set the Goose Mode, Tool Output, Experiment and more", + "Set the Goose Mode, Tool Output, Tool Permissions, Experiment and more", ) .interact()?; @@ -194,7 +198,7 @@ pub async fn handle_configure() -> Result<(), Box> { "toggle" => toggle_extensions_dialog(), "add" => configure_extensions_dialog(), "remove" => remove_extension_dialog(), - "settings" => configure_settings_dialog(), + "settings" => configure_settings_dialog().await.and(Ok(())), "providers" => configure_provider_dialog().await.and(Ok(())), _ => unreachable!(), } @@ -753,9 +757,14 @@ pub fn remove_extension_dialog() -> Result<(), Box> { Ok(()) } -pub fn configure_settings_dialog() -> Result<(), Box> { +pub async fn configure_settings_dialog() -> Result<(), Box> { let setting_type = cliclack::select("What setting would you like to configure?") .item("goose_mode", "Goose Mode", "Configure Goose mode") + .item( + "tool_permission", + "Tool Permission", + "Set permission for individual tool of enabled extensions", + ) .item( "tool_output", "Tool Output", @@ -772,6 +781,9 @@ pub fn configure_settings_dialog() -> Result<(), Box> { "goose_mode" => { configure_goose_mode_dialog()?; } + "tool_permission" => { + configure_tool_permissions_dialog().await.and(Ok(()))?; + } "tool_output" => { configure_tool_output_dialog()?; } @@ -907,3 +919,146 @@ pub fn toggle_experiments_dialog() -> Result<(), Box> { cliclack::outro("Experiments settings updated successfully")?; Ok(()) } + +pub async fn configure_tool_permissions_dialog() -> Result<(), Box> { + // Load config and get provider/model + let config = Config::global(); + + let provider_name: String = config + .get_param("GOOSE_PROVIDER") + .expect("No provider configured. Please set model provider first"); + + let model: String = config + .get_param("GOOSE_MODEL") + .expect("No model configured. Please set model first"); + let model_config = goose::model::ModelConfig::new(model.clone()); + let provider = + goose::providers::create(&provider_name, model_config).expect("Failed to create provider"); + + // Create the agent + let mut agent = Agent::new(provider); + for extension in ExtensionConfigManager::get_all().expect("should load extensions") { + if extension.enabled { + let config = extension.config.clone(); + agent + .add_extension(config.clone()) + .await + .unwrap_or_else(|_| { + println!( + "{} Failed to check extension: {}", + style("Error").red().italic(), + config.name() + ); + }); + } + } + + let mut permission_manager = PermissionManager::default(); + // Fetch the list of tools grouped by extension + let tools: Vec = agent + .list_tools() + .await + .into_iter() + .map(|tool| { + ToolInfo::new( + &tool.name, + &tool.description, + get_parameter_names(&tool), + permission_manager.get_user_permission(&tool.name), + ) + }) + .collect(); + + let mut tools_by_extension: HashMap> = HashMap::new(); + + for tool in tools { + if let Some((extension_name, _tool_name)) = tool.name.split_once("__") { + tools_by_extension + .entry(extension_name.to_string()) + .or_default() + .push(tool); + } + } + + // Ask the user to choose an extension + let extension_name = cliclack::select("Choose an extension to configure tools") + .items( + &tools_by_extension + .keys() + .map(|ext| (ext.clone(), ext.clone(), "")) + .collect::>(), + ) + .interact()?; + + // Fetch tools for the selected extension + let selected_tools = tools_by_extension.get(&extension_name).unwrap(); + + let tool_name = cliclack::select("Choose a tool to update permission") + .items( + &selected_tools + .iter() + .map(|tool| (tool.name.clone(), tool.name.clone(), &tool.description)) + .collect::>(), + ) + .interact()?; + + // Find the selected tool + let tool = selected_tools + .iter() + .find(|tool| tool.name == tool_name) + .unwrap(); + + // Display tool description and current permission level + let current_permission = match tool.permission { + Some(PermissionLevel::AlwaysAllow) => "Always Allow", + Some(PermissionLevel::AskBefore) => "Ask Before", + Some(PermissionLevel::NeverAllow) => "Never Allow", + None => "Not Set", + }; + + // Allow user to set the permission level + let permission = cliclack::select(format!( + "Set permission level for tool {}, current permission level: {}", + tool.name, current_permission + )) + .item( + "always_allow", + "Always Allow", + "Allow this tool to execute without asking", + ) + .item( + "ask_before", + "Ask Before", + "Prompt before executing this tool", + ) + .item( + "never_allow", + "Never Allow", + "Prevent this tool from executing", + ) + .interact()?; + + let permission_label = match permission { + "always_allow" => "Always Allow", + "ask_before" => "Ask Before", + "never_allow" => "Never Allow", + _ => unreachable!(), + }; + + // Update the permission level in the configuration + let new_permission = match permission { + "always_allow" => PermissionLevel::AlwaysAllow, + "ask_before" => PermissionLevel::AskBefore, + "never_allow" => PermissionLevel::NeverAllow, + _ => unreachable!(), + }; + + permission_manager.update_user_permission(&tool.name, new_permission); + + cliclack::outro(format!( + "Updated permission level for tool {} to {}.", + tool.name, permission_label + ))?; + + Ok(()) +} diff --git a/crates/goose/src/agents/extension.rs b/crates/goose/src/agents/extension.rs index 8e5748bd..1375b776 100644 --- a/crates/goose/src/agents/extension.rs +++ b/crates/goose/src/agents/extension.rs @@ -297,10 +297,10 @@ impl ExtensionInfo { /// Information about the tool used for building prompts #[derive(Clone, Debug, Serialize, ToSchema)] pub struct ToolInfo { - name: String, - description: String, - parameters: Vec, - permission: Option, + pub name: String, + pub description: String, + pub parameters: Vec, + pub permission: Option, } impl ToolInfo {