From 225d83eaa707c148fa012788356884ecdc9f92af Mon Sep 17 00:00:00 2001 From: Deenu Date: Fri, 30 Jan 2026 23:49:14 +0530 Subject: [PATCH 1/2] feat: add extensible recipe registry and CLI command --- bin/coderrr.js | 20 ++++++++++++++++- docs/recipes.md | 18 ++++++++++++++++ src/recipeManager.js | 42 ++++++++++++++++++++++++++++++++++++ src/recipeUI.js | 19 ++++++++++++++++ src/utils/recipeValidator.js | 16 ++++++++++++++ test/recipes.test.js | 10 +++++++++ 6 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 docs/recipes.md create mode 100644 src/recipeManager.js create mode 100644 src/recipeUI.js create mode 100644 src/utils/recipeValidator.js create mode 100644 test/recipes.test.js diff --git a/bin/coderrr.js b/bin/coderrr.js index 4d3ed3c..e3b6d16 100644 --- a/bin/coderrr.js +++ b/bin/coderrr.js @@ -15,7 +15,25 @@ const Agent = require('../src/agent'); const configManager = require('../src/configManager'); const { getProviderChoices, getModelChoices, getProvider, validateApiKey } = require('../src/providers'); const { tryExtractJSON } = require('../src/utils'); - +const { displayRecipeList } = require('../src/recipeUI'); +const recipeManager = require('../src/recipeManager'); +program + .command('recipe [name]') + .description('Manage and run custom coding recipes') + .option('-l, --list', 'List all available recipes') + .action((name, options) => { + if (options.list || !name) { + displayRecipeList(); + } else { + const recipe = recipeManager.getRecipe(name); + if (recipe) { + console.log(`Running recipe: ${recipe.name}...`); + // Logic to pass tasks to the agent would go here + } else { + console.log(`Recipe "${name}" not found.`); + } + } + }); // Optional: Load .env from user's home directory (for advanced users who want custom backend) const homeConfigPath = path.join(os.homedir(), '.coderrr', '.env'); if (fs.existsSync(homeConfigPath)) { diff --git a/docs/recipes.md b/docs/recipes.md new file mode 100644 index 0000000..7905a6c --- /dev/null +++ b/docs/recipes.md @@ -0,0 +1,18 @@ +# Recipe System + +Recipes are pre-defined sets of tasks that Coderrr can execute. + +## Usage +List available recipes: +`coderrr recipe --list` + +Run a recipe: +`coderrr recipe ` + +## Creating your own +Save a `.json` file in `~/.coderrr/recipes/`: +```json +{ + "name": "Quick Express", + "tasks": ["Initialize npm", "Install express", "Create app.js"] +} \ No newline at end of file diff --git a/src/recipeManager.js b/src/recipeManager.js new file mode 100644 index 0000000..7383624 --- /dev/null +++ b/src/recipeManager.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const RECIPES_DIR = path.join(os.homedir(), '.coderrr', 'recipes'); + +class RecipeManager { + constructor() { + this.ensureDirectory(); + } + + ensureDirectory() { + if (!fs.existsSync(RECIPES_DIR)) { + fs.mkdirSync(RECIPES_DIR, { recursive: true }); + // Add a default "Hello World" recipe + const defaultRecipe = { + name: "ping", + description: "A simple health check for the recipe system", + tasks: ["Create a file named ALIVE.md with the text 'Coderrr is here'"] + }; + fs.writeFileSync(path.join(RECIPES_DIR, 'ping.json'), JSON.stringify(defaultRecipe, null, 2)); + } + } + + listRecipes() { + const files = fs.readdirSync(RECIPES_DIR).filter(f => f.endsWith('.json')); + return files.map(f => { + const content = JSON.parse(fs.readFileSync(path.join(RECIPES_DIR, f), 'utf8')); + return { id: f.replace('.json', ''), ...content }; + }); + } + + getRecipe(name) { + const filePath = path.join(RECIPES_DIR, `${name}.json`); + if (fs.existsSync(filePath)) { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); + } + return null; + } +} + +module.exports = new RecipeManager(); \ No newline at end of file diff --git a/src/recipeUI.js b/src/recipeUI.js new file mode 100644 index 0000000..d185624 --- /dev/null +++ b/src/recipeUI.js @@ -0,0 +1,19 @@ +const chalk = require('chalk'); +const recipeManager = require('./recipeManager'); + +function displayRecipeList() { + const recipes = recipeManager.listRecipes(); + console.log('\n' + chalk.magenta.bold('📜 AVAILABLE CODERRR RECIPES')); + console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')); + + if (recipes.length === 0) { + console.log(chalk.yellow('No recipes found in ~/.coderrr/recipes')); + } else { + recipes.forEach(r => { + console.log(`${chalk.cyan.bold(r.id)}: ${chalk.white(r.description || 'No description')}`); + }); + } + console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n')); +} + +module.exports = { displayRecipeList }; \ No newline at end of file diff --git a/src/utils/recipeValidator.js b/src/utils/recipeValidator.js new file mode 100644 index 0000000..981fa15 --- /dev/null +++ b/src/utils/recipeValidator.js @@ -0,0 +1,16 @@ +/** + * Validates the structure of a custom recipe + */ +const validateRecipe = (recipe) => { + const errors = []; + if (!recipe.name) errors.push("Missing 'name' field"); + if (!Array.isArray(recipe.tasks) || recipe.tasks.length === 0) { + errors.push("'tasks' must be a non-empty array"); + } + return { + valid: errors.length === 0, + errors + }; +}; + +module.exports = { validateRecipe }; \ No newline at end of file diff --git a/test/recipes.test.js b/test/recipes.test.js new file mode 100644 index 0000000..cdb3256 --- /dev/null +++ b/test/recipes.test.js @@ -0,0 +1,10 @@ +const recipeManager = require('../src/recipeManager'); + +describe('Recipe System', () => { + test('should find the default ping recipe', () => { + const recipes = recipeManager.listRecipes(); + const ping = recipes.find(r => r.id === 'ping'); + expect(ping).toBeDefined(); + expect(ping.name).toBe('ping'); + }); +}); \ No newline at end of file From dbc32976a99247584ad61acad128295e74c06a5b Mon Sep 17 00:00:00 2001 From: Akash-nath29 Date: Sat, 31 Jan 2026 01:55:45 +0530 Subject: [PATCH 2/2] Fix issue @coderrr.js --- bin/coderrr.js | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/bin/coderrr.js b/bin/coderrr.js index 2228307..e791e7b 100644 --- a/bin/coderrr.js +++ b/bin/coderrr.js @@ -17,6 +17,26 @@ const { getProviderChoices, getModelChoices, getProvider, validateApiKey } = req const { tryExtractJSON } = require('../src/utils'); const { displayRecipeList } = require('../src/recipeUI'); const recipeManager = require('../src/recipeManager'); +const { displayInsights } = require('../src/insightsUI'); + +// Optional: Load .env from user's home directory (for advanced users who want custom backend) +const homeConfigPath = path.join(os.homedir(), '.coderrr', '.env'); +if (fs.existsSync(homeConfigPath)) { + require('dotenv').config({ path: homeConfigPath }); +} + +// For development: Load .env from package directory +const packageConfigPath = path.join(__dirname, '..', '.env'); +if (fs.existsSync(packageConfigPath)) { + require('dotenv').config({ path: packageConfigPath, override: false }); +} + +program + .name('coderrr') + .description('AI Coding Agent CLI - Your personal coding assistant') + .version('1.0.0'); + +// Recipe command - manage and run custom coding recipes program .command('recipe [name]') .description('Manage and run custom coding recipes') @@ -33,31 +53,15 @@ program console.log(`Recipe "${name}" not found.`); } } + }); -const { displayInsights } = require('../src/insightsUI'); - +// Insights command - display local usage statistics program .command('insights') .description('Display local usage statistics and task history') .action(() => { displayInsights(); }); -// Optional: Load .env from user's home directory (for advanced users who want custom backend) -const homeConfigPath = path.join(os.homedir(), '.coderrr', '.env'); -if (fs.existsSync(homeConfigPath)) { - require('dotenv').config({ path: homeConfigPath }); -} - -// For development: Load .env from package directory -const packageConfigPath = path.join(__dirname, '..', '.env'); -if (fs.existsSync(packageConfigPath)) { - require('dotenv').config({ path: packageConfigPath, override: false }); -} - -program - .name('coderrr') - .description('AI Coding Agent CLI - Your personal coding assistant') - .version('1.0.0'); // Config command - configure provider and API key program