Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bin/coderrr.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ 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.`);
}
}

const { displayInsights } = require('../src/insightsUI');

Expand Down
18 changes: 18 additions & 0 deletions docs/recipes.md
Original file line number Diff line number Diff line change
@@ -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 <name>`

## Creating your own
Save a `.json` file in `~/.coderrr/recipes/`:
```json
{
"name": "Quick Express",
"tasks": ["Initialize npm", "Install express", "Create app.js"]
}
42 changes: 42 additions & 0 deletions src/recipeManager.js
Original file line number Diff line number Diff line change
@@ -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();
19 changes: 19 additions & 0 deletions src/recipeUI.js
Original file line number Diff line number Diff line change
@@ -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 };
16 changes: 16 additions & 0 deletions src/utils/recipeValidator.js
Original file line number Diff line number Diff line change
@@ -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 };
10 changes: 10 additions & 0 deletions test/recipes.test.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
Loading