From b3d4f5697d4bd5cf7828c217eb485e3bc6e698f8 Mon Sep 17 00:00:00 2001 From: Douwe M Osinga Date: Fri, 31 Jul 2026 10:09:07 +0200 Subject: [PATCH 1/3] Remove CLI project support --- crates/goose-cli/src/cli.rs | 23 -- crates/goose-cli/src/commands/mod.rs | 1 - crates/goose-cli/src/commands/project.rs | 310 ------------------ crates/goose-cli/src/lib.rs | 1 - crates/goose-cli/src/project_tracker.rs | 142 -------- crates/goose-cli/src/session/mod.rs | 10 - .../docs/guides/goose-cli-commands.md | 26 -- .../guides/sessions/session-management.md | 11 - 8 files changed, 524 deletions(-) delete mode 100644 crates/goose-cli/src/commands/project.rs delete mode 100644 crates/goose-cli/src/project_tracker.rs diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index f999940ea20a..461029567a1b 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -17,7 +17,6 @@ use crate::commands::configure::configure_telemetry_consent_dialog; use crate::commands::configure::handle_configure; use crate::commands::info::handle_info; use crate::commands::plugin::{handle_plugin_install, handle_plugin_update}; -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::term::{ handle_term_info, handle_term_init, handle_term_log, handle_term_run, Shell, @@ -946,14 +945,6 @@ enum Command { extension_opts: ExtensionOptions, }, - /// Open the last project directory - #[command(about = "Open the last project directory", visible_alias = "p")] - Project {}, - - /// List recent project directories - #[command(about = "List recent project directories", visible_alias = "ps")] - Projects, - /// Execute commands from an instruction file #[command(about = "Execute commands from an instruction file or stdin")] Run { @@ -1349,8 +1340,6 @@ fn get_command_name(command: &Option) -> &'static str { Some(Command::Acp { .. }) => "acp", Some(Command::Serve { .. }) => "serve", Some(Command::Session { .. }) => "session", - Some(Command::Project {}) => "project", - Some(Command::Projects) => "projects", Some(Command::Run { .. }) => "run", Some(Command::Gateway { .. }) => "gateway", Some(Command::Schedule { .. }) => "schedule", @@ -2218,10 +2207,6 @@ pub async fn cli() -> anyhow::Result<()> { let cli = Cli::parse(); - if let Err(e) = crate::project_tracker::update_project_tracker(None, None) { - warn!("Warning: Failed to update project tracker: {}", e); - } - let command_name = get_command_name(&cli.command); tracing::info!( monotonic_counter.goose.cli_commands = 1, @@ -2293,14 +2278,6 @@ pub async fn cli() -> anyhow::Result<()> { ) .await } - Some(Command::Project {}) => { - handle_project_default()?; - Ok(()) - } - Some(Command::Projects) => { - handle_projects_interactive()?; - Ok(()) - } Some(Command::Run { input_opts, identifier, diff --git a/crates/goose-cli/src/commands/mod.rs b/crates/goose-cli/src/commands/mod.rs index d687e13e668d..f43e9057942f 100644 --- a/crates/goose-cli/src/commands/mod.rs +++ b/crates/goose-cli/src/commands/mod.rs @@ -3,7 +3,6 @@ pub mod doctor; pub mod gateway; pub mod info; pub mod plugin; -pub mod project; pub mod recipe; pub mod review; pub mod schedule; diff --git a/crates/goose-cli/src/commands/project.rs b/crates/goose-cli/src/commands/project.rs deleted file mode 100644 index 43fd67d60d79..000000000000 --- a/crates/goose-cli/src/commands/project.rs +++ /dev/null @@ -1,310 +0,0 @@ -use anyhow::Result; -use chrono::DateTime; -use cliclack::{self, intro, outro}; -use std::path::Path; - -use crate::project_tracker::ProjectTracker; -use goose::utils::safe_truncate; - -/// Format a DateTime for display -fn format_date(date: DateTime) -> String { - // Format: "2025-05-08 18:15:30" - date.format("%Y-%m-%d %H:%M:%S").to_string() -} - -/// Handle the default project command -/// -/// Offers options to resume the most recently accessed project -pub fn handle_project_default() -> Result<()> { - let goose_bin = std::env::current_exe() - .map(|p| p.to_string_lossy().into_owned()) - .unwrap_or_else(|_| "goose".to_string()); - let tracker = ProjectTracker::load()?; - let mut projects = tracker.list_projects(); - - if projects.is_empty() { - // If no projects exist, just start a new one in the current directory - println!("No previous projects found. Starting a new session in the current directory."); - let mut command = std::process::Command::new(&goose_bin); - command.arg("session"); - let status = command.status()?; - - if !status.success() { - println!("Failed to run goose. Exit code: {:?}", status.code()); - } - return Ok(()); - } - - // Sort projects by last_accessed (newest first) - projects.sort_by_key(|project| std::cmp::Reverse(project.last_accessed)); - - // Get the most recent project - let project = &projects[0]; - let project_dir = &project.path; - - // Check if the directory exists - if !Path::new(project_dir).exists() { - println!( - "Most recent project directory '{}' no longer exists.", - project_dir - ); - return Ok(()); - } - - // Format the path for display - let path = Path::new(project_dir); - let components: Vec<_> = path.components().collect(); - let len = components.len(); - let short_path = if len <= 2 { - project_dir.clone() - } else { - let mut path_str = String::new(); - path_str.push_str("..."); - for component in components.iter().skip(len - 2) { - path_str.push('/'); - path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); - } - path_str - }; - - // Ask the user what they want to do - let _ = intro("goose Project Manager"); - - let current_dir = std::env::current_dir()?; - let current_dir_display = current_dir.display(); - - let choice = cliclack::select("Choose an option:") - .item( - "resume", - format!("Resume project with session: {}", short_path), - "Continue with the previous session", - ) - .item( - "fresh", - format!("Resume project with fresh session: {}", short_path), - "Change to the project directory but start a new session", - ) - .item( - "new", - format!( - "Start new project in current directory: {}", - current_dir_display - ), - "Stay in the current directory and start a new session", - ) - .interact()?; - - match choice { - "resume" => { - let _ = outro(format!("Changing to directory: {}", project_dir)); - - // Get the session ID if available - let session_id = project.last_session_id.clone(); - - // Change to the project directory - std::env::set_current_dir(project_dir)?; - - // Build the command to run goose - let mut command = std::process::Command::new(&goose_bin); - command.arg("session"); - - if let Some(id) = session_id { - command.arg("--name").arg(&id).arg("--resume"); - println!("Resuming session: {}", id); - } - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run goose. Exit code: {:?}", status.code()); - } - } - "fresh" => { - let _ = outro(format!( - "Changing to directory: {} with a fresh session", - project_dir - )); - - // Change to the project directory - std::env::set_current_dir(project_dir)?; - - // Build the command to run goose with a fresh session - let mut command = std::process::Command::new(&goose_bin); - command.arg("session"); - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run goose. Exit code: {:?}", status.code()); - } - } - "new" => { - let _ = outro("Starting a new session in the current directory"); - - // Build the command to run goose - let mut command = std::process::Command::new(&goose_bin); - command.arg("session"); - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run goose. Exit code: {:?}", status.code()); - } - } - _ => { - let _ = outro("Operation canceled"); - } - } - - Ok(()) -} - -/// Handle the interactive projects command -/// -/// Shows a list of projects and lets the user select one to resume -pub fn handle_projects_interactive() -> Result<()> { - let goose_bin = std::env::current_exe() - .map(|p| p.to_string_lossy().into_owned()) - .unwrap_or_else(|_| "goose".to_string()); - let tracker = ProjectTracker::load()?; - let mut projects = tracker.list_projects(); - - if projects.is_empty() { - println!("No projects found."); - return Ok(()); - } - - // Sort projects by last_accessed (newest first) - projects.sort_by_key(|project| std::cmp::Reverse(project.last_accessed)); - - // Format project paths for display - let project_choices: Vec<(String, String)> = projects - .iter() - .enumerate() - .map(|(i, project)| { - let path = Path::new(&project.path); - let components: Vec<_> = path.components().collect(); - let len = components.len(); - let short_path = if len <= 2 { - project.path.clone() - } else { - let mut path_str = String::new(); - path_str.push_str("..."); - for component in components.iter().skip(len - 2) { - path_str.push('/'); - path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); - } - path_str - }; - - // Include last instruction if available (truncated) - let instruction_preview = - project - .last_instruction - .as_ref() - .map_or(String::new(), |instr| { - let truncated = safe_truncate(instr, 40); - format!(" [{}]", truncated) - }); - - let formatted_date = format_date(project.last_accessed); - ( - format!("{}", i + 1), // Value to return - format!("{} ({}){}", short_path, formatted_date, instruction_preview), // Display text with instruction - ) - }) - .collect(); - - // Let the user select a project - let _ = intro("goose Project Manager"); - let mut select = cliclack::select("Select a project:"); - - // Add each project as an option - for (value, display) in &project_choices { - select = select.item(value, display, ""); - } - - // Add a cancel option - let cancel_value = String::from("cancel"); - select = select.item(&cancel_value, "Cancel", "Don't resume any project"); - - let selected = select.interact()?; - - if selected == "cancel" { - let _ = outro("Project selection canceled."); - return Ok(()); - } - - // Parse the selected index - let index = selected.parse::().unwrap_or(0); - if index == 0 || index > projects.len() { - let _ = outro("Invalid selection."); - return Ok(()); - } - - // Get the selected project - let project = &projects[index - 1]; - let project_dir = &project.path; - - // Check if the directory exists - if !Path::new(project_dir).exists() { - let _ = outro(format!( - "Project directory '{}' no longer exists.", - project_dir - )); - return Ok(()); - } - - // Ask if the user wants to resume the session or start a new one - let session_id = project.last_session_id.clone(); - let has_previous_session = session_id.is_some(); - - // Change to the project directory first - std::env::set_current_dir(project_dir)?; - let _ = outro(format!("Changed to directory: {}", project_dir)); - - // Only ask about resuming if there's a previous session - let resume_session = if has_previous_session { - let session_choice = cliclack::select("What would you like to do?") - .item( - "resume", - "Resume previous session", - "Continue with the previous session", - ) - .item( - "new", - "Start new session", - "Start a fresh session in this project directory", - ) - .interact()?; - - session_choice == "resume" - } else { - false - }; - - // Build the command to run goose - let mut command = std::process::Command::new(&goose_bin); - command.arg("session"); - - if resume_session { - if let Some(id) = session_id { - command.arg("--name").arg(&id).arg("--resume"); - println!("Resuming session: {}", id); - } - } else { - println!("Starting new session"); - } - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run goose. Exit code: {:?}", status.code()); - } - - Ok(()) -} diff --git a/crates/goose-cli/src/lib.rs b/crates/goose-cli/src/lib.rs index 285144c46826..d6c810e48827 100644 --- a/crates/goose-cli/src/lib.rs +++ b/crates/goose-cli/src/lib.rs @@ -9,7 +9,6 @@ compile_error!("Features `rustls-tls` and `native-tls` are mutually exclusive"); pub mod cli; pub mod commands; pub mod logging; -pub mod project_tracker; pub mod recipes; pub mod scenario_tests; pub mod session; diff --git a/crates/goose-cli/src/project_tracker.rs b/crates/goose-cli/src/project_tracker.rs deleted file mode 100644 index 0cafe7291a5d..000000000000 --- a/crates/goose-cli/src/project_tracker.rs +++ /dev/null @@ -1,142 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; -use goose::config::paths::Paths; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::path::{Path, PathBuf}; - -/// Structure to track project information -#[derive(Debug, Serialize, Deserialize)] -pub struct ProjectInfo { - /// The absolute path to the project directory - pub path: String, - /// Last time the project was accessed - pub last_accessed: DateTime, - /// Last instruction sent to goose (if available) - pub last_instruction: Option, - /// Last session ID associated with this project - pub last_session_id: Option, -} - -/// Structure to hold all tracked projects -#[derive(Debug, Serialize, Deserialize)] -pub struct ProjectTracker { - projects: HashMap, -} - -/// Project information with path as a separate field for easier access -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ProjectInfoDisplay { - /// The absolute path to the project directory - pub path: String, - /// Last time the project was accessed - pub last_accessed: DateTime, - /// Last instruction sent to goose (if available) - pub last_instruction: Option, - /// Last session ID associated with this project - pub last_session_id: Option, -} - -impl ProjectTracker { - /// Get the path to the projects.json file - fn get_projects_file() -> Result { - let projects_file = Paths::in_data_dir("projects.json"); - if let Some(parent) = projects_file.parent() { - if !parent.exists() { - fs::create_dir_all(parent)?; - } - } - - Ok(projects_file) - } - - /// Load the project tracker from the projects.json file - pub fn load() -> Result { - let projects_file = Self::get_projects_file()?; - - if projects_file.exists() { - let file_content = fs::read_to_string(&projects_file)?; - let tracker: ProjectTracker = serde_json::from_str(&file_content) - .context("Failed to parse projects.json file")?; - Ok(tracker) - } else { - // If the file doesn't exist, create a new empty tracker - Ok(ProjectTracker { - projects: HashMap::new(), - }) - } - } - - /// Save the project tracker to the projects.json file - pub fn save(&self) -> Result<()> { - let projects_file = Self::get_projects_file()?; - let json = serde_json::to_string_pretty(self)?; - fs::write(projects_file, json)?; - Ok(()) - } - - /// Update project information for the current directory - /// - /// # Arguments - /// * `project_dir` - The project directory to update - /// * `instruction` - Optional instruction that was sent to goose - /// * `session_id` - Optional session ID associated with this project - pub fn update_project( - &mut self, - project_dir: &Path, - instruction: Option<&str>, - session_id: Option<&str>, - ) -> Result<()> { - let dir_str = project_dir.to_string_lossy().to_string(); - - // Create or update the project entry - let project_info = self.projects.entry(dir_str.clone()).or_insert(ProjectInfo { - path: dir_str, - last_accessed: Utc::now(), - last_instruction: None, - last_session_id: None, - }); - - // Update the last accessed time - project_info.last_accessed = Utc::now(); - - // Update the last instruction if provided - if let Some(instr) = instruction { - project_info.last_instruction = Some(instr.to_string()); - } - - // Update the session ID if provided - if let Some(id) = session_id { - project_info.last_session_id = Some(id.to_string()); - } - - self.save() - } - - /// List all tracked projects - /// - /// Returns a vector of ProjectInfoDisplay objects - pub fn list_projects(&self) -> Vec { - self.projects - .values() - .map(|info| ProjectInfoDisplay { - path: info.path.clone(), - last_accessed: info.last_accessed, - last_instruction: info.last_instruction.clone(), - last_session_id: info.last_session_id.clone(), - }) - .collect() - } -} - -/// Update the project tracker with the current directory and optional instruction -/// -/// # Arguments -/// * `instruction` - Optional instruction that was sent to goose -/// * `session_id` - Optional session ID associated with this project -pub fn update_project_tracker(instruction: Option<&str>, session_id: Option<&str>) -> Result<()> { - let current_dir = std::env::current_dir()?; - let mut tracker = ProjectTracker::load()?; - tracker.update_project(¤t_dir, instruction, session_id) -} diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 1c76c2f0fee1..6cf6be2868bd 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -737,16 +737,6 @@ impl CliSession { history.save(editor); self.push_message(Message::user().with_text(content)); - if let Err(e) = crate::project_tracker::update_project_tracker( - Some(content), - Some(&self.session_id), - ) { - eprintln!( - "Warning: Failed to update project tracker with instruction: {}", - e - ); - } - let _provider = self.agent.provider().await?; println!(); diff --git a/documentation/docs/guides/goose-cli-commands.md b/documentation/docs/guides/goose-cli-commands.md index e7484317964e..553eb7b3b054 100644 --- a/documentation/docs/guides/goose-cli-commands.md +++ b/documentation/docs/guides/goose-cli-commands.md @@ -728,32 +728,6 @@ goose serve --with-builtin developer,memory --- -### Project Management - -#### project -Start working on your last project or create a new one. - -**Alias**: `p` - -**Usage:** -```bash -goose project -``` - ---- - -#### projects -Choose one of your projects to start working on. - -**Alias**: `ps` - -**Usage:** -```bash -goose projects -``` - ---- - ### Terminal Integration #### term diff --git a/documentation/docs/guides/sessions/session-management.md b/documentation/docs/guides/sessions/session-management.md index 9c86db6dc84c..af8fae24e11c 100644 --- a/documentation/docs/guides/sessions/session-management.md +++ b/documentation/docs/guides/sessions/session-management.md @@ -322,17 +322,6 @@ Sessions created in goose Desktop can be resumed in the CLI and vice versa. All While you can resume sessions, we recommend creating new sessions for new tasks to reduce the chance of [doom spiraling](/docs/troubleshooting/known-issues#stuck-in-a-loop-or-unresponsive). ::: -### Resume Project-Based Sessions - - - - Project-based sessions are only available through the CLI. - - - You can use the [`project`](/docs/guides/goose-cli-commands#project) and [`projects`](/docs/guides/goose-cli-commands#projects) commands to start or resume sessions from a project, which is a tracked working directory with session metadata. - - - ## Duplicate Sessions Create a complete copy of any session to reuse configurations, experiment with variations, or preserve important work. From 5cc681f57b95d99879d5cdadfdfee2cf962960a6 Mon Sep 17 00:00:00 2001 From: Douwe M Osinga Date: Fri, 31 Jul 2026 10:27:31 +0200 Subject: [PATCH 2/3] Update CLI docs automation for project removal --- .../cli-command-tracking/recipes/update-cli-commands.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/documentation/automation/cli-command-tracking/recipes/update-cli-commands.yaml b/documentation/automation/cli-command-tracking/recipes/update-cli-commands.yaml index 045523845f4d..d3bd0f613bbc 100644 --- a/documentation/automation/cli-command-tracking/recipes/update-cli-commands.yaml +++ b/documentation/automation/cli-command-tracking/recipes/update-cli-commands.yaml @@ -65,7 +65,6 @@ instructions: | - Core Commands (configure, info, version, update) - Session Management (session and subcommands) - Task Execution (run, bench, recipe, schedule, mcp, acp) - - Project Management (project, projects) - Interface (web) - Interactive Session Features (slash commands, themes, etc.) From 196d04a273ad55192ed4b77af7ae932727b742c9 Mon Sep 17 00:00:00 2001 From: Douwe M Osinga Date: Fri, 31 Jul 2026 16:32:47 +0200 Subject: [PATCH 3/3] Remove unused tracing import --- crates/goose-cli/src/cli.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 461029567a1b..ca8b3dd165d5 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -37,8 +37,6 @@ use goose::session::session_manager::SessionType; use goose::session::SessionManager; use std::io::Read; use std::path::PathBuf; -use tracing::warn; - const GOOSE_SERVER_SECRET_KEY_ENV: &str = "GOOSE_SERVER__SECRET_KEY"; fn generate_serve_secret_key() -> String {