Skip to main content
Glama
process.ts4.78 kB
import { execa } from 'execa'; import { CommandResult } from '../types/index.js'; import { SecurityValidator, DEFAULT_SECURITY_CONFIG } from './security.js'; import { setTimeout } from 'timers/promises'; export class ProcessExecutor { private security: SecurityValidator; constructor() { this.security = new SecurityValidator(DEFAULT_SECURITY_CONFIG); } /** * Execute a command safely with security validation */ async execute( command: string, args: string[] = [], options: { cwd?: string; timeout?: number; maxOutputSize?: number; env?: Record<string, string>; } = {} ): Promise<CommandResult> { const startTime = Date.now(); try { // Security validation this.security.validateCommand(command, args); if (options.cwd) { this.security.validatePath(options.cwd); } // Set timeout const timeout = options.timeout || DEFAULT_SECURITY_CONFIG.maxExecutionTime; const maxOutputSize = options.maxOutputSize || DEFAULT_SECURITY_CONFIG.maxOutputSize; // Execute command const result = await execa(command, args, { cwd: options.cwd, timeout, maxBuffer: maxOutputSize, env: { ...process.env, ...options.env, }, encoding: 'utf8', reject: false, // Don't throw on non-zero exit codes }); const duration = Date.now() - startTime; return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode, duration, }; } catch (error) { const duration = Date.now() - startTime; if (error instanceof Error) { // Handle timeout if (error.message.includes('timeout')) { throw new Error(`Command timed out after ${options.timeout || DEFAULT_SECURITY_CONFIG.maxExecutionTime}ms`); } // Handle killed process if (error.message.includes('killed')) { throw new Error('Command was killed due to resource limits'); } // Re-throw security errors if (error.message.includes('not allowed') || error.message.includes('dangerous')) { throw error; } } return { stdout: '', stderr: error instanceof Error ? error.message : String(error), exitCode: 1, duration, }; } } /** * Execute command and stream output */ async *executeStream( command: string, args: string[] = [], options: { cwd?: string; timeout?: number; env?: Record<string, string>; } = {} ): AsyncGenerator<{ type: 'stdout' | 'stderr'; data: string }, CommandResult> { // Security validation this.security.validateCommand(command, args); if (options.cwd) { this.security.validatePath(options.cwd); } const startTime = Date.now(); let stdout = ''; let stderr = ''; try { const subprocess = execa(command, args, { cwd: options.cwd, timeout: options.timeout || DEFAULT_SECURITY_CONFIG.maxExecutionTime, env: { ...process.env, ...options.env, }, encoding: 'utf8', buffer: false, }); // Stream stdout if (subprocess.stdout) { subprocess.stdout.on('data', (chunk: Buffer) => { const data = chunk.toString(); stdout += data; }); } // Stream stderr if (subprocess.stderr) { subprocess.stderr.on('data', (chunk: Buffer) => { const data = chunk.toString(); stderr += data; }); } const result = await subprocess; const duration = Date.now() - startTime; return { stdout, stderr, exitCode: result.exitCode, duration, }; } catch (error) { const duration = Date.now() - startTime; return { stdout, stderr: error instanceof Error ? error.message : String(error), exitCode: 1, duration, }; } } /** * Check if a command exists in the system */ async checkCommand(command: string): Promise<boolean> { try { const result = await this.execute('which', [command]); return result.exitCode === 0 && result.stdout.trim().length > 0; } catch { return false; } } /** * Get the full path of a command */ async getCommandPath(command: string): Promise<string | null> { try { const result = await this.execute('which', [command]); return result.exitCode === 0 ? result.stdout.trim() : null; } catch { return null; } } } // Singleton instance export const processExecutor = new ProcessExecutor();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cristianoaredes/mcp-mobile-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server