UNPKG

agent-rules-generator

Version:

Interactive CLI tool to generate .agent.md and .windsurfrules files for AI-assisted development

427 lines (361 loc) • 12.7 kB
/** * Recipe Management Module * Handles recipe selection, searching, and application * * This module provides a comprehensive interface for managing recipes, * including browsing, searching, applying, and refreshing recipes from * remote repositories. It integrates with both standard recipes and * Windsurf-specific recipes. * * @module RecipeManager * @author ubuntupunk * @since 1.2.0 */ const chalk = require('chalk'); const inquirer = require('inquirer').default; const { loadRecipes, searchRecipes, refreshRecipes, getRecipe } = require('./recipes_lib'); const { fetchWindsurfRecipes, listWindsurfRecipes, searchWindsurfRecipes, refreshWindsurfRecipes } = require('./windsurf_scraper'); /** * Recipe Manager Class * * Manages all recipe-related operations including selection, application, * searching, and integration with Windsurf recipes. * * @class RecipeManager */ class RecipeManager { /** * Creates a new RecipeManager instance * * @param {Object} config - Configuration object containing project settings * @param {Object} config.overview - Project overview information * @param {Object} config.technologyStack - Technology stack configuration */ constructor(config) { this.config = config; } async selectRecipe() { console.log(chalk.blue('\nšŸ³ Recipe Selection')); const { choice } = await inquirer.prompt([ { type: 'list', name: 'choice', message: 'How would you like to set up your project?', choices: [ 'Use a recipe (recommended)', 'Manual setup', 'Search recipes', 'Windsurf recipes', 'Refresh recipes', 'Cache management', 'Repository settings' ] } ]); if (choice === 'Use a recipe (recommended)') { return await this.browseRecipes(); } if (choice === 'Manual setup') { return 'manual'; } if (choice === 'Search recipes') { await this.searchRecipesCommand(); return await this.selectRecipe(); } if (choice === 'Windsurf recipes') { await this.handleWindsurfRecipes(); return await this.selectRecipe(); } if (choice === 'Refresh recipes') { await this.refreshRecipesCommand(); return await this.selectRecipe(); } if (choice === 'Cache management') { return 'cache_management'; } if (choice === 'Repository settings') { return 'repository_settings'; } } /** * Browse and select from available recipes * * Loads recipes from the remote repository, displays them to the user, * and allows selection and application of a chosen recipe. * * @async * @returns {Promise<string>} Status of the operation ('recipe_applied', etc.) * @throws {Error} When recipe loading fails */ async browseRecipes() { console.log(chalk.blue('\nšŸ“‹ Loading recipes...')); try { const recipes = await loadRecipes(); const recipeKeys = Object.keys(recipes); if (recipeKeys.length === 0) { console.log(chalk.yellow('No recipes found. Try refreshing recipes first.')); return await this.selectRecipe(); } console.log(chalk.green(`\nāœ… Found ${recipeKeys.length} recipes:\n`)); recipeKeys.forEach((key, index) => { const recipe = recipes[key]; console.log(chalk.cyan(`${index + 1}. ${recipe.name}`)); console.log(chalk.gray(` ${recipe.description}`)); console.log(chalk.gray(` Category: ${recipe.category}`)); console.log(); }); const { selectedRecipe } = await inquirer.prompt([ { type: 'list', name: 'selectedRecipe', message: 'Select a recipe:', choices: recipeKeys.map(key => ({ name: `${recipes[key].name} - ${recipes[key].description}`, value: key })) } ]); await this.applyRecipe(selectedRecipe, recipes); return 'recipe_applied'; } catch (error) { console.log(chalk.red(`āŒ Error loading recipes: ${error.message}`)); return await this.selectRecipe(); } } async applyRecipe(recipeKey, recipes) { const recipe = recipes[recipeKey]; console.log(chalk.green(`\nāœ… Selected recipe: ${recipe.name}`)); // Apply the recipe's tech stack to our configuration this.config.technologyStack = { ...recipe.techStack }; console.log(chalk.gray(`šŸ“‹ Applied tech stack: ${JSON.stringify(recipe.techStack)}`)); // Ask if user wants to customize const { customize } = await inquirer.prompt([ { type: 'confirm', name: 'customize', message: 'Would you like to customize the tech stack?', default: false } ]); if (customize) { return 'customize_tech_stack'; } return 'recipe_applied'; } async searchRecipesCommand() { const { query } = await inquirer.prompt([ { type: 'input', name: 'query', message: 'Search recipes (technology, framework, etc.):', validate: input => input.trim().length > 0 || 'Please enter a search term' } ]); console.log(chalk.blue(`\nšŸ” Searching recipes for: "${query}"`)); try { const recipes = await loadRecipes(); const matchingKeys = await searchRecipes(query, recipes); if (matchingKeys.length === 0) { console.log(chalk.yellow('No matching recipes found.')); return; } console.log(chalk.green(`\nāœ… Found ${matchingKeys.length} matching recipes:`)); matchingKeys.forEach((key, index) => { const recipe = recipes[key]; console.log(chalk.cyan(`${index + 1}. ${recipe.name}`)); console.log(chalk.gray(` ${recipe.description}`)); console.log(chalk.gray(` Category: ${recipe.category}`)); console.log(); }); } catch (error) { console.log(chalk.red(`āŒ Error searching recipes: ${error.message}`)); } } async refreshRecipesCommand() { console.log(chalk.blue('\nšŸ”„ Refreshing recipes from remote repository...')); try { const recipes = await refreshRecipes(); const count = Object.keys(recipes).length; console.log(chalk.green(`āœ… Successfully refreshed ${count} recipes`)); } catch (error) { console.error(chalk.red(`āŒ Error refreshing recipes: ${error.message}`)); } } /** * Handle Windsurf recipes menu and operations * * Provides a menu interface for browsing, searching, and refreshing * Windsurf-specific recipes scraped from the Windsurf directory. * * @async * @returns {Promise<void>} */ async handleWindsurfRecipes() { console.log(chalk.blue('\n🌊 Windsurf Recipes')); const { action } = await inquirer.prompt([ { type: 'list', name: 'action', message: 'What would you like to do with Windsurf recipes?', choices: [ 'Browse Windsurf recipes', 'Search Windsurf recipes', 'Refresh Windsurf recipes', 'Back to main menu' ] } ]); if (action === 'Browse Windsurf recipes') { return await this.browseWindsurfRecipes(); } else if (action === 'Search Windsurf recipes') { return await this.searchWindsurfRecipes(); } else if (action === 'Refresh Windsurf recipes') { await this.refreshWindsurfRecipes(); } } async browseWindsurfRecipes() { console.log(chalk.blue('\nšŸ“‹ Loading Windsurf recipes...')); try { const recipes = await listWindsurfRecipes(); if (recipes.length === 0) { console.log(chalk.yellow('No Windsurf recipes found. Try refreshing first.')); return; } console.log(chalk.green(`\nāœ… Found ${recipes.length} Windsurf recipes:\n`)); recipes.forEach((recipe, index) => { console.log(chalk.cyan(`${index + 1}. ${recipe.name}`)); console.log(chalk.gray(` Category: ${recipe.category}`)); console.log(chalk.gray(` Tech Stack: ${JSON.stringify(recipe.techStack)}`)); console.log(); }); const { useWindsurfRecipe } = await inquirer.prompt([ { type: 'confirm', name: 'useWindsurfRecipe', message: 'Would you like to use one of these Windsurf recipes?', default: false } ]); if (useWindsurfRecipe) { const { selectedRecipe } = await inquirer.prompt([ { type: 'list', name: 'selectedRecipe', message: 'Select a Windsurf recipe:', choices: recipes.map((recipe) => ({ name: `${recipe.name} (${recipe.category})`, value: recipe.key })) } ]); return await this.applyWindsurfRecipe(selectedRecipe); } } catch (error) { console.log(chalk.red(`āŒ Error loading Windsurf recipes: ${error.message}`)); } } async applyWindsurfRecipe(recipeKey) { console.log(chalk.blue(`\n🌊 Applying Windsurf recipe: ${recipeKey}`)); try { const windsurfRecipes = await fetchWindsurfRecipes(); const recipe = windsurfRecipes[recipeKey]; if (!recipe) { console.log(chalk.red('āŒ Recipe not found')); return; } this.config.technologyStack = { ...recipe.techStack }; this.config.windsurfRules = recipe.windsurfRules; console.log(chalk.green(`āœ… Applied Windsurf recipe: ${recipe.name}`)); console.log(chalk.gray(`šŸ“‹ Tech stack: ${JSON.stringify(recipe.techStack)}`)); const { customize } = await inquirer.prompt([ { type: 'confirm', name: 'customize', message: 'Would you like to customize the tech stack?', default: false } ]); if (customize) { return 'customize_tech_stack'; } } catch (error) { console.log(chalk.red(`āŒ Error applying Windsurf recipe: ${error.message}`)); } } async searchWindsurfRecipes() { const { query } = await inquirer.prompt([ { type: 'input', name: 'query', message: 'Search Windsurf recipes (technology, framework, etc.):', validate: input => input.trim().length > 0 || 'Please enter a search term' } ]); console.log(chalk.blue(`\nšŸ” Searching Windsurf recipes for: "${query}"`)); try { const matchingKeys = await searchWindsurfRecipes(query); if (matchingKeys.length === 0) { console.log(chalk.yellow('No matching Windsurf recipes found.')); return; } console.log(chalk.green(`\nāœ… Found ${matchingKeys.length} matching Windsurf recipes:`)); const windsurfRecipes = await fetchWindsurfRecipes(); matchingKeys.forEach((key, index) => { const recipe = windsurfRecipes[key]; console.log(chalk.cyan(`${index + 1}. ${recipe.name}`)); console.log(chalk.gray(` Category: ${recipe.category}`)); console.log(chalk.gray(` Tech Stack: ${JSON.stringify(recipe.techStack)}`)); console.log(); }); // Ask if user wants to select one of the found recipes const { useFoundRecipe } = await inquirer.prompt([ { type: 'confirm', name: 'useFoundRecipe', message: 'Would you like to use one of these Windsurf recipes?', default: true } ]); if (useFoundRecipe) { const { selectedRecipe } = await inquirer.prompt([ { type: 'list', name: 'selectedRecipe', message: 'Select a Windsurf recipe:', choices: matchingKeys.map((key) => { const recipe = windsurfRecipes[key]; return { name: `${recipe.name} (${recipe.category})`, value: key }; }) } ]); return await this.applyWindsurfRecipe(selectedRecipe); } } catch (error) { console.log(chalk.red(`āŒ Error searching Windsurf recipes: ${error.message}`)); } } async refreshWindsurfRecipes() { console.log(chalk.blue('\nšŸ”„ Refreshing Windsurf recipes...')); try { const recipes = await refreshWindsurfRecipes(); const count = Object.keys(recipes).length; console.log(chalk.green(`āœ… Successfully refreshed ${count} Windsurf recipes`)); } catch (error) { console.log(chalk.red(`āŒ Failed to refresh Windsurf recipes: ${error.message}`)); } } } module.exports = { RecipeManager };