UNPKG

@ams-dev/process-manager

Version:

MCP server for managing and monitoring development processes

183 lines 6.43 kB
import { execa } from 'execa'; import EventEmitter from 'eventemitter3'; export class ProcessManager extends EventEmitter { processes = new Map(); nextId = 1; defaultLogBuffer = 1000; constructor() { super(); } async startProcess(config) { const id = `proc-${this.nextId++}`; const [cmd, ...args] = config.command.split(' '); const info = { id, config, status: 'running', startTime: new Date(), }; const childProcess = execa(cmd, args, { cwd: config.cwd, env: { ...globalThis.process.env, ...config.env }, reject: false, all: true, buffer: false, }); info.pid = childProcess.pid; const managedProcess = { info, process: childProcess, logs: [], logBuffer: this.defaultLogBuffer, }; this.processes.set(id, managedProcess); childProcess.stdout?.on('data', (data) => { this.addLog(id, 'stdout', data.toString()); }); childProcess.stderr?.on('data', (data) => { this.addLog(id, 'stderr', data.toString()); }); childProcess.on('exit', (code) => { const proc = this.processes.get(id); if (proc) { proc.info.status = code === 0 ? 'stopped' : 'failed'; proc.info.stopTime = new Date(); proc.info.exitCode = code ?? undefined; this.emit('process:exit', { id, code }); if (config.autoRestart && code !== 0) { setTimeout(() => { this.restartProcess(id).catch(err => { console.error(`Failed to restart process ${id}:`, err); }); }, 1000); } } }); childProcess.on('error', (error) => { const proc = this.processes.get(id); if (proc) { proc.info.status = 'failed'; proc.info.error = error.message; this.emit('process:error', { id, error }); } }); this.emit('process:start', info); return info; } async stopProcess(processId, timeout = 5000) { const managed = this.processes.get(processId); if (!managed) { throw new Error(`Process ${processId} not found`); } if (!managed.process || managed.info.status !== 'running') { throw new Error(`Process ${processId} is not running`); } managed.process.kill('SIGTERM'); return new Promise((resolve, reject) => { const timer = setTimeout(() => { if (managed.process && managed.info.status === 'running') { managed.process.kill('SIGKILL'); } resolve(); }, timeout); managed.process?.on('exit', () => { clearTimeout(timer); resolve(); }); managed.process?.on('error', (error) => { clearTimeout(timer); reject(error); }); }); } async restartProcess(processId) { const managed = this.processes.get(processId); if (!managed) { throw new Error(`Process ${processId} not found`); } if (managed.info.status === 'running') { await this.stopProcess(processId); } const newInfo = await this.startProcess(managed.info.config); this.processes.delete(processId); return newInfo; } getProcessLogs(processId, filter, since, lines) { const managed = this.processes.get(processId); if (!managed) { throw new Error(`Process ${processId} not found`); } let logs = managed.logs; if (since !== undefined) { const sinceDate = new Date(since); logs = logs.filter(log => log.timestamp >= sinceDate); } if (filter) { const regex = new RegExp(filter); logs = logs.filter(log => regex.test(log.data)); } if (lines !== undefined) { logs = logs.slice(-lines); } return logs; } listProcesses(status) { const processes = Array.from(this.processes.values()); if (!status || status === 'all') { return processes.map(p => p.info); } return processes .filter(p => p.info.status === status) .map(p => p.info); } sendInput(processId, input) { const managed = this.processes.get(processId); if (!managed) { throw new Error(`Process ${processId} not found`); } if (!managed.process || managed.info.status !== 'running') { throw new Error(`Process ${processId} is not running`); } if (!managed.process.stdin) { throw new Error(`Process ${processId} does not support stdin`); } managed.process.stdin.write(input + '\n'); } addLog(processId, stream, data) { const managed = this.processes.get(processId); if (!managed) return; const log = { timestamp: new Date(), stream, data: data.trim(), }; managed.logs.push(log); if (managed.logs.length > managed.logBuffer) { managed.logs = managed.logs.slice(-managed.logBuffer); this.processes.get(processId).logs = managed.logs; } this.emit('process:log', { processId, log }); if (managed.info.config.watchPatterns) { for (const pattern of managed.info.config.watchPatterns) { const regex = new RegExp(pattern); if (regex.test(data)) { this.emit('process:pattern-match', { processId, pattern, log, }); } } } } cleanup() { for (const managed of this.processes.values()) { if (managed.info.status === 'running' && managed.process) { managed.process.kill('SIGTERM'); } } this.processes.clear(); } } //# sourceMappingURL=process-manager.js.map