import { tool } from '@lmstudio/sdk';
import { z } from 'zod';
import { execCommand, runProcess, normalizeLineEndings } from '../executor';
import { logger } from '../logger';
export async function registerShellTools(): Promise<any[]> {
const tools: any[] = [];
tools.push(tool({
name: 'shell_exec',
description: 'Execute a shell command and return the output.',
parameters: {
command: z.string().describe('The shell command to execute'),
timeout_ms: z.number().optional().describe('Timeout in milliseconds (default: 30000)'),
cwd: z.string().optional().describe('Working directory (optional)'),
shell_path: z.string().optional().describe('Override shell path (optional)'),
windows_shell: z.enum(['powershell', 'cmd']).optional().describe('Windows shell: powershell or cmd'),
env: z.record(z.string()).optional().describe('Environment variables'),
stdin: z.string().optional().describe('Stdin input'),
max_output_bytes: z.number().optional().describe('Max output size in bytes'),
},
implementation: async (params: any) => {
try {
const result = await execCommand(params.command, {
timeoutMs: params.timeout_ms ?? 30000,
cwd: params.cwd,
shellPath: params.shell_path,
windowsShell: params.windows_shell,
env: params.env,
stdin: params.stdin,
maxOutputBytes: params.max_output_bytes,
});
// Normalize line endings for consistent output across platforms
const stdout = normalizeLineEndings(result.stdout);
const stderr = normalizeLineEndings(result.stderr);
return {
success: true,
stdout,
stderr,
exitCode: result.exitCode,
timedOut: result.timedOut,
shell: result.shell,
platform: result.platform,
// Combined output for convenience — stdout + stderr together
output: stdout + (stderr ? '\n' + stderr : ''),
};
} catch (err: any) {
logger.error('shell_exec failed', { error: err.message, command: params.command });
return {
success: false,
stdout: '',
stderr: err.message,
exitCode: err.exitCode ?? -1,
timedOut: err.timedOut ?? false,
shell: 'unknown',
platform: process.platform,
output: '',
error: err.message,
};
}
},
}));
tools.push(tool({
name: 'run_process',
description: 'Spawn a process with arguments.',
parameters: {
command: z.string().describe('The executable to run'),
args: z.array(z.string()).optional().describe('Arguments to pass'),
timeout_ms: z.number().optional().describe('Timeout in milliseconds'),
cwd: z.string().optional().describe('Working directory'),
env: z.record(z.string()).optional().describe('Environment variables'),
stdin: z.string().optional().describe('Stdin input'),
max_output_bytes: z.number().optional().describe('Max output size in bytes'),
},
implementation: async (params: any) => {
try {
const result = await runProcess(
params.command,
params.args ?? [],
{
timeoutMs: params.timeout_ms ?? 30000,
cwd: params.cwd,
env: params.env,
stdin: params.stdin,
maxOutputBytes: params.max_output_bytes,
},
);
// Normalize line endings
const stdout = normalizeLineEndings(result.stdout);
const stderr = normalizeLineEndings(result.stderr);
return {
success: true,
stdout,
stderr,
exitCode: result.exitCode,
timedOut: result.timedOut,
platform: result.platform,
// Combined output for convenience
output: stdout + (stderr ? '\n' + stderr : ''),
};
} catch (err: any) {
logger.error('run_process failed', { error: err.message, command: params.command });
return {
success: false,
stdout: '',
stderr: err.message,
exitCode: err.exitCode ?? -1,
timedOut: err.timedOut ?? false,
platform: process.platform,
output: '',
error: err.message,
};
}
},
}));
return tools;
}