chore: refactor configure_extensions_dialog to reduce line count (#6277)
This commit is contained in:
@@ -690,6 +690,251 @@ pub fn toggle_extensions_dialog() -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_extension_timeout() -> anyhow::Result<u64> {
|
||||||
|
Ok(
|
||||||
|
cliclack::input("Please set the timeout for this tool (in secs):")
|
||||||
|
.placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string())
|
||||||
|
.validate(|input: &String| match input.parse::<u64>() {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(_) => Err("Please enter a valid timeout"),
|
||||||
|
})
|
||||||
|
.interact()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_extension_description() -> anyhow::Result<String> {
|
||||||
|
Ok(cliclack::input("Enter a description for this extension:")
|
||||||
|
.placeholder("Description")
|
||||||
|
.validate(|input: &String| {
|
||||||
|
if input.trim().is_empty() {
|
||||||
|
Err("Please enter a valid description")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.interact()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_extension_name(placeholder: &str) -> anyhow::Result<String> {
|
||||||
|
let extensions = get_all_extension_names();
|
||||||
|
Ok(
|
||||||
|
cliclack::input("What would you like to call this extension?")
|
||||||
|
.placeholder(placeholder)
|
||||||
|
.validate(move |input: &String| {
|
||||||
|
if input.is_empty() {
|
||||||
|
Err("Please enter a name")
|
||||||
|
} else if extensions.contains(input) {
|
||||||
|
Err("An extension with this name already exists")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.interact()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_env_vars() -> anyhow::Result<(HashMap<String, String>, Vec<String>)> {
|
||||||
|
let mut envs = HashMap::new();
|
||||||
|
let mut env_keys = Vec::new();
|
||||||
|
let config = Config::global();
|
||||||
|
|
||||||
|
if !cliclack::confirm("Would you like to add environment variables?").interact()? {
|
||||||
|
return Ok((envs, env_keys));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let key: String = cliclack::input("Environment variable name:")
|
||||||
|
.placeholder("API_KEY")
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let value: String = cliclack::password("Environment variable value:")
|
||||||
|
.mask('▪')
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
match config.set_secret(&key, &value) {
|
||||||
|
Ok(_) => env_keys.push(key),
|
||||||
|
Err(_) => {
|
||||||
|
envs.insert(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cliclack::confirm("Add another environment variable?").interact()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((envs, env_keys))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_headers() -> anyhow::Result<HashMap<String, String>> {
|
||||||
|
let mut headers = HashMap::new();
|
||||||
|
|
||||||
|
if !cliclack::confirm("Would you like to add custom headers?").interact()? {
|
||||||
|
return Ok(headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let key: String = cliclack::input("Header name:")
|
||||||
|
.placeholder("Authorization")
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let value: String = cliclack::input("Header value:")
|
||||||
|
.placeholder("Bearer token123")
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
headers.insert(key, value);
|
||||||
|
|
||||||
|
if !cliclack::confirm("Add another header?").interact()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_builtin_extension() -> anyhow::Result<()> {
|
||||||
|
let extensions = vec![
|
||||||
|
(
|
||||||
|
"autovisualiser",
|
||||||
|
"Auto Visualiser",
|
||||||
|
"Data visualisation and UI generation tools",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"computercontroller",
|
||||||
|
"Computer Controller",
|
||||||
|
"controls for webscraping, file caching, and automations",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"developer",
|
||||||
|
"Developer Tools",
|
||||||
|
"Code editing and shell access",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"memory",
|
||||||
|
"Memory",
|
||||||
|
"Tools to save and retrieve durable memories",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tutorial",
|
||||||
|
"Tutorial",
|
||||||
|
"Access interactive tutorials and guides",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut select = cliclack::select("Which built-in extension would you like to enable?");
|
||||||
|
for (id, name, desc) in &extensions {
|
||||||
|
select = select.item(id, name, desc);
|
||||||
|
}
|
||||||
|
let extension = select.interact()?.to_string();
|
||||||
|
let timeout = prompt_extension_timeout()?;
|
||||||
|
|
||||||
|
let (display_name, description) = extensions
|
||||||
|
.iter()
|
||||||
|
.find(|(id, _, _)| id == &extension)
|
||||||
|
.map(|(_, name, desc)| (name.to_string(), desc.to_string()))
|
||||||
|
.unwrap_or_else(|| (extension.clone(), extension.clone()));
|
||||||
|
|
||||||
|
set_extension(ExtensionEntry {
|
||||||
|
enabled: true,
|
||||||
|
config: ExtensionConfig::Builtin {
|
||||||
|
name: extension.clone(),
|
||||||
|
display_name: Some(display_name),
|
||||||
|
timeout: Some(timeout),
|
||||||
|
bundled: Some(true),
|
||||||
|
description,
|
||||||
|
available_tools: Vec::new(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cliclack::outro(format!("Enabled {} extension", style(extension).green()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_stdio_extension() -> anyhow::Result<()> {
|
||||||
|
let name = prompt_extension_name("my-extension")?;
|
||||||
|
|
||||||
|
let command_str: String = cliclack::input("What command should be run?")
|
||||||
|
.placeholder("npx -y @block/gdrive")
|
||||||
|
.validate(|input: &String| {
|
||||||
|
if input.is_empty() {
|
||||||
|
Err("Please enter a command")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let timeout = prompt_extension_timeout()?;
|
||||||
|
|
||||||
|
let mut parts = command_str.split_whitespace();
|
||||||
|
let cmd = parts.next().unwrap_or("").to_string();
|
||||||
|
let args: Vec<String> = parts.map(String::from).collect();
|
||||||
|
|
||||||
|
let description = prompt_extension_description()?;
|
||||||
|
let (envs, env_keys) = collect_env_vars()?;
|
||||||
|
|
||||||
|
set_extension(ExtensionEntry {
|
||||||
|
enabled: true,
|
||||||
|
config: ExtensionConfig::Stdio {
|
||||||
|
name: name.clone(),
|
||||||
|
cmd,
|
||||||
|
args,
|
||||||
|
envs: Envs::new(envs),
|
||||||
|
env_keys,
|
||||||
|
description,
|
||||||
|
timeout: Some(timeout),
|
||||||
|
bundled: None,
|
||||||
|
available_tools: Vec::new(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cliclack::outro(format!("Added {} extension", style(name).green()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_streamable_http_extension() -> anyhow::Result<()> {
|
||||||
|
let name = prompt_extension_name("my-remote-extension")?;
|
||||||
|
|
||||||
|
let uri: String = cliclack::input("What is the Streaming HTTP endpoint URI?")
|
||||||
|
.placeholder("http://localhost:8000/messages")
|
||||||
|
.validate(|input: &String| {
|
||||||
|
if input.is_empty() {
|
||||||
|
Err("Please enter a URI")
|
||||||
|
} else if !(input.starts_with("http://") || input.starts_with("https://")) {
|
||||||
|
Err("URI should start with http:// or https://")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let timeout = prompt_extension_timeout()?;
|
||||||
|
let description = prompt_extension_description()?;
|
||||||
|
let headers = collect_headers()?;
|
||||||
|
|
||||||
|
// Original behavior: no env var collection for Streamable HTTP
|
||||||
|
let envs = HashMap::new();
|
||||||
|
let env_keys = Vec::new();
|
||||||
|
|
||||||
|
set_extension(ExtensionEntry {
|
||||||
|
enabled: true,
|
||||||
|
config: ExtensionConfig::StreamableHttp {
|
||||||
|
name: name.clone(),
|
||||||
|
uri,
|
||||||
|
envs: Envs::new(envs),
|
||||||
|
env_keys,
|
||||||
|
headers,
|
||||||
|
description,
|
||||||
|
timeout: Some(timeout),
|
||||||
|
bundled: None,
|
||||||
|
available_tools: Vec::new(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cliclack::outro(format!("Added {} extension", style(name).green()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
||||||
let extension_type = cliclack::select("What type of extension would you like to add?")
|
let extension_type = cliclack::select("What type of extension would you like to add?")
|
||||||
.item(
|
.item(
|
||||||
@@ -710,297 +955,13 @@ pub fn configure_extensions_dialog() -> anyhow::Result<()> {
|
|||||||
.interact()?;
|
.interact()?;
|
||||||
|
|
||||||
match extension_type {
|
match extension_type {
|
||||||
// TODO we'll want a place to collect all these options, maybe just an enum in goose-mcp
|
"built-in" => configure_builtin_extension()?,
|
||||||
"built-in" => {
|
"stdio" => configure_stdio_extension()?,
|
||||||
let extensions = vec![
|
"streamable_http" => configure_streamable_http_extension()?,
|
||||||
(
|
|
||||||
"autovisualiser",
|
|
||||||
"Auto Visualiser",
|
|
||||||
"Data visualisation and UI generation tools",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"computercontroller",
|
|
||||||
"Computer Controller",
|
|
||||||
"controls for webscraping, file caching, and automations",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"developer",
|
|
||||||
"Developer Tools",
|
|
||||||
"Code editing and shell access",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"memory",
|
|
||||||
"Memory",
|
|
||||||
"Tools to save and retrieve durable memories",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"tutorial",
|
|
||||||
"Tutorial",
|
|
||||||
"Access interactive tutorials and guides",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut select = cliclack::select("Which built-in extension would you like to enable?");
|
|
||||||
for (id, name, desc) in &extensions {
|
|
||||||
select = select.item(id, name, desc);
|
|
||||||
}
|
|
||||||
let extension = select.interact()?.to_string();
|
|
||||||
|
|
||||||
let timeout: u64 = cliclack::input("Please set the timeout for this tool (in secs):")
|
|
||||||
.placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string())
|
|
||||||
.validate(|input: &String| match input.parse::<u64>() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err("Please enter a valid timeout"),
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let (display_name, description) = extensions
|
|
||||||
.iter()
|
|
||||||
.find(|(id, _, _)| id == &extension)
|
|
||||||
.map(|(_, name, desc)| (name.to_string(), desc.to_string()))
|
|
||||||
.unwrap_or_else(|| (extension.clone(), extension.clone()));
|
|
||||||
|
|
||||||
set_extension(ExtensionEntry {
|
|
||||||
enabled: true,
|
|
||||||
config: ExtensionConfig::Builtin {
|
|
||||||
name: extension.clone(),
|
|
||||||
display_name: Some(display_name),
|
|
||||||
timeout: Some(timeout),
|
|
||||||
bundled: Some(true),
|
|
||||||
description,
|
|
||||||
available_tools: Vec::new(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
cliclack::outro(format!("Enabled {} extension", style(extension).green()))?;
|
|
||||||
}
|
|
||||||
"stdio" => {
|
|
||||||
let extensions = get_all_extension_names();
|
|
||||||
let name: String = cliclack::input("What would you like to call this extension?")
|
|
||||||
.placeholder("my-extension")
|
|
||||||
.validate(move |input: &String| {
|
|
||||||
if input.is_empty() {
|
|
||||||
Err("Please enter a name")
|
|
||||||
} else if extensions.contains(input) {
|
|
||||||
Err("An extension with this name already exists")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let command_str: String = cliclack::input("What command should be run?")
|
|
||||||
.placeholder("npx -y @block/gdrive")
|
|
||||||
.validate(|input: &String| {
|
|
||||||
if input.is_empty() {
|
|
||||||
Err("Please enter a command")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let timeout: u64 = cliclack::input("Please set the timeout for this tool (in secs):")
|
|
||||||
.placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string())
|
|
||||||
.validate(|input: &String| match input.parse::<u64>() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err("Please enter a valid timeout"),
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
// Split the command string into command and args
|
|
||||||
// TODO: find a way to expose this to the frontend so we dont need to re-write code
|
|
||||||
let mut parts = command_str.split_whitespace();
|
|
||||||
let cmd = parts.next().unwrap_or("").to_string();
|
|
||||||
let args: Vec<String> = parts.map(String::from).collect();
|
|
||||||
|
|
||||||
let description = cliclack::input("Enter a description for this extension:")
|
|
||||||
.placeholder("Description")
|
|
||||||
.validate(|input: &String| match input.parse::<String>() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err("Please enter a valid description"),
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let add_env =
|
|
||||||
cliclack::confirm("Would you like to add environment variables?").interact()?;
|
|
||||||
|
|
||||||
let mut envs = HashMap::new();
|
|
||||||
let mut env_keys = Vec::new();
|
|
||||||
let config = Config::global();
|
|
||||||
|
|
||||||
if add_env {
|
|
||||||
loop {
|
|
||||||
let key: String = cliclack::input("Environment variable name:")
|
|
||||||
.placeholder("API_KEY")
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let value: String = cliclack::password("Environment variable value:")
|
|
||||||
.mask('▪')
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
// Try to store in keychain
|
|
||||||
let keychain_key = key.to_string();
|
|
||||||
match config.set_secret(&keychain_key, &value) {
|
|
||||||
Ok(_) => {
|
|
||||||
// Successfully stored in keychain, add to env_keys
|
|
||||||
env_keys.push(keychain_key);
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// Failed to store in keychain, store directly in envs
|
|
||||||
envs.insert(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cliclack::confirm("Add another environment variable?").interact()? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_extension(ExtensionEntry {
|
|
||||||
enabled: true,
|
|
||||||
config: ExtensionConfig::Stdio {
|
|
||||||
name: name.clone(),
|
|
||||||
cmd,
|
|
||||||
args,
|
|
||||||
envs: Envs::new(envs),
|
|
||||||
env_keys,
|
|
||||||
description,
|
|
||||||
timeout: Some(timeout),
|
|
||||||
bundled: None,
|
|
||||||
available_tools: Vec::new(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
cliclack::outro(format!("Added {} extension", style(name).green()))?;
|
|
||||||
}
|
|
||||||
"streamable_http" => {
|
|
||||||
let extensions = get_all_extension_names();
|
|
||||||
let name: String = cliclack::input("What would you like to call this extension?")
|
|
||||||
.placeholder("my-remote-extension")
|
|
||||||
.validate(move |input: &String| {
|
|
||||||
if input.is_empty() {
|
|
||||||
Err("Please enter a name")
|
|
||||||
} else if extensions.contains(input) {
|
|
||||||
Err("An extension with this name already exists")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let uri: String = cliclack::input("What is the Streamable HTTP endpoint URI?")
|
|
||||||
.placeholder("http://localhost:8000/messages")
|
|
||||||
.validate(|input: &String| {
|
|
||||||
if input.is_empty() {
|
|
||||||
Err("Please enter a URI")
|
|
||||||
} else if !(input.starts_with("http://") || input.starts_with("https://")) {
|
|
||||||
Err("URI should start with http:// or https://")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let timeout: u64 = cliclack::input("Please set the timeout for this tool (in secs):")
|
|
||||||
.placeholder(&goose::config::DEFAULT_EXTENSION_TIMEOUT.to_string())
|
|
||||||
.validate(|input: &String| match input.parse::<u64>() {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_) => Err("Please enter a valid timeout"),
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let description = cliclack::input("Enter a description for this extension:")
|
|
||||||
.placeholder("Description")
|
|
||||||
.validate(|input: &String| {
|
|
||||||
if input.trim().is_empty() {
|
|
||||||
Err("Please enter a valid description")
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let add_headers =
|
|
||||||
cliclack::confirm("Would you like to add custom headers?").interact()?;
|
|
||||||
|
|
||||||
let mut headers = HashMap::new();
|
|
||||||
if add_headers {
|
|
||||||
loop {
|
|
||||||
let key: String = cliclack::input("Header name:")
|
|
||||||
.placeholder("Authorization")
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let value: String = cliclack::input("Header value:")
|
|
||||||
.placeholder("Bearer token123")
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
headers.insert(key, value);
|
|
||||||
|
|
||||||
if !cliclack::confirm("Add another header?").interact()? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let add_env = false; // No env prompt for Streamable HTTP
|
|
||||||
|
|
||||||
let mut envs = HashMap::new();
|
|
||||||
let mut env_keys = Vec::new();
|
|
||||||
let config = Config::global();
|
|
||||||
|
|
||||||
if add_env {
|
|
||||||
loop {
|
|
||||||
let key: String = cliclack::input("Environment variable name:")
|
|
||||||
.placeholder("API_KEY")
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
let value: String = cliclack::password("Environment variable value:")
|
|
||||||
.mask('▪')
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
// Try to store in keychain
|
|
||||||
let keychain_key = key.to_string();
|
|
||||||
match config.set_secret(&keychain_key, &Value::String(value.clone())) {
|
|
||||||
Ok(_) => {
|
|
||||||
// Successfully stored in keychain, add to env_keys
|
|
||||||
env_keys.push(keychain_key);
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
// Failed to store in keychain, store directly in envs
|
|
||||||
envs.insert(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cliclack::confirm("Add another environment variable?").interact()? {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_extension(ExtensionEntry {
|
|
||||||
enabled: true,
|
|
||||||
config: ExtensionConfig::StreamableHttp {
|
|
||||||
name: name.clone(),
|
|
||||||
uri,
|
|
||||||
envs: Envs::new(envs),
|
|
||||||
env_keys,
|
|
||||||
headers,
|
|
||||||
description,
|
|
||||||
timeout: Some(timeout),
|
|
||||||
bundled: None,
|
|
||||||
available_tools: Vec::new(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
cliclack::outro(format!("Added {} extension", style(name).green()))?;
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
print_config_file_saved()?;
|
print_config_file_saved()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user