local-agent
Version:
A CLI agentic system for orchestrating tools and memory with per-folder scoping
253 lines • 9.41 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.isTerminalCommand = isTerminalCommand;
exports.executeTerminalCommand = executeTerminalCommand;
exports.executeInteractiveCommand = executeInteractiveCommand;
exports.getCurrentWorkingDirectory = getCurrentWorkingDirectory;
/**
* @fileoverview
* Provides terminal command detection and execution for the agent CLI.
* Allows users to run standard terminal commands directly through the agent interface.
*/
const child_process_1 = require("child_process");
const util_1 = require("util");
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const fs_1 = require("fs");
// Create a promise-based version of exec
const execPromise = (0, util_1.promisify)(child_process_1.exec);
// Common terminal commands that should be executed directly
const TERMINAL_COMMANDS = [
// File system commands
'ls', 'dir', 'cd', 'pwd', 'mkdir', 'rmdir', 'touch', 'rm', 'cp', 'mv', 'cat', 'less', 'more',
'head', 'tail', 'find', 'grep', 'awk', 'sed', 'diff', 'chmod', 'chown',
// Navigation commands
'cd', 'pushd', 'popd', 'dirs',
// Package managers
'npm', 'yarn', 'pnpm', 'bun', 'pip', 'pip3', 'gem', 'bundle', 'cargo', 'go',
// Git commands
'git', 'svn', 'hg',
// Process management
'ps', 'top', 'htop', 'kill', 'pkill',
// Network commands
'ping', 'curl', 'wget', 'ssh', 'netstat', 'ifconfig', 'ip',
// System commands
'echo', 'date', 'whoami', 'hostname', 'uname', 'df', 'du', 'free',
// Archiving and compression
'tar', 'gzip', 'gunzip', 'zip', 'unzip',
// Shell built-ins and other common commands
'alias', 'bg', 'fg', 'jobs', 'history', 'clear', 'env', 'export', 'source',
'sudo', 'su', 'man', 'info', 'which', 'whereis', 'type',
// Text editors
'vi', 'vim', 'nano', 'emacs', 'code'
];
// Known shell built-ins that require special handling
const SHELL_BUILTINS = ['cd', 'export', 'source', 'alias', 'unalias', 'set', 'unset'];
// Keep track of the current working directory
let currentWorkingDirectory = process.cwd();
/**
* Determines if a user input is a terminal command.
* Checks if the input starts with a known terminal command or appears to be a terminal command pattern.
*
* @param {string} input - The user input to check.
* @returns {boolean} True if the input appears to be a terminal command.
*/
function isTerminalCommand(input) {
const trimmedInput = input.trim();
// Skip empty inputs
if (!trimmedInput)
return false;
// Check for command with parameters
const firstWord = trimmedInput.split(' ')[0];
// Direct match with known command
if (TERMINAL_COMMANDS.includes(firstWord)) {
return true;
}
// Check for relative/absolute paths (common in terminal usage)
if (trimmedInput.startsWith('./') ||
trimmedInput.startsWith('../') ||
trimmedInput.startsWith('/') ||
/^[a-zA-Z]:\\/.test(trimmedInput)) {
return true;
}
// Check for environment variables assignment (VAR=value)
if (/^[a-zA-Z_][a-zA-Z0-9_]*=/.test(trimmedInput)) {
return true;
}
// Check for pipes and redirections (common in terminal usage)
if (trimmedInput.includes('|') ||
trimmedInput.includes('>') ||
trimmedInput.includes('<')) {
return true;
}
// Check for executable in PATH
if (isExecutableInPath(firstWord)) {
return true;
}
return false;
}
/**
* Checks if a command is an executable in the PATH.
*
* @param {string} command - The command to check.
* @returns {boolean} True if the command is found in PATH.
*/
function isExecutableInPath(command) {
// Skip obviously non-executable strings
if (!command || command.includes(' ') || command.length < 2) {
return false;
}
// Get PATH environment variable
const envPath = process.env.PATH || '';
const pathSeparator = os.platform() === 'win32' ? ';' : ':';
const pathExtensions = os.platform() === 'win32'
? (process.env.PATHEXT || '.exe;.cmd;.bat').split(';')
: [''];
const paths = envPath.split(pathSeparator);
// Check if the command exists in any of the PATH directories
for (const directory of paths) {
for (const ext of pathExtensions) {
const fullPath = path.join(directory, command + ext);
if ((0, fs_1.existsSync)(fullPath)) {
return true;
}
}
}
return false;
}
/**
* Handles the special case of changing directory (cd command).
* Updates the current working directory for the process.
*
* @param {string} command - The full cd command with arguments.
* @returns {string} Message about the directory change.
*/
function handleCdCommand(command) {
const parts = command.trim().split(' ');
// Extract the target directory
let targetDir = parts.slice(1).join(' ').trim();
// Handle special cases
if (!targetDir || targetDir === '~') {
targetDir = os.homedir();
}
else if (targetDir.startsWith('~')) {
targetDir = path.join(os.homedir(), targetDir.substring(1));
}
else if (!path.isAbsolute(targetDir)) {
targetDir = path.resolve(currentWorkingDirectory, targetDir);
}
// Check if directory exists
if (!(0, fs_1.existsSync)(targetDir)) {
return `cd: ${parts[1]}: No such file or directory`;
}
try {
// Update the current working directory
process.chdir(targetDir);
currentWorkingDirectory = process.cwd();
return `Changed directory to ${currentWorkingDirectory}`;
}
catch (error) {
return `cd: ${error instanceof Error ? error.message : String(error)}`;
}
}
/**
* Executes a terminal command and returns the result.
* Handles special cases like 'cd' that change the environment state.
*
* @param {string} command - The command to execute.
* @returns {Promise<{stdout: string, stderr: string}>} The command execution result.
*/
async function executeTerminalCommand(command) {
// Trim the command
const trimmedCommand = command.trim();
// Handle empty commands
if (!trimmedCommand) {
return { stdout: '', stderr: '' };
}
// Special handling for cd command
if (trimmedCommand.startsWith('cd ') || trimmedCommand === 'cd') {
const result = handleCdCommand(trimmedCommand);
return { stdout: result, stderr: '' };
}
try {
// Execute the command in the current working directory
const { stdout, stderr } = await execPromise(trimmedCommand, { cwd: currentWorkingDirectory });
return { stdout, stderr };
}
catch (error) {
// Handle command execution errors
if (error instanceof Error) {
const execError = error;
return {
stdout: execError.stdout || '',
stderr: execError.stderr || error.message
};
}
return {
stdout: '',
stderr: error ? String(error) : 'Unknown error occurred'
};
}
}
/**
* Executes a terminal command interactively, attaching stdin/stdout/stderr.
* Useful for commands that require user interaction.
*
* @param {string} command - The command to execute.
* @param {string[]} args - Arguments for the command.
* @returns {Promise<number>} Exit code of the process.
*/
function executeInteractiveCommand(command, args = []) {
return new Promise((resolve) => {
const childProcess = (0, child_process_1.spawn)(command, args, {
cwd: currentWorkingDirectory,
stdio: 'inherit', // Attach to parent process I/O
shell: true
});
childProcess.on('close', (code) => {
resolve(code || 0);
});
});
}
/**
* Returns the current working directory.
*
* @returns {string} The current working directory.
*/
function getCurrentWorkingDirectory() {
return currentWorkingDirectory;
}
//# sourceMappingURL=terminal-commands.js.map