UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

161 lines 8.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecureTmuxWrapper = void 0; const commandSanitizer_1 = require("./commandSanitizer"); const child_process_1 = require("child_process"); const util_1 = require("util"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class SecureTmuxWrapper { static async executeSecurely(command, args, options = {}) { const startTime = new Date().toISOString(); try { const sanitized = commandSanitizer_1.CommandSanitizer.prepareTmuxExecution(command, args); const fullCommand = this.buildCommand(sanitized); const execOptions = { timeout: options.timeout || this.DEFAULT_TIMEOUT, maxBuffer: options.maxBuffer || this.DEFAULT_MAX_BUFFER, cwd: options.cwd, env: { ...process.env, ...options.env } }; const { stdout, stderr } = await execAsync(fullCommand, execOptions); return { success: true, stdout: stdout || '', stderr: stderr || '', command: fullCommand, timestamp: startTime }; } catch (error) { return { success: false, stdout: error.stdout || '', stderr: error.stderr || error.message || 'Unknown error', command: `${this.TMUX_BINARY} ${command} ${args.join(' ')}`, timestamp: startTime }; } } static async sendKeysSecurely(sessionName, windowIndex, keys, options = {}) { const target = commandSanitizer_1.CommandSanitizer.createSafeTmuxTarget(sessionName, windowIndex); const sanitizedKeys = this.sanitizeKeys(keys); return this.executeSecurely('send-keys', ['-t', target, sanitizedKeys], options); } static async sendCommandSecurely(sessionName, windowIndex, command, options = {}) { const target = commandSanitizer_1.CommandSanitizer.createSafeTmuxTarget(sessionName, windowIndex); const sanitizedCommand = this.sanitizeKeys(command); return this.executeSecurely('send-keys', ['-t', target, sanitizedCommand, 'Enter'], options); } static async createSessionSecurely(sessionName, initialDirectory, options = {}) { const sessionValidation = commandSanitizer_1.CommandSanitizer.validateSessionName(sessionName); if (!sessionValidation.isValid) { throw new Error(`Invalid session name: ${sessionValidation.errors.join(', ')}`); } const args = ['-d', '-s', sessionName]; if (initialDirectory) { const pathValidation = commandSanitizer_1.CommandSanitizer.sanitizeFilePath(initialDirectory); if (!pathValidation.isValid) { throw new Error(`Invalid directory path: ${pathValidation.errors.join(', ')}`); } args.push('-c', pathValidation.path); } return this.executeSecurely('new-session', args, options); } static async createWindowSecurely(sessionName, windowName, initialDirectory, options = {}) { const sessionValidation = commandSanitizer_1.CommandSanitizer.validateSessionName(sessionName); if (!sessionValidation.isValid) { throw new Error(`Invalid session name: ${sessionValidation.errors.join(', ')}`); } const windowValidation = commandSanitizer_1.CommandSanitizer.validateProjectName(windowName); if (!windowValidation.isValid) { throw new Error(`Invalid window name: ${windowValidation.errors.join(', ')}`); } const args = ['-t', sessionName, '-n', windowName]; if (initialDirectory) { const pathValidation = commandSanitizer_1.CommandSanitizer.sanitizeFilePath(initialDirectory); if (!pathValidation.isValid) { throw new Error(`Invalid directory path: ${pathValidation.errors.join(', ')}`); } args.push('-c', pathValidation.path); } return this.executeSecurely('new-window', args, options); } static async capturePaneSecurely(sessionName, windowIndex, startLine, endLine, options = {}) { const target = commandSanitizer_1.CommandSanitizer.createSafeTmuxTarget(sessionName, windowIndex); const args = ['-t', target, '-p']; if (startLine !== undefined && endLine !== undefined) { if (!Number.isInteger(startLine) || !Number.isInteger(endLine) || startLine < 0 || endLine < 0 || startLine > endLine) { throw new Error('Invalid line range: start and end must be non-negative integers with start <= end'); } args.push('-S', startLine.toString(), '-E', endLine.toString()); } return this.executeSecurely('capture-pane', args, options); } static async listSessionsSecurely(options = {}) { return this.executeSecurely('list-sessions', ['-F', '#{session_name}:#{session_windows}:#{session_created}'], options); } static async listWindowsSecurely(sessionName, options = {}) { const sessionValidation = commandSanitizer_1.CommandSanitizer.validateSessionName(sessionName); if (!sessionValidation.isValid) { throw new Error(`Invalid session name: ${sessionValidation.errors.join(', ')}`); } return this.executeSecurely('list-windows', ['-t', sessionName, '-F', '#{window_index}:#{window_name}:#{window_active}'], options); } static async killSessionSecurely(sessionName, options = {}) { const sessionValidation = commandSanitizer_1.CommandSanitizer.validateSessionName(sessionName); if (!sessionValidation.isValid) { throw new Error(`Invalid session name: ${sessionValidation.errors.join(', ')}`); } return this.executeSecurely('kill-session', ['-t', sessionName], options); } static async hasSessionSecurely(sessionName, options = {}) { const sessionValidation = commandSanitizer_1.CommandSanitizer.validateSessionName(sessionName); if (!sessionValidation.isValid) { throw new Error(`Invalid session name: ${sessionValidation.errors.join(', ')}`); } const result = await this.executeSecurely('has-session', ['-t', sessionName], options); return result.success; } static async killWindowSecurely(sessionName, windowIndex, options = {}) { const target = commandSanitizer_1.CommandSanitizer.createSafeTmuxTarget(sessionName, windowIndex); return this.executeSecurely('kill-window', ['-t', target], options); } static buildCommand(sanitized) { const escapedArgs = sanitized.args.map(arg => this.escapeShellArgument(arg)); return `${this.TMUX_BINARY} ${sanitized.command} ${escapedArgs.join(' ')}`; } static sanitizeKeys(keys) { if (!keys || typeof keys !== 'string') { throw new Error('Keys must be a non-empty string'); } let sanitized = keys .replace(/[;&|`$(){}]/g, '') .replace(/\\/g, '\\\\') .trim(); if (sanitized.length === 0) { throw new Error('Keys cannot be empty after sanitization'); } if (sanitized.length > 10000) { throw new Error('Keys exceed maximum length of 10000 characters'); } return sanitized; } static escapeShellArgument(arg) { return `'${arg.replace(/'/g, "'\"'\"'")}'`; } static async validateTmuxAvailable() { try { const result = await execAsync(`${this.TMUX_BINARY} -V`, { timeout: 5000 }); return result.stdout.includes('tmux'); } catch (error) { return false; } } } exports.SecureTmuxWrapper = SecureTmuxWrapper; SecureTmuxWrapper.DEFAULT_TIMEOUT = 30000; SecureTmuxWrapper.DEFAULT_MAX_BUFFER = 1024 * 1024; SecureTmuxWrapper.TMUX_BINARY = 'tmux'; //# sourceMappingURL=secureTmuxWrapper.js.map