Complete collection of AI agent skills including: - Frontend Development (Vue, React, Next.js, Three.js) - Backend Development (NestJS, FastAPI, Node.js) - Mobile Development (React Native, Expo) - Testing (E2E, frontend, webapp) - DevOps (GitHub Actions, CI/CD) - Marketing (SEO, copywriting, analytics) - Security (binary analysis, vulnerability scanning) - And many more... Synchronized from: https://skills.sh/ Co-Authored-By: Claude <noreply@anthropic.com>
267 lines
6.2 KiB
JavaScript
267 lines
6.2 KiB
JavaScript
/**
|
|
* Shell Command Tool
|
|
* Executes shell commands with proper error handling and output formatting
|
|
*/
|
|
|
|
const { exec, spawn } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const { BaseTool, ToolResult } = require('./tool-base.cjs');
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
class ShellTool extends BaseTool {
|
|
constructor(config = {}) {
|
|
super({
|
|
name: 'shell',
|
|
description: 'Execute shell commands in the terminal',
|
|
parameters: [
|
|
{
|
|
name: 'command',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'The shell command to execute'
|
|
},
|
|
{
|
|
name: 'cwd',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Working directory for command execution'
|
|
},
|
|
{
|
|
name: 'timeout',
|
|
type: 'number',
|
|
required: false,
|
|
description: 'Execution timeout in milliseconds (default: 30000)'
|
|
},
|
|
{
|
|
name: 'env',
|
|
type: 'object',
|
|
required: false,
|
|
description: 'Environment variables for the command'
|
|
}
|
|
],
|
|
...config
|
|
});
|
|
|
|
this.defaultTimeout = config.defaultTimeout || 30000;
|
|
this.maxOutputSize = config.maxOutputSize || 100000; // 100KB
|
|
}
|
|
|
|
/**
|
|
* Execute a shell command
|
|
*/
|
|
async execute(params) {
|
|
const { command, cwd, timeout = this.defaultTimeout, env } = params;
|
|
|
|
try {
|
|
// Security check for dangerous commands
|
|
const securityCheck = this.checkSecurity(command);
|
|
if (!securityCheck.safe) {
|
|
throw new Error(`Security warning: ${securityCheck.reason}`);
|
|
}
|
|
|
|
const options = {
|
|
timeout,
|
|
cwd: cwd || process.cwd(),
|
|
env: { ...process.env, ...env },
|
|
maxBuffer: 10 * 1024 * 1024 // 10MB
|
|
};
|
|
|
|
// Execute command
|
|
const { stdout, stderr } = await execAsync(command, options);
|
|
|
|
// Format output
|
|
const output = this.formatOutput(stdout, stderr);
|
|
|
|
return ToolResult.success(
|
|
{ stdout, stderr, exitCode: 0 },
|
|
output,
|
|
{ command, cwd: options.cwd }
|
|
);
|
|
} catch (error) {
|
|
// Handle execution errors
|
|
const output = this.formatErrorOutput(error);
|
|
|
|
return ToolResult.failure(
|
|
error,
|
|
output,
|
|
{ command, exitCode: error.code || 1 }
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Basic security check for commands
|
|
*/
|
|
checkSecurity(command) {
|
|
const dangerousPatterns = [
|
|
'rm -rf /',
|
|
'rm -rf /*',
|
|
'mkfs',
|
|
'format',
|
|
'> /dev/sd',
|
|
'dd if=',
|
|
':(){:|:&};:', // Fork bomb
|
|
'chmod 000 /',
|
|
'chown -R'
|
|
];
|
|
|
|
const lowerCommand = command.toLowerCase();
|
|
|
|
for (const pattern of dangerousPatterns) {
|
|
if (lowerCommand.includes(pattern)) {
|
|
return {
|
|
safe: false,
|
|
reason: `Command contains dangerous pattern: ${pattern}`
|
|
};
|
|
}
|
|
}
|
|
|
|
return { safe: true };
|
|
}
|
|
|
|
/**
|
|
* Format command output for display
|
|
*/
|
|
formatOutput(stdout, stderr) {
|
|
let output = '';
|
|
|
|
if (stdout && stdout.trim()) {
|
|
output += stdout;
|
|
}
|
|
|
|
if (stderr && stderr.trim()) {
|
|
if (output) output += '\n';
|
|
output += `[stderr]: ${stderr}`;
|
|
}
|
|
|
|
// Truncate if too large
|
|
if (output.length > this.maxOutputSize) {
|
|
output = output.substring(0, this.maxOutputSize);
|
|
output += `\n... [Output truncated, exceeded ${this.maxOutputSize} bytes]`;
|
|
}
|
|
|
|
return output || '[No output]';
|
|
}
|
|
|
|
/**
|
|
* Format error output
|
|
*/
|
|
formatErrorOutput(error) {
|
|
let output = '';
|
|
|
|
if (error.killed) {
|
|
output = `Command timed out after ${error.timeout}ms`;
|
|
} else if (error.code) {
|
|
output = `Command failed with exit code ${error.code}`;
|
|
} else {
|
|
output = `Command failed: ${error.message}`;
|
|
}
|
|
|
|
if (error.stderr) {
|
|
output += `\n${error.stderr}`;
|
|
}
|
|
|
|
if (error.stdout) {
|
|
output += `\n${error.stdout}`;
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Streaming Shell Tool
|
|
* For long-running commands with real-time output
|
|
*/
|
|
class StreamingShellTool extends BaseTool {
|
|
constructor(config = {}) {
|
|
super({
|
|
name: 'shell_stream',
|
|
description: 'Execute long-running shell commands with streaming output',
|
|
parameters: [
|
|
{
|
|
name: 'command',
|
|
type: 'string',
|
|
required: true,
|
|
description: 'The shell command to execute'
|
|
},
|
|
{
|
|
name: 'cwd',
|
|
type: 'string',
|
|
required: false,
|
|
description: 'Working directory for command execution'
|
|
},
|
|
{
|
|
name: 'onData',
|
|
type: 'function',
|
|
required: false,
|
|
description: 'Callback for streaming data chunks'
|
|
}
|
|
],
|
|
...config
|
|
});
|
|
}
|
|
|
|
async execute(params) {
|
|
return new Promise((resolve, reject) => {
|
|
const { command, cwd, onData } = params;
|
|
const output = { stdout: '', stderr: '' };
|
|
|
|
const options = {
|
|
cwd: cwd || process.cwd(),
|
|
shell: true
|
|
};
|
|
|
|
const proc = spawn(command, options);
|
|
|
|
proc.stdout.on('data', (data) => {
|
|
const text = data.toString();
|
|
output.stdout += text;
|
|
if (onData) onData({ type: 'stdout', data: text });
|
|
});
|
|
|
|
proc.stderr.on('data', (data) => {
|
|
const text = data.toString();
|
|
output.stderr += text;
|
|
if (onData) onData({ type: 'stderr', data: text });
|
|
});
|
|
|
|
proc.on('close', (code) => {
|
|
if (code === 0) {
|
|
resolve(
|
|
ToolResult.success(
|
|
output,
|
|
output.stdout || '[Process completed successfully]',
|
|
{ exitCode: code }
|
|
)
|
|
);
|
|
} else {
|
|
resolve(
|
|
ToolResult.failure(
|
|
new Error(`Process exited with code ${code}`),
|
|
output.stderr || output.stdout,
|
|
{ exitCode: code, ...output }
|
|
)
|
|
);
|
|
}
|
|
});
|
|
|
|
proc.on('error', (error) => {
|
|
resolve(
|
|
ToolResult.failure(
|
|
error,
|
|
`Failed to spawn process: ${error.message}`,
|
|
{ error: error.message }
|
|
)
|
|
);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
ShellTool,
|
|
StreamingShellTool
|
|
};
|