feat: Add installable skills marketplace with remote registry#129
feat: Add installable skills marketplace with remote registry#129Akash-nath29 merged 3 commits intodevfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🚀 Thanks for opening a Pull Request! A maintainer will review this soon. Meanwhile: Your contribution helps make this project better! |
There was a problem hiding this comment.
Pull request overview
This PR introduces a comprehensive Skills Marketplace system that enables Coderrr to discover, install, and execute third-party Python-based skills from a remote GitHub registry. The implementation adds plugin architecture with local skill management, remote marketplace integration, and agent-level execution support.
Changes:
- Added complete skill marketplace infrastructure with four new modules for registry management, remote downloading, Python tool execution, and CLI commands
- Integrated skill tool execution into the agent's plan execution flow with a new
invoke_skillaction type - Extended backend API model and updated documentation to support the new marketplace feature
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| src/skillMarketplace.js | Implements remote registry client for fetching, searching, caching, and downloading skills from GitHub |
| src/skillRegistry.js | Manages local skill discovery, validation, metadata parsing, and tool manifest generation |
| src/skillRunner.js | Executes Python tools in isolated subprocesses with argument conversion and timeout handling |
| src/skillsUI.js | Provides CLI commands for skill management (install, uninstall, list, search, info) |
| src/agent.js | Loads skills on initialization, injects tool manifest into prompts, and executes invoke_skill actions |
| src/executor.js | Imports skillRunner module to support skill execution infrastructure |
| bin/coderrr.js | Registers skill management commands with the CLI program |
| backend/main.py | Extends PlanStep model to support invoke_skill action with skill, tool, and args fields |
| README.md | Documents the Skills Marketplace feature with usage examples and popular skills table |
| } else if (step.action === 'invoke_skill') { | ||
| // Execute a skill tool | ||
| ui.info(`Invoking skill tool: ${step.skill}/${step.tool}`); | ||
|
|
||
| const result = await skillRunner.executeTool( | ||
| step.skill, | ||
| step.tool, | ||
| step.args || {}, | ||
| { cwd: this.workingDir } | ||
| ); | ||
|
|
||
| if (result.success) { | ||
| stepResult = `Skill ${step.skill}/${step.tool} executed successfully`; | ||
| stepSuccess = true; | ||
| ui.success(`Tool output:\n${result.output}`); | ||
| } else { | ||
| throw new Error(result.error || 'Skill tool execution failed'); | ||
| } |
There was a problem hiding this comment.
Skill tool execution lacks user permission prompts. According to the project's architecture guidelines (CodingGuidelineID: 1000000), "ALL commands require user permission" like GitHub Copilot's model. The run_command action uses requirePermission: true (line 454), but the new invoke_skill action executes Python tools directly without asking for user confirmation. This violates the established safety pattern where users must approve potentially dangerous operations.
Consider adding a permission prompt before executing skill tools, similar to how command execution is handled. This is especially important since skills are third-party code that could perform arbitrary operations.
| for (const tool of skillInfo.tools) { | ||
| console.log(` Downloading ${tool}.py...`); | ||
| try { | ||
| const toolContent = await downloadFile(`${baseUrl}/tools/${tool}.py`); | ||
| fs.writeFileSync(path.join(toolsDir, `${tool}.py`), toolContent, 'utf8'); |
There was a problem hiding this comment.
Path traversal vulnerability in tool names during download. Tool names from skillInfo.tools are used directly in file paths without validation. A malicious registry entry with tool names like ../../../malicious could write Python files outside the tools directory.
Add validation to ensure tool names contain only safe characters (alphanumeric, hyphens, underscores) and use path.basename() to prevent path traversal. Reject tool names containing path separators or traversal sequences.
| /** | ||
| * Skills UI for Coderrr CLI | ||
| * | ||
| * Provides CLI commands for managing agent skills. | ||
| * Supports both local and remote (marketplace) installation. | ||
| */ | ||
|
|
||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| const chalk = require('chalk'); | ||
| const inquirer = require('inquirer'); | ||
| const skillRegistry = require('./skillRegistry'); | ||
| const marketplace = require('./skillMarketplace'); | ||
| const { checkPythonAvailable, installSkillDependencies } = require('./skillRunner'); | ||
|
|
||
| /** | ||
| * Display list of installed skills | ||
| */ | ||
| function displaySkillsList() { | ||
| const skills = skillRegistry.loadAllSkills(); | ||
|
|
||
| if (skills.length === 0) { | ||
| console.log(chalk.yellow('\n▲ No skills installed.')); | ||
| console.log(chalk.gray(' Install skills with: coderrr install <skill-name>')); | ||
| console.log(chalk.gray(' Browse marketplace with: coderrr market\n')); | ||
| return; | ||
| } | ||
|
|
||
| console.log(chalk.cyan.bold('\n├─ Installed Skills\n')); | ||
|
|
||
| for (const skill of skills) { | ||
| console.log(` ${chalk.white.bold(skill.name)} - ${chalk.gray(skill.description)}`); | ||
| for (const tool of skill.tools) { | ||
| const params = tool.parameters.length > 0 ? `(${tool.parameters.join(', ')})` : '()'; | ||
| console.log(` ${chalk.green('•')} ${tool.name}${chalk.gray(params)}`); | ||
| } | ||
| console.log(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Install a skill from local path | ||
| * @param {string} sourcePath - Resolved path to skill folder | ||
| */ | ||
| async function installLocalSkill(sourcePath) { | ||
| if (!fs.existsSync(sourcePath)) { | ||
| console.log(chalk.red(`\n✗ Source not found: ${sourcePath}\n`)); | ||
| return false; | ||
| } | ||
|
|
||
| const validation = skillRegistry.validateSkillStructure(sourcePath); | ||
| if (!validation.valid) { | ||
| console.log(chalk.red(`\n✗ Invalid skill: ${validation.error}`)); | ||
| console.log(chalk.gray('\n Required structure:')); | ||
| console.log(chalk.gray(' <skill>/')); | ||
| console.log(chalk.gray(' ├── Skills.md')); | ||
| console.log(chalk.gray(' └── tools/')); | ||
| console.log(chalk.gray(' └── *.py\n')); | ||
| return false; | ||
| } | ||
|
|
||
| const skillName = path.basename(sourcePath); | ||
| const targetPath = path.join(skillRegistry.SKILLS_DIR, skillName); | ||
|
|
||
| if (fs.existsSync(targetPath)) { | ||
| console.log(chalk.yellow(`\n▲ Skill "${skillName}" already installed.`)); | ||
| console.log(chalk.gray(` Use: coderrr uninstall ${skillName}\n`)); | ||
| return false; | ||
| } | ||
|
|
||
| skillRegistry.ensureSkillsDir(); | ||
| fs.cpSync(sourcePath, targetPath, { recursive: true }); | ||
|
|
||
| const depsResult = await installSkillDependencies(skillName); | ||
| if (!depsResult.success && depsResult.error) { | ||
| console.log(chalk.yellow(`\n▲ Warning: ${depsResult.error}`)); | ||
| } | ||
|
|
||
| const skill = skillRegistry.loadSkill(skillName); | ||
| console.log(chalk.green(`\n■ Skill "${skillName}" installed!`)); | ||
| console.log(` Tools: ${skill.tools.map(t => t.name).join(', ')}\n`); | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Install a skill from marketplace | ||
| * @param {string} skillName - Name of skill in registry | ||
| */ | ||
| async function installRemoteSkill(skillName) { | ||
| console.log(` Fetching "${skillName}" from marketplace...`); | ||
|
|
||
| const result = await marketplace.downloadSkill(skillName); | ||
|
|
||
| if (!result.success) { | ||
| console.log(chalk.red(`\n✗ ${result.error}\n`)); | ||
| return false; | ||
| } | ||
|
|
||
| console.log(chalk.green(`\n■ Skill "${skillName}" installed!`)); | ||
| console.log(` Tools: ${result.skill.tools.join(', ')}\n`); | ||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Install a skill (auto-detect local vs remote) | ||
| * @param {string} source - Local path or skill name | ||
| */ | ||
| async function installSkill(source) { | ||
| console.log(chalk.cyan.bold('\n├─ Installing Skill\n')); | ||
|
|
||
| const python = await checkPythonAvailable(); | ||
| if (!python.available) { | ||
| console.log(chalk.red('✗ Python not available. Skills require Python 3.8+.\n')); | ||
| return false; | ||
| } | ||
| console.log(chalk.green(` ■ Python ${python.version} found`)); | ||
|
|
||
| if (marketplace.isLocalPath(source)) { | ||
| return await installLocalSkill(path.resolve(source)); | ||
| } else { | ||
| return await installRemoteSkill(source); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Uninstall a skill | ||
| * @param {string} skillName - Name of the skill | ||
| */ | ||
| async function uninstallSkill(skillName) { | ||
| if (!skillRegistry.isSkillInstalled(skillName)) { | ||
| console.log(chalk.yellow(`\n▲ Skill "${skillName}" not installed.\n`)); | ||
| return false; | ||
| } | ||
|
|
||
| const { confirm } = await inquirer.prompt([{ | ||
| type: 'confirm', | ||
| name: 'confirm', | ||
| message: `Remove skill "${skillName}"?`, | ||
| default: false | ||
| }]); | ||
|
|
||
| if (!confirm) { | ||
| console.log(chalk.yellow('\n▲ Cancelled.\n')); | ||
| return false; | ||
| } | ||
|
|
||
| if (skillRegistry.removeSkill(skillName)) { | ||
| console.log(chalk.green(`\n■ Skill "${skillName}" uninstalled.\n`)); | ||
| return true; | ||
| } else { | ||
| console.log(chalk.red(`\n✗ Failed to uninstall.\n`)); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Search marketplace for skills | ||
| * @param {string} query - Search query | ||
| */ | ||
| async function searchMarketplace(query) { | ||
| console.log(chalk.cyan.bold('\n├─ Searching Marketplace\n')); | ||
|
|
||
| try { | ||
| const results = await marketplace.searchSkills(query); | ||
|
|
||
| if (results.length === 0) { | ||
| console.log(chalk.yellow(` No skills found for: "${query}"\n`)); | ||
| return; | ||
| } | ||
|
|
||
| console.log(` Found ${results.length} skill(s):\n`); | ||
|
|
||
| for (const skill of results) { | ||
| const installed = skillRegistry.isSkillInstalled(skill.name); | ||
| const status = installed ? chalk.green(' [installed]') : ''; | ||
|
|
||
| console.log(` ${chalk.cyan.bold(skill.name)}${status}`); | ||
| console.log(` ${skill.description}`); | ||
| if (skill.tags && skill.tags.length > 0) { | ||
| console.log(` ${chalk.gray('Tags: ' + skill.tags.join(', '))}`); | ||
| } | ||
| console.log(); | ||
| } | ||
| } catch (error) { | ||
| console.log(chalk.red(` ✗ ${error.message}\n`)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * List all available skills in marketplace | ||
| */ | ||
| async function listMarketplace() { | ||
| console.log(chalk.cyan.bold('\n├─ Available Skills (Marketplace)\n')); | ||
|
|
||
| try { | ||
| const skills = await marketplace.listAvailableSkills(); | ||
|
|
||
| if (skills.length === 0) { | ||
| console.log(chalk.yellow(' No skills available in marketplace.\n')); | ||
| return; | ||
| } | ||
|
|
||
| for (const skill of skills) { | ||
| const installed = skillRegistry.isSkillInstalled(skill.name); | ||
| const status = installed ? chalk.green(' ✓') : ''; | ||
|
|
||
| console.log(` ${chalk.cyan(skill.name)}${status} - ${skill.description}`); | ||
| } | ||
| console.log(); | ||
| console.log(chalk.gray(` Install with: coderrr install <skill-name>\n`)); | ||
| } catch (error) { | ||
| console.log(chalk.red(` ✗ ${error.message}\n`)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Show detailed info about a skill | ||
| * @param {string} skillName - Name of skill | ||
| */ | ||
| async function showSkillInfo(skillName) { | ||
| console.log(chalk.cyan.bold('\n├─ Skill Info\n')); | ||
|
|
||
| try { | ||
| const skill = await marketplace.getSkillInfo(skillName); | ||
|
|
||
| if (!skill) { | ||
| console.log(chalk.red(` Skill not found: ${skillName}\n`)); | ||
| return; | ||
| } | ||
|
|
||
| const installed = skillRegistry.isSkillInstalled(skillName); | ||
|
|
||
| console.log(` ${chalk.white.bold(skill.displayName || skill.name)}\n`); | ||
| console.log(` Name: ${skill.name}`); | ||
| console.log(` Status: ${installed ? chalk.green('Installed') : chalk.yellow('Not installed')}`); | ||
| console.log(` Version: ${skill.version}`); | ||
| console.log(` Author: ${skill.author}`); | ||
| console.log(` Description: ${skill.description}`); | ||
| console.log(` Tools: ${skill.tools.join(', ')}`); | ||
| if (skill.tags && skill.tags.length > 0) { | ||
| console.log(` Tags: ${skill.tags.join(', ')}`); | ||
| } | ||
| console.log(); | ||
|
|
||
| if (!installed) { | ||
| console.log(chalk.gray(` Install with: coderrr install ${skillName}\n`)); | ||
| } | ||
| } catch (error) { | ||
| console.log(chalk.red(` ✗ ${error.message}\n`)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Register skill commands with commander program | ||
| * @param {Command} program - Commander program instance | ||
| */ | ||
| function registerSkillCommands(program) { | ||
| // List installed skills | ||
| program | ||
| .command('skills') | ||
| .description('List all installed agent skills') | ||
| .action(() => { | ||
| displaySkillsList(); | ||
| }); | ||
|
|
||
| // Install a skill (local or from marketplace) | ||
| program | ||
| .command('install <source>') | ||
| .description('Install a skill (name from marketplace or local path)') | ||
| .action(async (source) => { | ||
| await installSkill(source); | ||
| }); | ||
|
|
||
| // Uninstall a skill | ||
| program | ||
| .command('uninstall <skill-name>') | ||
| .description('Uninstall an installed skill') | ||
| .action(async (skillName) => { | ||
| await uninstallSkill(skillName); | ||
| }); | ||
|
|
||
| // Search marketplace | ||
| program | ||
| .command('search <query>') | ||
| .description('Search for skills in the marketplace') | ||
| .action(async (query) => { | ||
| await searchMarketplace(query); | ||
| }); | ||
|
|
||
| // List all available skills in marketplace | ||
| program | ||
| .command('market') | ||
| .description('Browse all available skills in the marketplace') | ||
| .action(async () => { | ||
| await listMarketplace(); | ||
| }); | ||
|
|
||
| // Show skill info | ||
| program | ||
| .command('info <skill-name>') | ||
| .description('Show detailed information about a skill') | ||
| .action(async (skillName) => { | ||
| await showSkillInfo(skillName); | ||
| }); | ||
| } | ||
|
|
||
| module.exports = { | ||
| displaySkillsList, | ||
| installSkill, | ||
| uninstallSkill, | ||
| searchMarketplace, | ||
| listMarketplace, | ||
| showSkillInfo, | ||
| registerSkillCommands | ||
| }; |
There was a problem hiding this comment.
Test coverage gap for new skill marketplace functionality. The repository has comprehensive automated testing using Jest (as evidenced by existing test files like test/doctor.test.js, test/insights.test.js, and test/recipes.test.js), but the newly added skill marketplace modules lack any test coverage.
Consider adding tests for:
skillMarketplace.js: Registry fetching, caching, skill download/installation, error handlingskillRegistry.js: Skill discovery, validation, metadata parsing, manifest generationskillRunner.js: Tool execution, argument building, Python availability checking, timeout handlingskillsUI.js: Command registration and execution flows
This is especially important given the security-sensitive nature of downloading and executing third-party code.
| // Download each tool | ||
| for (const tool of skillInfo.tools) { | ||
| console.log(` Downloading ${tool}.py...`); | ||
| try { | ||
| const toolContent = await downloadFile(`${baseUrl}/tools/${tool}.py`); | ||
| fs.writeFileSync(path.join(toolsDir, `${tool}.py`), toolContent, 'utf8'); | ||
| } catch (e) { | ||
| console.warn(` Warning: Could not download ${tool}.py`); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Inconsistent error handling in downloadSkill function. When individual tool downloads fail (line 184-186), the function logs a warning but continues. However, if all tools fail to download, the skill would be installed with no functional tools, but the function would still return success. This could lead to a confusing user experience where a skill appears installed but doesn't work.
Consider tracking how many tools were successfully downloaded and returning an error or warning if no tools were successfully downloaded. Alternatively, document this behavior clearly so users understand partial installations are possible.
| // Download each tool | |
| for (const tool of skillInfo.tools) { | |
| console.log(` Downloading ${tool}.py...`); | |
| try { | |
| const toolContent = await downloadFile(`${baseUrl}/tools/${tool}.py`); | |
| fs.writeFileSync(path.join(toolsDir, `${tool}.py`), toolContent, 'utf8'); | |
| } catch (e) { | |
| console.warn(` Warning: Could not download ${tool}.py`); | |
| } | |
| } | |
| // Download each tool | |
| const totalTools = Array.isArray(skillInfo.tools) ? skillInfo.tools.length : 0; | |
| let downloadedTools = 0; | |
| for (const tool of skillInfo.tools) { | |
| console.log(` Downloading ${tool}.py...`); | |
| try { | |
| const toolContent = await downloadFile(`${baseUrl}/tools/${tool}.py`); | |
| fs.writeFileSync(path.join(toolsDir, `${tool}.py`), toolContent, 'utf8'); | |
| downloadedTools += 1; | |
| } catch (e) { | |
| console.warn(` Warning: Could not download ${tool}.py`); | |
| } | |
| } | |
| // If no tools were successfully downloaded, treat as a failed installation | |
| if (totalTools > 0 && downloadedTools === 0) { | |
| throw new Error(`No tools for skill "${skillName}" could be downloaded`); | |
| } | |
| // If some, but not all, tools were downloaded, warn about partial install | |
| if (totalTools > 0 && downloadedTools > 0 && downloadedTools < totalTools) { | |
| console.warn( | |
| ` Warning: Only ${downloadedTools}/${totalTools} tools were downloaded for skill "${skillName}".` | |
| ); | |
| } |
|
|
||
| // Load installed agent skills for tool invocation | ||
| this.installedSkills = skillRegistry.loadAllSkills(); | ||
| this.toolManifest = skillRegistry.generateToolManifest(); |
There was a problem hiding this comment.
Redundant skill loading during execution. Lines 471-473 reload installed skills and regenerate the tool manifest inside the run_command execution block, but this is already done during agent initialization (lines 64-66). This redundant loading serves no purpose as it's only executed when this.runningProcesses is undefined, which is unlikely after proper initialization.
The comment says "Load installed agent skills for tool invocation" but this is not related to command execution. This appears to be leftover code or a copy-paste error. Consider removing this redundant code block as skills are already loaded at initialization.
| // Load installed agent skills for tool invocation | |
| this.installedSkills = skillRegistry.loadAllSkills(); | |
| this.toolManifest = skillRegistry.generateToolManifest(); |
| const proc = spawn(pythonCommand, [toolPath, ...argsList], { | ||
| cwd, | ||
| env: { | ||
| ...process.env, | ||
| CODERRR_SKILL: skillName, | ||
| CODERRR_TOOL: toolName, | ||
| CODERRR_CWD: cwd | ||
| }, | ||
| shell: true, | ||
| timeout | ||
| }); |
There was a problem hiding this comment.
Inconsistent use of shell option with spawn. The code passes shell: true to spawn (line 56) when executing Python scripts, which is unnecessary and introduces security risks. When spawn is given an array of arguments (as done here with [toolPath, ...argsList]), the shell option is not needed for basic execution and opens up potential command injection vulnerabilities if any arguments are not properly sanitized.
Python script execution doesn't require a shell. Consider removing shell: true unless there's a specific reason it's needed (which should be documented). This would improve security and performance. If shell features are required, document why and ensure all inputs are thoroughly validated.
| const skillDir = path.join(skillRegistry.SKILLS_DIR, skillName); | ||
| const toolsDir = path.join(skillDir, 'tools'); | ||
| const baseUrl = skillInfo.download_url; | ||
|
|
||
| try { | ||
| // Create directories | ||
| skillRegistry.ensureSkillsDir(); | ||
| fs.mkdirSync(toolsDir, { recursive: true }); | ||
|
|
||
| // Download Skills.md | ||
| console.log(` Downloading Skills.md...`); | ||
| const skillsMd = await downloadFile(`${baseUrl}/Skills.md`); | ||
| fs.writeFileSync(path.join(skillDir, 'Skills.md'), skillsMd, 'utf8'); | ||
|
|
||
| // Try to download requirements.txt (optional) | ||
| try { | ||
| const requirements = await downloadFile(`${baseUrl}/requirements.txt`); | ||
| fs.writeFileSync(path.join(skillDir, 'requirements.txt'), requirements, 'utf8'); | ||
| console.log(` Found requirements.txt`); | ||
| } catch (e) { | ||
| // requirements.txt is optional, ignore | ||
| } | ||
|
|
||
| // Download each tool | ||
| for (const tool of skillInfo.tools) { | ||
| console.log(` Downloading ${tool}.py...`); | ||
| try { | ||
| const toolContent = await downloadFile(`${baseUrl}/tools/${tool}.py`); | ||
| fs.writeFileSync(path.join(toolsDir, `${tool}.py`), toolContent, 'utf8'); |
There was a problem hiding this comment.
Missing URL validation allows arbitrary file downloads. The downloadSkill function constructs download URLs from skillInfo.download_url (from the remote registry) without validating that the URL is from a trusted domain. While the default REGISTRY_URL is GitHub, a compromised or malicious registry could return arbitrary URLs, potentially leading to SSRF attacks or downloading malicious content.
Add validation to ensure download_url matches expected patterns (e.g., starts with the expected GitHub raw content URL pattern). Consider implementing a whitelist of allowed domains or URL patterns for security.
| // Timeout handler | ||
| setTimeout(() => { | ||
| if (resolved) return; | ||
| resolved = true; | ||
|
|
||
| proc.kill('SIGTERM'); | ||
| resolve({ | ||
| success: false, | ||
| output: stdout.trim(), | ||
| error: `Tool execution timed out after ${timeout}ms`, | ||
| exitCode: 124 | ||
| }); | ||
| }, timeout); |
There was a problem hiding this comment.
Race condition in timeout handling. The timeout handler at line 93-104 uses a separate setTimeout that runs concurrently with the process's 'close' and 'error' event handlers. If the process completes naturally just as the timeout fires, there's a race condition where both handlers could attempt to resolve the promise. While the resolved flag prevents double resolution, the proc.kill('SIGTERM') on line 97 could still be called on an already-completed process, potentially causing an uncaught error.
The spawn timeout option at line 57 should already handle timeout automatically, making the manual setTimeout redundant. Consider removing the manual timeout handler (lines 93-104) and relying solely on the spawn timeout option, which handles this more cleanly. If manual handling is needed, ensure proc.kill() is wrapped in a try-catch.
| // Timeout handler | |
| setTimeout(() => { | |
| if (resolved) return; | |
| resolved = true; | |
| proc.kill('SIGTERM'); | |
| resolve({ | |
| success: false, | |
| output: stdout.trim(), | |
| error: `Tool execution timed out after ${timeout}ms`, | |
| exitCode: 124 | |
| }); | |
| }, timeout); |
| const proc = spawn(pythonCommand, ['-m', 'pip', 'install', '-r', requirementsPath], { | ||
| shell: true, | ||
| timeout: 120000 // 2 minute timeout for pip install | ||
| }); |
There was a problem hiding this comment.
Insufficient timeout for pip install operations. The timeout for pip install -r requirements.txt is set to 120000ms (2 minutes) at line 212. For skills with many or large dependencies, this timeout may be insufficient, especially on slower internet connections or when packages need to be compiled. This could lead to partial installations or false failures.
Consider either increasing the timeout (e.g., to 5-10 minutes) or making it configurable. Also consider informing the user when dependency installation is taking a long time, rather than silently timing out.
| const path = require('path'); | ||
| const os = require('os'); | ||
| const ui = require('./ui'); | ||
| const skillRunner = require('./skillRunner'); |
There was a problem hiding this comment.
Unused variable skillRunner.
| const skillRunner = require('./skillRunner'); |
This pull request introduces a comprehensive "Skills Marketplace" system to Coderrr, allowing users to browse, install, and invoke third-party skills (plugins) that extend the agent's capabilities. The update includes both backend and frontend changes to support skill discovery, installation, management, and execution, as well as documentation and prompt updates to guide users and the AI model in using these new features.
Major new features and changes:
Skills Marketplace and Registry
Marketplace Client Implementation:
Added
src/skillMarketplace.js, which connects to a remote GitHub-based skill registry, supports searching, listing, downloading, and installing skills, and manages a local cache for efficiency. This module also handles skill installation, including dependency management and error recovery.Skill Registry Management:
Introduced
src/skillRegistry.jsto discover, validate, and load installed skills from the user's~/.coderrr/skills/directory. It parses skill metadata, tool docstrings, and generates a manifest of available tools for use by the agent and LLM context.Agent and Execution Integration
Agent Support for Skills and Tools:
Updated
src/agent.jsto load installed skills and generate a tool manifest on initialization. The agent now injects this manifest into the LLM prompt, enabling the AI to suggest or select skill tools as part of its plan. It also supports executing steps with the newinvoke_skillaction by calling the appropriate tool viaskillRunner. [1] [2] [3] [4] [5]Executor Integration:
Updated
src/executor.jsto importskillRunner, preparing for skill tool execution from various contexts.Backend and API
Extended the backend
PlanStepmodel inbackend/main.pyto support the newinvoke_skillaction, including fields for skill name, tool name, and arguments, allowing the agent to plan and execute skill-based steps. [1] [2]Documentation
Added a new "Skills Marketplace" section to
README.md, explaining how to browse, install, and use skills, and listing popular skills with descriptions. The table of contents was updated accordingly.Most important changes:
Skills Marketplace & Registry
src/skillMarketplace.jsto enable searching, listing, downloading, and installing skills from a remote registry, with cache management and dependency installation.src/skillRegistry.jsto discover, validate, and load installed skills, parse their metadata, and generate a manifest of available tools.Agent & Execution Integration
src/agent.jsto load installed skills, inject a tool manifest into the LLM prompt, and handle the newinvoke_skillaction for executing skill tools. [1] [2] [3] [4] [5]src/executor.jsto importskillRunnerfor executing skill tools.Backend & API
PlanStepmodel inbackend/main.pywith support for theinvoke_skillaction and related fields, enabling the backend to plan steps that use installed skills. [1] [2]Documentation
README.md, guiding users on discovering and installing skills and updating the table of contents.