import * as plugins from './plugins.js'; export interface IShellToolOptions { /** Allowed commands whitelist. If empty, all commands are allowed. */ allowedCommands?: string[]; /** Working directory for shell execution */ cwd?: string; } export function shellTool(options?: IShellToolOptions): plugins.ToolSet { const smartshell = new plugins.smartshell.Smartshell({ executor: 'bash' }); return { run_command: plugins.tool({ description: 'Execute a shell command. Provide the full command string. stdout and stderr are returned.', inputSchema: plugins.z.object({ command: plugins.z.string().describe('The shell command to execute'), cwd: plugins.z .string() .optional() .describe('Working directory for the command'), timeout: plugins.z .number() .optional() .describe('Timeout in milliseconds'), }), execute: async ({ command, cwd, timeout, }: { command: string; cwd?: string; timeout?: number; }) => { // Validate against allowed commands whitelist if (options?.allowedCommands?.length) { const baseCommand = command.split(/\s+/)[0]; if (!options.allowedCommands.includes(baseCommand)) { return `Command "${baseCommand}" is not in the allowed commands list: ${options.allowedCommands.join(', ')}`; } } // Build full command string with cd prefix if cwd specified const effectiveCwd = cwd ?? options?.cwd; const fullCommand = effectiveCwd ? `cd ${JSON.stringify(effectiveCwd)} && ${command}` : command; const execResult = await smartshell.exec(fullCommand); const output = execResult.exitCode === 0 ? execResult.stdout : `Exit code: ${execResult.exitCode}\nstdout:\n${execResult.stdout}\nstderr:\n${execResult.stderr ?? ''}`; return plugins.truncateOutput(output).content; }, }), }; }