From 1f9eb88137319a2285a60546feb864c374a6dadf Mon Sep 17 00:00:00 2001 From: Eric Garcia Date: Sat, 24 Jan 2026 15:47:25 -0500 Subject: [PATCH] feat(cli): Add blue agent command for Goose integration - Add `blue agent` command that launches Goose with Blue MCP extension - Detects Goose installation and provides install instructions if missing - Supports --model flag and additional Goose arguments - Uses exec() on Unix to replace process for clean signal handling Completes RFC 0005: Local LLM Integration (15/15 tasks) Co-Authored-By: Claude Opus 4.5 --- apps/blue-cli/src/main.rs | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/apps/blue-cli/src/main.rs b/apps/blue-cli/src/main.rs index 649b773..50df04e 100644 --- a/apps/blue-cli/src/main.rs +++ b/apps/blue-cli/src/main.rs @@ -75,6 +75,17 @@ enum Commands { #[command(subcommand)] command: SessionCommands, }, + + /// Launch Goose AI agent with Blue extension + Agent { + /// Model to use (default: claude-sonnet-4-20250514) + #[arg(long, short)] + model: Option, + + /// Additional Goose arguments + #[arg(trailing_var_arg = true)] + args: Vec, + }, } #[derive(Subcommand)] @@ -391,6 +402,9 @@ async fn main() -> Result<()> { Some(Commands::Migrate { from }) => { println!("Coming home from {}.", from); } + Some(Commands::Agent { model, args }) => { + handle_agent_command(model, args).await?; + } } Ok(()) @@ -1117,3 +1131,71 @@ async fn handle_session_command(command: SessionCommands) -> Result<()> { Ok(()) } + +async fn handle_agent_command(model: Option, extra_args: Vec) -> Result<()> { + use std::process::Command; + + // Check if Goose is installed + let goose_check = Command::new("goose") + .arg("--version") + .output(); + + match goose_check { + Err(_) => { + println!("Goose not found. Install it first:"); + println!(" pipx install goose-ai"); + println!(" # or"); + println!(" brew install goose"); + println!("\nSee https://github.com/block/goose for more options."); + return Ok(()); + } + Ok(output) if !output.status.success() => { + println!("Goose check failed. Ensure it's properly installed."); + return Ok(()); + } + Ok(_) => {} + } + + // Get the path to the blue binary + let blue_binary = std::env::current_exe()?; + + // Build the extension command + let extension_cmd = format!("{} mcp", blue_binary.display()); + + println!("Starting Goose with Blue extension..."); + println!(" Extension: {}", extension_cmd); + + // Build goose command + let mut cmd = Command::new("goose"); + cmd.arg("session"); + cmd.arg("--with-extension").arg(&extension_cmd); + + // Add model if specified + if let Some(m) = model { + cmd.arg("--model").arg(m); + } + + // Add any extra arguments + for arg in extra_args { + cmd.arg(arg); + } + + // Execute goose, replacing current process + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + let err = cmd.exec(); + // exec() only returns if there was an error + anyhow::bail!("Failed to exec goose: {}", err); + } + + #[cfg(not(unix))] + { + // On non-Unix, spawn and wait + let status = cmd.status()?; + if !status.success() { + anyhow::bail!("Goose exited with status: {}", status); + } + Ok(()) + } +}