This commit is contained in:
2025-12-02 10:59:09 +00:00
commit c1317712a9
23 changed files with 11942 additions and 0 deletions

View 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}`;
}
}
}