Local Shell
Execute shell commands locally from agent tool calls.
Overview
Local Shell allows agents to:
Run command-line tools
Execute scripts
Access system utilities
Manage files and processes
⚠️ Security Warning: Shell access should be carefully controlled and validated.
Basic Usage
Enable Shell Tool
// Add shell tool
agent.AddTool(ToolType.LocalShell);
// Agent can now execute shell commandsExecute Command
agent.AddTool("run_command", (string command) =>
{
var process = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {command}",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
return output;
});Safe Command Execution
Command Whitelist
public class SafeShellExecutor : IToolExecutor
{
public string[] SupportedTools => new[] { "run_safe_command" };
private readonly HashSet<string> allowedCommands = new()
{
"dir",
"echo",
"type",
"findstr"
};
public async UniTask<string> ExecuteAsync(ToolCall toolCall)
{
var args = JsonUtility.FromJson<CommandArgs>(toolCall.Function.Arguments);
// Extract command name
string commandName = args.command.Split(' ')[0];
// Check whitelist
if (!allowedCommands.Contains(commandName))
{
return $"Error: Command '{commandName}' is not allowed";
}
// Execute
return await ExecuteCommand(args.command);
}
async UniTask<string> ExecuteCommand(string command)
{
var process = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {command}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
return $"Error: {error}";
}
return output;
}
[System.Serializable]
class CommandArgs
{
public string command;
}
}File Operations
File Management
public class FileShellTools : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
void Start()
{
agent.AddTool("list_files", ListFiles);
agent.AddTool("read_file", ReadFile);
agent.AddTool("find_files", FindFiles);
}
string ListFiles(string directory)
{
try
{
var files = Directory.GetFiles(directory);
var dirs = Directory.GetDirectories(directory);
return $"Directories: {dirs.Length}\n" +
string.Join("\n", dirs.Select(Path.GetFileName)) +
$"\n\nFiles: {files.Length}\n" +
string.Join("\n", files.Select(Path.GetFileName));
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
string ReadFile(string filePath)
{
try
{
// Security: Only allow certain file types
string extension = Path.GetExtension(filePath).ToLower();
if (extension != ".txt" && extension != ".log" && extension != ".json")
{
return "Error: Only .txt, .log, and .json files allowed";
}
string content = File.ReadAllText(filePath);
// Limit output size
if (content.Length > 5000)
{
content = content.Substring(0, 5000) + "\n... (truncated)";
}
return content;
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
string FindFiles(string pattern, string directory = ".")
{
try
{
var files = Directory.GetFiles(directory, pattern, SearchOption.AllDirectories);
if (files.Length == 0)
{
return $"No files found matching '{pattern}'";
}
// Limit results
var results = files.Take(20);
string output = $"Found {files.Length} files (showing first 20):\n";
output += string.Join("\n", results.Select(Path.GetFileName));
return output;
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
}System Information
System Queries
agent.AddTool("get_system_info", () =>
{
return $@"Operating System: {SystemInfo.operatingSystem}
Processor: {SystemInfo.processorType}
Processor Count: {SystemInfo.processorCount}
Memory: {SystemInfo.systemMemorySize} MB
Graphics: {SystemInfo.graphicsDeviceName}
Graphics Memory: {SystemInfo.graphicsMemorySize} MB";
});
agent.AddTool("get_disk_info", () =>
{
var drives = DriveInfo.GetDrives();
var info = new StringBuilder();
foreach (var drive in drives)
{
if (drive.IsReady)
{
info.AppendLine($"{drive.Name}:");
info.AppendLine($" Total: {drive.TotalSize / 1024 / 1024 / 1024} GB");
info.AppendLine($" Free: {drive.AvailableFreeSpace / 1024 / 1024 / 1024} GB");
}
}
return info.ToString();
});Process Management
Process Control
public class ProcessTools : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
void Start()
{
agent.AddTool("list_processes", ListProcesses);
agent.AddTool("kill_process", KillProcess);
}
string ListProcesses()
{
var processes = Process.GetProcesses()
.OrderByDescending(p => p.WorkingSet64)
.Take(10);
var output = new StringBuilder();
output.AppendLine("Top 10 processes by memory:");
foreach (var process in processes)
{
try
{
long memoryMB = process.WorkingSet64 / 1024 / 1024;
output.AppendLine($"{process.ProcessName}: {memoryMB} MB");
}
catch
{
// Some processes may not be accessible
}
}
return output.ToString();
}
string KillProcess(string processName)
{
try
{
var processes = Process.GetProcessesByName(processName);
if (processes.Length == 0)
{
return $"No process found with name '{processName}'";
}
foreach (var process in processes)
{
process.Kill();
}
return $"Killed {processes.Length} process(es) named '{processName}'";
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
}Script Execution
Run Scripts
public class ScriptExecutor : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
[SerializeField] private string scriptsDirectory = "Scripts";
void Start()
{
agent.AddTool("run_script", RunScript);
}
async UniTask<string> RunScript(string scriptName)
{
string scriptPath = Path.Combine(scriptsDirectory, scriptName);
// Security: Validate script path
if (!File.Exists(scriptPath))
{
return $"Script not found: {scriptName}";
}
string extension = Path.GetExtension(scriptPath).ToLower();
string executor = extension switch
{
".bat" or ".cmd" => "cmd.exe",
".ps1" => "powershell.exe",
".py" => "python",
_ => null
};
if (executor == null)
{
return $"Unsupported script type: {extension}";
}
// Execute
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = executor,
Arguments = extension == ".ps1"
? $"-ExecutionPolicy Bypass -File \"{scriptPath}\""
: $"/c \"{scriptPath}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
return $"Script failed:\n{error}";
}
return output;
}
}Environment Variables
Access Environment
agent.AddTool("get_env", (string variableName) =>
{
string value = Environment.GetEnvironmentVariable(variableName);
if (string.IsNullOrEmpty(value))
{
return $"Environment variable '{variableName}' not found";
}
return $"{variableName} = {value}";
});
agent.AddTool("list_env", () =>
{
var variables = Environment.GetEnvironmentVariables();
var output = new StringBuilder();
foreach (DictionaryEntry entry in variables)
{
output.AppendLine($"{entry.Key} = {entry.Value}");
}
return output.ToString();
});Timeout & Cancellation
Handle Long Commands
public async UniTask<string> ExecuteWithTimeout(
string command,
int timeoutSeconds = 30)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(timeoutSeconds));
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {command}",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
try
{
process.Start();
string output = await process.StandardOutput.ReadToEndAsync()
.AsUniTask()
.AttachExternalCancellation(cts.Token);
await process.WaitForExitAsync(cts.Token);
return output;
}
catch (OperationCanceledException)
{
process.Kill();
return "Error: Command timed out";
}
}Security Best Practices
Input Sanitization
public string SanitizeCommand(string command)
{
// Remove dangerous characters
var dangerous = new[] { "&", "|", ";", ">", "<", "`", "$", "(", ")" };
foreach (var ch in dangerous)
{
if (command.Contains(ch))
{
throw new ArgumentException($"Command contains dangerous character: {ch}");
}
}
return command;
}Permission Checks
public class PermissionChecker
{
private readonly HashSet<string> adminCommands = new()
{
"format",
"del",
"rmdir",
"reg"
};
public bool RequiresAdmin(string command)
{
string cmd = command.Split(' ')[0].ToLower();
return adminCommands.Contains(cmd);
}
public bool HasAdminRights()
{
using var identity = System.Security.Principal.WindowsIdentity.GetCurrent();
var principal = new System.Security.Principal.WindowsPrincipal(identity);
return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
}Complete Example
using UnityEngine;
using Glitch9.AIDevKit.Agents;
using Cysharp.Threading.Tasks;
using System.Diagnostics;
public class SafeShellTools : MonoBehaviour
{
[SerializeField] private AgentBehaviour agent;
[SerializeField] private int commandTimeout = 30;
private readonly HashSet<string> allowedCommands = new()
{
"dir", "echo", "type", "findstr", "where"
};
void Start()
{
agent.AddTool("shell_command", ExecuteShellCommand);
agent.AddTool("list_directory", ListDirectory);
agent.AddTool("find_file", FindFile);
}
async UniTask<string> ExecuteShellCommand(string command)
{
// Validate command
if (!IsCommandAllowed(command))
{
return $"Error: Command not allowed";
}
// Sanitize input
try
{
command = SanitizeCommand(command);
}
catch (ArgumentException ex)
{
return ex.Message;
}
// Execute with timeout
return await ExecuteWithTimeout(command);
}
bool IsCommandAllowed(string command)
{
string cmdName = command.Split(' ')[0].ToLower();
return allowedCommands.Contains(cmdName);
}
string SanitizeCommand(string command)
{
var dangerous = new[] { "&", "|", ";", ">", "<", "`", "$" };
foreach (var ch in dangerous)
{
if (command.Contains(ch))
{
throw new ArgumentException($"Dangerous character: {ch}");
}
}
return command;
}
async UniTask<string> ExecuteWithTimeout(string command)
{
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(commandTimeout));
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c {command}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
try
{
process.Start();
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync(cts.Token);
string output = await outputTask;
string error = await errorTask;
if (process.ExitCode != 0)
{
return $"Error (code {process.ExitCode}):\n{error}";
}
return output;
}
catch (OperationCanceledException)
{
try { process.Kill(); } catch { }
return "Error: Command timed out";
}
}
string ListDirectory(string path = ".")
{
try
{
// Security: Validate path
if (!Directory.Exists(path))
{
return $"Directory not found: {path}";
}
var files = Directory.GetFiles(path);
var dirs = Directory.GetDirectories(path);
var output = new StringBuilder();
output.AppendLine($"Directory: {Path.GetFullPath(path)}");
output.AppendLine($"\nDirectories ({dirs.Length}):");
foreach (var dir in dirs)
{
output.AppendLine($" [DIR] {Path.GetFileName(dir)}");
}
output.AppendLine($"\nFiles ({files.Length}):");
foreach (var file in files)
{
var info = new FileInfo(file);
output.AppendLine($" {info.Length,10:N0} {info.Name}");
}
return output.ToString();
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
string FindFile(string pattern, string directory = ".")
{
try
{
var files = Directory.GetFiles(
directory,
pattern,
SearchOption.AllDirectories
);
if (files.Length == 0)
{
return $"No files found matching '{pattern}'";
}
var output = new StringBuilder();
output.AppendLine($"Found {files.Length} files:");
foreach (var file in files.Take(50))
{
output.AppendLine($" {file}");
}
if (files.Length > 50)
{
output.AppendLine($" ... and {files.Length - 50} more");
}
return output.ToString();
}
catch (Exception ex)
{
return $"Error: {ex.Message}";
}
}
}Next Steps
Last updated