Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // tools/bash.mjs — Shell execution tool (foreground mode only)
- //
- // Runs a shell command via `child_process.spawn()` and returns
- // the combined stdout+stderr output as a string.
- // Optional `timeout` (in ms) kills the process after the delay.
- const MAX_OUTPUT = 20000;
- import { spawn } from 'child_process';
- export default {
- name: 'bash',
- description: 'Run a shell command and return combined stdout+stderr. Optional timeout in ms.',
- parameters: {
- type: 'object',
- properties: {
- command: {
- type: 'string',
- description: 'The shell command to execute'
- },
- timeout: {
- type: 'string',
- description: 'Optional timeout in milliseconds. Kills the process if exceeded.'
- }
- },
- required: ['command']
- },
- /**
- * Execute a shell command and return its output.
- *
- * @param {object} args - Tool arguments containing `command` and optional `timeout`
- * @returns {Promise<string>} Combined stdout+stderr, or an error message
- */
- handler: async (args) => {
- const { command, timeout } = args;
- // Guard: reject calls without a command
- if (!command) return '[missing command]';
- // Spawn `bash -c "<command>"` with stdout and stderr piped to us.
- // `detached: true` creates a new process group so we can kill
- // the entire subtree (e.g., a pipeline) with one signal.
- return new Promise((resolve) => {
- const child = spawn('bash', ['-c', command], {
- stdio: ['ignore', 'pipe', 'pipe'],
- detached: true
- });
- // Accumulate stdout and stderr into a single string.
- // Buffer values auto-coerce to string via `+=`.
- let output = '';
- for (const stream of [child.stdout, child.stderr]) {
- stream.on('data', (chunk) => { output += chunk; });
- }
- // Helper to kill the entire process group (negative pid).
- // Wrapped in try/catch because the group may already be gone.
- const killGroup = () => {
- try { process.kill(-child.pid); }
- catch {}
- };
- // Wire SIGINT so Ctrl+C in the terminal kills the child process group.
- process.on('SIGINT', killGroup);
- // Optional timeout: kill the process group if it runs too long.
- let timer = null;
- const timeoutMs = timeout ? parseInt(timeout, 10) : null;
- if (timeoutMs && !isNaN(timeoutMs)) {
- timer = setTimeout(killGroup, timeoutMs);
- }
- // Handle spawn errors (e.g., bash not found, permission denied).
- child.on('error', (err) => {
- process.off('SIGINT', killGroup);
- if (timer) clearTimeout(timer);
- resolve(`[error] ${err.message}`);
- });
- // On normal exit: detach SIGINT handler, clean up timer, return output.
- child.on('exit', (code, signal) => {
- process.off('SIGINT', killGroup);
- if (timer) clearTimeout(timer);
- let trimmed = output.trim();
- const truncated = trimmed.length > MAX_OUTPUT;
- if (truncated) {
- trimmed = trimmed.slice(0, MAX_OUTPUT);
- }
- const prefix = signal ? `[signal ${signal}]` : `[exit ${code ?? 'unknown'}]`;
- if (!trimmed) {
- resolve(`${prefix}, no output`);
- } else {
- const suffix = truncated
- ? `\n[output truncated — ${output.length - MAX_OUTPUT} chars removed]`
- : '';
- resolve(`${prefix}\n${trimmed}${suffix}`);
- }
- });
- });
- }
- };
Advertisement
Add Comment
Please, Sign In to add comment