Guest User

Untitled

a guest
Jul 5th, 2026
76
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // tools/bash.mjs — Shell execution tool (foreground mode only)
  2. //
  3. // Runs a shell command via `child_process.spawn()` and returns
  4. // the combined stdout+stderr output as a string.
  5. // Optional `timeout` (in ms) kills the process after the delay.
  6.  
  7. const MAX_OUTPUT = 20000;
  8. import { spawn } from 'child_process';
  9.  
  10. export default {
  11.     name: 'bash',
  12.     description: 'Run a shell command and return combined stdout+stderr. Optional timeout in ms.',
  13.     parameters: {
  14.         type: 'object',
  15.         properties: {
  16.             command: {
  17.                 type: 'string',
  18.                 description: 'The shell command to execute'
  19.             },
  20.             timeout: {
  21.                 type: 'string',
  22.                 description: 'Optional timeout in milliseconds. Kills the process if exceeded.'
  23.             }
  24.         },
  25.         required: ['command']
  26.     },
  27.  
  28.     /**
  29.      * Execute a shell command and return its output.
  30.      *
  31.      * @param {object} args - Tool arguments containing `command` and optional `timeout`
  32.      * @returns {Promise<string>} Combined stdout+stderr, or an error message
  33.      */
  34.     handler: async (args) => {
  35.         const { command, timeout } = args;
  36.  
  37.         // Guard: reject calls without a command
  38.         if (!command) return '[missing command]';
  39.  
  40.         // Spawn `bash -c "<command>"` with stdout and stderr piped to us.
  41.         // `detached: true` creates a new process group so we can kill
  42.         // the entire subtree (e.g., a pipeline) with one signal.
  43.         return new Promise((resolve) => {
  44.             const child = spawn('bash', ['-c', command], {
  45.                 stdio: ['ignore', 'pipe', 'pipe'],
  46.                 detached: true
  47.             });
  48.  
  49.             // Accumulate stdout and stderr into a single string.
  50.             // Buffer values auto-coerce to string via `+=`.
  51.             let output = '';
  52.             for (const stream of [child.stdout, child.stderr]) {
  53.                 stream.on('data', (chunk) => { output += chunk; });
  54.             }
  55.  
  56.             // Helper to kill the entire process group (negative pid).
  57.             // Wrapped in try/catch because the group may already be gone.
  58.             const killGroup = () => {
  59.                 try { process.kill(-child.pid); }
  60.                 catch {}
  61.             };
  62.  
  63.             // Wire SIGINT so Ctrl+C in the terminal kills the child process group.
  64.             process.on('SIGINT', killGroup);
  65.  
  66.             // Optional timeout: kill the process group if it runs too long.
  67.             let timer = null;
  68.             const timeoutMs = timeout ? parseInt(timeout, 10) : null;
  69.             if (timeoutMs && !isNaN(timeoutMs)) {
  70.                 timer = setTimeout(killGroup, timeoutMs);
  71.             }
  72.  
  73.             // Handle spawn errors (e.g., bash not found, permission denied).
  74.             child.on('error', (err) => {
  75.                 process.off('SIGINT', killGroup);
  76.                 if (timer) clearTimeout(timer);
  77.                 resolve(`[error] ${err.message}`);
  78.             });
  79.  
  80.             // On normal exit: detach SIGINT handler, clean up timer, return output.
  81.             child.on('exit', (code, signal) => {
  82.                 process.off('SIGINT', killGroup);
  83.                 if (timer) clearTimeout(timer);
  84.                 let trimmed = output.trim();
  85.                 const truncated = trimmed.length > MAX_OUTPUT;
  86.                 if (truncated) {
  87.                     trimmed = trimmed.slice(0, MAX_OUTPUT);
  88.                 }
  89.                 const prefix = signal ? `[signal ${signal}]` : `[exit ${code ?? 'unknown'}]`;
  90.                 if (!trimmed) {
  91.                     resolve(`${prefix}, no output`);
  92.                 } else {
  93.                     const suffix = truncated
  94.                         ? `\n[output truncated — ${output.length - MAX_OUTPUT} chars removed]`
  95.                         : '';
  96.                     resolve(`${prefix}\n${trimmed}${suffix}`);
  97.                 }
  98.             });
  99.         });
  100.     }
  101. };
  102.  
Advertisement
Add Comment
Please, Sign In to add comment