initial
This commit is contained in:
182
ts/smartagent.tools.shell.ts
Normal file
182
ts/smartagent.tools.shell.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as interfaces from './smartagent.interfaces.js';
|
||||
import { BaseToolWrapper } from './smartagent.tools.base.js';
|
||||
|
||||
/**
|
||||
* Shell tool for executing commands securely
|
||||
* Wraps @push.rocks/smartshell with execSpawn for safety (no shell injection)
|
||||
*/
|
||||
export class ShellTool extends BaseToolWrapper {
|
||||
public name = 'shell';
|
||||
public description =
|
||||
'Execute shell commands securely. Uses execSpawn (shell:false) to prevent command injection.';
|
||||
|
||||
public actions: interfaces.IToolAction[] = [
|
||||
{
|
||||
name: 'execute',
|
||||
description:
|
||||
'Execute a command with arguments (secure, no shell injection possible). Command and args are passed separately.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
command: {
|
||||
type: 'string',
|
||||
description: 'The command to execute (e.g., "ls", "cat", "grep", "node")',
|
||||
},
|
||||
args: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Array of arguments (each argument is properly escaped)',
|
||||
},
|
||||
cwd: { type: 'string', description: 'Working directory for the command' },
|
||||
timeout: { type: 'number', description: 'Timeout in milliseconds' },
|
||||
env: {
|
||||
type: 'object',
|
||||
description: 'Additional environment variables (key-value pairs)',
|
||||
},
|
||||
},
|
||||
required: ['command'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'which',
|
||||
description: 'Check if a command exists and get its path',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
command: { type: 'string', description: 'Command name to look up (e.g., "node", "git")' },
|
||||
},
|
||||
required: ['command'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
private smartshell!: plugins.smartshell.Smartshell;
|
||||
|
||||
public async initialize(): Promise<void> {
|
||||
this.smartshell = new plugins.smartshell.Smartshell({
|
||||
executor: 'bash',
|
||||
});
|
||||
this.isInitialized = true;
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<void> {
|
||||
this.isInitialized = false;
|
||||
}
|
||||
|
||||
public async execute(
|
||||
action: string,
|
||||
params: Record<string, unknown>
|
||||
): Promise<interfaces.IToolExecutionResult> {
|
||||
this.validateAction(action);
|
||||
this.ensureInitialized();
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'execute': {
|
||||
const command = params.command as string;
|
||||
const args = (params.args as string[]) || [];
|
||||
|
||||
// Build options
|
||||
const options: {
|
||||
timeout?: number;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
cwd?: string;
|
||||
} = {};
|
||||
|
||||
if (params.timeout) {
|
||||
options.timeout = params.timeout as number;
|
||||
}
|
||||
|
||||
if (params.env) {
|
||||
options.env = {
|
||||
...process.env,
|
||||
...(params.env as NodeJS.ProcessEnv),
|
||||
};
|
||||
}
|
||||
|
||||
// Use execSpawn for security - no shell injection possible
|
||||
const result = await this.smartshell.execSpawn(command, args, options);
|
||||
|
||||
return {
|
||||
success: result.exitCode === 0,
|
||||
result: {
|
||||
command,
|
||||
args,
|
||||
exitCode: result.exitCode,
|
||||
stdout: result.stdout,
|
||||
stderr: result.stderr,
|
||||
signal: result.signal,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'which': {
|
||||
try {
|
||||
const commandPath = await plugins.smartshell.which(params.command as string);
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
command: params.command,
|
||||
path: commandPath,
|
||||
exists: true,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
command: params.command,
|
||||
path: null,
|
||||
exists: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
error: `Unknown action: ${action}`,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
||||
switch (action) {
|
||||
case 'execute': {
|
||||
const command = params.command as string;
|
||||
const args = (params.args as string[]) || [];
|
||||
const fullCommand = [command, ...args].join(' ');
|
||||
let summary = `Execute: ${fullCommand}`;
|
||||
|
||||
if (params.cwd) {
|
||||
summary += ` (in ${params.cwd})`;
|
||||
}
|
||||
|
||||
if (params.timeout) {
|
||||
summary += ` [timeout: ${params.timeout}ms]`;
|
||||
}
|
||||
|
||||
if (params.env && Object.keys(params.env as object).length > 0) {
|
||||
const envKeys = Object.keys(params.env as object).join(', ');
|
||||
summary += ` [env: ${envKeys}]`;
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
case 'which':
|
||||
return `Check if command "${params.command}" exists and get its path`;
|
||||
|
||||
default:
|
||||
return `Unknown action: ${action}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user