chore: Used common function to list recipes in local machine (#4974)

This commit is contained in:
Lifei Zhou
2025-10-06 13:16:50 +11:00
committed by GitHub
parent bcbfef07a7
commit 0df1e0704b
11 changed files with 235 additions and 275 deletions
+1 -1
View File
@@ -1006,7 +1006,7 @@ pub async fn cli() -> Result<()> {
.unwrap_or(&recipe_name);
let recipe_version =
crate::recipes::search_recipe::retrieve_recipe_file(&recipe_name)
crate::recipes::search_recipe::load_recipe_file(&recipe_name)
.ok()
.and_then(|rf| {
goose::recipe::template_recipe::parse_recipe_content(
@@ -5,7 +5,7 @@ use goose::recipe::SubRecipe;
use crate::recipes::print_recipe::print_recipe_info;
use crate::recipes::recipe::load_recipe;
use crate::recipes::search_recipe::retrieve_recipe_file;
use crate::recipes::search_recipe::load_recipe_file;
use crate::{
cli::{InputConfig, RecipeInfo},
session::SessionSettings,
@@ -24,7 +24,7 @@ pub fn extract_recipe_info_from_cli(
let mut all_sub_recipes = recipe.sub_recipes.clone().unwrap_or_default();
if !additional_sub_recipes.is_empty() {
for sub_recipe_name in additional_sub_recipes {
match retrieve_recipe_file(&sub_recipe_name) {
match load_recipe_file(&sub_recipe_name) {
Ok(recipe_file) => {
let name = extract_recipe_name(&sub_recipe_name);
let recipe_file_path = recipe_file.file_path;
@@ -1,9 +1,9 @@
use anyhow::{anyhow, Result};
use console::style;
use goose::recipe::template_recipe::parse_recipe_content;
use goose::recipe::RECIPE_FILE_EXTENSIONS;
use serde::{Deserialize, Serialize};
use crate::recipes::recipe::RECIPE_FILE_EXTENSIONS;
use goose::recipe::read_recipe_file_content::RecipeFile;
use std::env;
use std::fs;
+3 -5
View File
@@ -2,7 +2,7 @@ use crate::recipes::print_recipe::{
missing_parameters_command_line, print_recipe_explanation,
print_required_parameters_for_template,
};
use crate::recipes::search_recipe::retrieve_recipe_file;
use crate::recipes::search_recipe::load_recipe_file;
use crate::recipes::secret_discovery::{discover_recipe_secrets, SecretRequirement};
use anyhow::Result;
use goose::config::Config;
@@ -15,8 +15,6 @@ use goose::recipe::Recipe;
use serde_json::Value;
use std::collections::HashMap;
pub const RECIPE_FILE_EXTENSIONS: &[&str] = &["yaml", "json"];
fn create_user_prompt_callback() -> impl Fn(&str, &str) -> Result<String> {
|key: &str, description: &str| -> Result<String> {
let input_value =
@@ -26,7 +24,7 @@ fn create_user_prompt_callback() -> impl Fn(&str, &str) -> Result<String> {
}
fn load_recipe_file_with_dir(recipe_name: &str) -> Result<(RecipeFile, String)> {
let recipe_file = retrieve_recipe_file(recipe_name)?;
let recipe_file = load_recipe_file(recipe_name)?;
let recipe_dir_str = recipe_file
.parent_dir
.to_str()
@@ -36,7 +34,7 @@ fn load_recipe_file_with_dir(recipe_name: &str) -> Result<(RecipeFile, String)>
}
pub fn load_recipe(recipe_name: &str, params: Vec<(String, String)>) -> Result<Recipe> {
let recipe_file = retrieve_recipe_file(recipe_name)?;
let recipe_file = load_recipe_file(recipe_name)?;
match build_recipe_from_template(recipe_file, params, Some(create_user_prompt_callback())) {
Ok(recipe) => {
let secret_requirements = discover_recipe_secrets(&recipe);
+21 -155
View File
@@ -1,35 +1,15 @@
use anyhow::{anyhow, Result};
use anyhow::Result;
use goose::config::Config;
use goose::recipe::read_recipe_file_content::{read_recipe_file, RecipeFile};
use goose::recipe::template_recipe::parse_recipe_content;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use crate::recipes::recipe::RECIPE_FILE_EXTENSIONS;
use goose::recipe::read_recipe_file_content::RecipeFile;
use super::github_recipe::{
list_github_recipes, retrieve_recipe_from_github, RecipeInfo, RecipeSource,
GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY,
};
use goose::recipe::local_recipes::{list_local_recipes, load_local_recipe_file};
const GOOSE_RECIPE_PATH_ENV_VAR: &str = "GOOSE_RECIPE_PATH";
pub fn retrieve_recipe_file(recipe_name: &str) -> Result<RecipeFile> {
if RECIPE_FILE_EXTENSIONS
.iter()
.any(|ext| recipe_name.ends_with(&format!(".{}", ext)))
{
let path = PathBuf::from(recipe_name);
return read_recipe_file(path);
}
if is_file_path(recipe_name) || is_file_name(recipe_name) {
return Err(anyhow!(
"Recipe file {} is not a json or yaml file",
recipe_name
));
}
retrieve_recipe_from_local_path(recipe_name).or_else(|e| {
pub fn load_recipe_file(recipe_name: &str) -> Result<RecipeFile> {
load_local_recipe_file(recipe_name).or_else(|e| {
if let Some(recipe_repo_full_name) = configured_github_recipe_repo() {
retrieve_recipe_from_github(recipe_name, &recipe_repo_full_name)
} else {
@@ -38,60 +18,6 @@ pub fn retrieve_recipe_file(recipe_name: &str) -> Result<RecipeFile> {
})
}
fn is_file_path(recipe_name: &str) -> bool {
recipe_name.contains('/')
|| recipe_name.contains('\\')
|| recipe_name.starts_with('~')
|| recipe_name.starts_with('.')
}
fn is_file_name(recipe_name: &str) -> bool {
Path::new(recipe_name).extension().is_some()
}
fn read_recipe_in_dir(dir: &Path, recipe_name: &str) -> Result<RecipeFile> {
for ext in RECIPE_FILE_EXTENSIONS {
let recipe_path = dir.join(format!("{}.{}", recipe_name, ext));
if let Ok(result) = read_recipe_file(recipe_path) {
return Ok(result);
}
}
Err(anyhow!(format!(
"No {}.yaml or {}.json recipe file found in directory: {}",
recipe_name,
recipe_name,
dir.display()
)))
}
fn retrieve_recipe_from_local_path(recipe_name: &str) -> Result<RecipeFile> {
let mut search_dirs = vec![PathBuf::from(".")];
if let Ok(recipe_path_env) = env::var(GOOSE_RECIPE_PATH_ENV_VAR) {
let path_separator = if cfg!(windows) { ';' } else { ':' };
let recipe_path_env_dirs: Vec<PathBuf> = recipe_path_env
.split(path_separator)
.map(PathBuf::from)
.collect();
search_dirs.extend(recipe_path_env_dirs);
}
for dir in &search_dirs {
if let Ok(result) = read_recipe_in_dir(dir, recipe_name) {
return Ok(result);
}
}
let search_dirs_str = search_dirs
.iter()
.map(|p| p.to_string_lossy())
.collect::<Vec<_>>()
.join(":");
Err(anyhow!(
"️ Failed to retrieve {}.yaml or {}.json in {}",
recipe_name,
recipe_name,
search_dirs_str
))
}
fn configured_github_recipe_repo() -> Option<String> {
let config = Config::global();
match config.get_param(GOOSE_RECIPE_GITHUB_REPO_CONFIG_KEY) {
@@ -105,8 +31,22 @@ pub fn list_available_recipes() -> Result<Vec<RecipeInfo>> {
let mut recipes = Vec::new();
// Search local recipes
if let Ok(local_recipes) = discover_local_recipes() {
recipes.extend(local_recipes);
if let Ok(local_recipes) = list_local_recipes() {
recipes.extend(local_recipes.into_iter().map(|(path, recipe)| {
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
RecipeInfo {
name,
source: RecipeSource::Local,
path: path.to_string_lossy().to_string(),
title: Some(recipe.title),
description: Some(recipe.description),
}
}));
}
// Search GitHub recipes if configured
@@ -118,77 +58,3 @@ pub fn list_available_recipes() -> Result<Vec<RecipeInfo>> {
Ok(recipes)
}
fn discover_local_recipes() -> Result<Vec<RecipeInfo>> {
let mut recipes = Vec::new();
let mut search_dirs = vec![PathBuf::from(".")];
// Add GOOSE_RECIPE_PATH directories
if let Ok(recipe_path_env) = env::var(GOOSE_RECIPE_PATH_ENV_VAR) {
let path_separator = if cfg!(windows) { ';' } else { ':' };
let recipe_path_env_dirs: Vec<PathBuf> = recipe_path_env
.split(path_separator)
.map(PathBuf::from)
.collect();
search_dirs.extend(recipe_path_env_dirs);
}
for dir in search_dirs {
if let Ok(dir_recipes) = scan_directory_for_recipes(&dir) {
recipes.extend(dir_recipes);
}
}
Ok(recipes)
}
fn scan_directory_for_recipes(dir: &Path) -> Result<Vec<RecipeInfo>> {
let mut recipes = Vec::new();
if !dir.exists() || !dir.is_dir() {
return Ok(recipes);
}
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(extension) = path.extension() {
if RECIPE_FILE_EXTENSIONS.contains(&extension.to_string_lossy().as_ref()) {
if let Ok(recipe_info) = create_local_recipe_info(&path) {
recipes.push(recipe_info);
}
}
}
}
}
Ok(recipes)
}
fn create_local_recipe_info(path: &Path) -> Result<RecipeInfo> {
let content = fs::read_to_string(path)?;
let recipe_dir = path
.parent()
.unwrap_or_else(|| Path::new("."))
.to_string_lossy()
.to_string();
let (recipe, _) = parse_recipe_content(&content, recipe_dir)?;
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let path_str = path.to_string_lossy().to_string();
Ok(RecipeInfo {
name,
source: RecipeSource::Local,
path: path_str,
title: Some(recipe.title),
description: Some(recipe.description),
})
}
@@ -1,4 +1,4 @@
use crate::recipes::search_recipe::retrieve_recipe_file;
use crate::recipes::search_recipe::load_recipe_file;
use goose::agents::extension::ExtensionConfig;
use goose::recipe::Recipe;
use std::collections::HashSet;
@@ -116,7 +116,7 @@ fn discover_recipe_secrets_recursive(
/// For secret discovery, we only need the recipe structure (extensions and env_keys),
/// not parameter-substituted content, so we parse the raw YAML directly for speed and robustness.
fn load_sub_recipe(recipe_path: &str) -> Result<Recipe, Box<dyn std::error::Error>> {
let recipe_file = retrieve_recipe_file(recipe_path)?;
let recipe_file = load_recipe_file(recipe_path)?;
let recipe: Recipe = serde_yaml::from_str(&recipe_file.content)?;
Ok(recipe)
}