Skip to main content

Local Environment Setup

The local adapter enables command execution on your local machine with native shell integration, runtime optimization, and comprehensive process management.

Installation​

The local adapter is included in the core package:

npm install @xec-sh/core
# or
yarn add @xec-sh/core

Basic Configuration​

Default Setup​

The local adapter works out of the box with zero configuration:

import { $ } from '@xec-sh/core';

// Executes using your system's default shell
const result = await $`echo "Hello, World!"`;
console.log(result.stdout); // "Hello, World!"

Custom Configuration​

Configure the local adapter for specific requirements:

import { createExecutionEngine } from '@xec-sh/core';

const engine = createExecutionEngine({
adapters: {
local: {
// Prefer Bun runtime if available
preferBun: true,

// Force specific implementation
forceImplementation: 'node', // or 'bun'

// Set user/group IDs (Unix only)
uid: 1000,
gid: 1000,

// Custom kill signal
killSignal: 'SIGTERM',

// Default timeout
defaultTimeout: 30000,

// Output encoding
encoding: 'utf8',

// Maximum buffer size (10MB)
maxBuffer: 10 * 1024 * 1024
}
}
});

const result = await engine.execute({ adapter: 'local' }, 'ls -la');

Shell Configuration​

Shell Detection​

The local adapter automatically detects your system shell:

// Auto-detection based on platform
const result = await $`echo $0`;
// Output: /bin/bash (Linux/Mac) or cmd.exe (Windows)

Explicit Shell Selection​

Specify which shell to use:

// Use specific shell binary
await $.with({ shell: '/bin/bash' })`echo $BASH_VERSION`;
await $.with({ shell: '/bin/zsh' })`echo $ZSH_VERSION`;
await $.with({ shell: '/usr/bin/fish' })`echo $FISH_VERSION`;

// Use boolean for system default
await $.with({ shell: true })`echo "Using system shell"`;

// Disable shell (direct execution)
await $.with({ shell: false })`/usr/bin/node --version`;

Shell Options​

Configure shell-specific behavior:

// Bash with specific options
await $.with({
shell: '/bin/bash',
env: {
BASH_ENV: '~/.bashrc',
SHELLOPTS: 'errexit:nounset'
}
})`source ~/.bash_profile && run-command`;

// PowerShell on Windows
await $.with({
shell: 'powershell.exe'
})`Get-Process | Where-Object {$_.CPU -gt 100}`;

// Command prompt on Windows
await $.with({
shell: 'cmd.exe'
})`dir /b *.txt`;

Runtime Selection​

Automatic Runtime Detection​

The adapter automatically detects and uses the best available runtime:

import { RuntimeDetector } from '@xec-sh/core';

if (RuntimeDetector.isBun()) {
console.log('Running with Bun - optimized performance');
} else {
console.log('Running with Node.js');
}

// Execution automatically uses detected runtime
await $`node --version || bun --version`;

Bun Optimization​

When Bun is available, the adapter uses optimized APIs:

// Configure to prefer Bun
const engine = createExecutionEngine({
adapters: {
local: {
preferBun: true // Use Bun.spawn when available
}
}
});

// Force Bun implementation (fails if Bun not available)
const bunEngine = createExecutionEngine({
adapters: {
local: {
forceImplementation: 'bun'
}
}
});

Process Management​

Working Directory​

Set the working directory for command execution:

// Relative path (from current working directory)
await $.with({ cwd: './src' })`ls -la`;

// Absolute path
await $.with({ cwd: '/tmp' })`pwd`; // Output: /tmp

// Chain directory changes
const projectDir = '/home/user/project';
await $.with({ cwd: projectDir })`npm install`;
await $.with({ cwd: `${projectDir}/src` })`npm test`;

Environment Variables​

Manage environment variables for processes:

// Add specific variables
await $.with({
env: {
NODE_ENV: 'production',
DEBUG: 'app:*'
}
})`node app.js`;

// Merge with existing environment
await $.with({
env: {
...process.env,
CUSTOM_VAR: 'value'
}
})`./script.sh`;

// Clear environment (Unix only)
await $.with({
env: {}
})`env`; // Shows empty environment

Process Signals​

Handle process signals and termination:

// Custom timeout with specific signal
const proc = $.with({
timeout: 5000,
killSignal: 'SIGKILL'
})`sleep 10`;

try {
await proc;
} catch (error) {
console.log('Process killed after timeout');
}

// Manual signal handling
const longRunning = $`tail -f /var/log/system.log`;

// Kill after 10 seconds
setTimeout(() => longRunning.kill('SIGTERM'), 10000);

// Handle graceful shutdown
process.on('SIGINT', async () => {
longRunning.kill();
await longRunning.catch(() => {}); // Ignore kill error
process.exit(0);
});

Input/Output Streams​

Standard Input​

Provide input to processes:

// String input
await $.with({ stdin: 'Hello, World!' })`cat`;

// Buffer input
const buffer = Buffer.from('Binary data', 'utf8');
await $.with({ stdin: buffer })`wc -c`;

// Stream input
import { createReadStream } from 'fs';
const stream = createReadStream('input.txt');
await $.with({ stdin: stream })`grep "pattern"`;

// Pipe from another command
const output = await $`echo "test"`;
await $.with({ stdin: output.stdout })`tr '[:lower:]' '[:upper:]'`;

Output Handling​

Control how output is captured:

// Default - capture stdout and stderr
const result = await $`ls -la`;
console.log(result.stdout);
console.log(result.stderr);

// Inherit parent's stdio
await $.with({
stdout: 'inherit',
stderr: 'inherit'
})`npm install`; // Output goes directly to console

// Ignore output
await $.with({
stdout: 'ignore',
stderr: 'ignore'
})`silent-command`;

// Pipe to custom streams
import { createWriteStream } from 'fs';
const logFile = createWriteStream('output.log');
await $.with({
stdout: logFile,
stderr: logFile
})`verbose-command`;

Error Handling​

Exit Code Handling​

Configure how non-zero exit codes are handled:

// Default - throw on non-zero exit
try {
await $`exit 1`;
} catch (error) {
console.log('Exit code:', error.exitCode); // 1
console.log('Command:', error.command); // "exit 1"
}

// Don't throw on non-zero exit
const result = await $.with({ throwOnNonZeroExit: false })`exit 42`;
console.log(result.exitCode); // 42
console.log(result.failed); // true

// Check specific exit codes
const { exitCode } = await $.with({ throwOnNonZeroExit: false })`grep "pattern" file.txt`;
if (exitCode === 0) {
console.log('Pattern found');
} else if (exitCode === 1) {
console.log('Pattern not found');
} else {
console.log('Error occurred');
}

Timeout Handling​

Set execution timeouts:

// Timeout with error
try {
await $.with({ timeout: 1000 })`sleep 5`;
} catch (error) {
if (error.name === 'TimeoutError') {
console.log('Command timed out after', error.timeout, 'ms');
}
}

// Timeout with custom signal
await $.with({
timeout: 5000,
killSignal: 'SIGKILL' // Force kill on timeout
})`potentially-hanging-command`;

// No timeout (default)
await $.with({ timeout: 0 })`long-running-process`;

Error Context​

Access detailed error information:

try {
await $.with({ cwd: '/nonexistent' })`ls`;
} catch (error) {
console.log('Error code:', error.code); // 'ENOENT'
console.log('Exit code:', error.exitCode); // null (spawn failed)
console.log('Signal:', error.signal); // null
console.log('Command:', error.command); // 'ls'
console.log('Working dir:', error.cwd); // '/nonexistent'
console.log('Stack trace:', error.stack);
}

Performance Optimization​

Buffer Management​

Control memory usage for large outputs:

// Increase buffer for large outputs
const result = await $.with({
maxBuffer: 100 * 1024 * 1024 // 100MB
})`cat large-file.txt`;

// Stream processing for unlimited output
const proc = $`find / -type f`;
proc.stdout.pipe(process.stdout);
await proc;

Parallel Execution​

Run multiple commands efficiently:

// Parallel execution
const [result1, result2, result3] = await Promise.all([
$`command1`,
$`command2`,
$`command3`
]);

// Sequential with shared state
const tempDir = await $`mktemp -d`;
await $.with({ cwd: tempDir.stdout.trim() })`touch file1.txt`;
await $.with({ cwd: tempDir.stdout.trim() })`touch file2.txt`;

Process Reuse​

Optimize repeated executions:

// Create reusable configuration
const nodeExec = (script) => $.with({
shell: false,
timeout: 10000
})`node -e ${script}`;

// Reuse configuration
await nodeExec('console.log("Test 1")');
await nodeExec('console.log("Test 2")');
await nodeExec('console.log("Test 3")');

Platform-Specific Features​

Unix/Linux/macOS​

Unix-specific features and configurations:

// Set user and group IDs
await $.with({
uid: 1000,
gid: 1000
})`whoami`;

// Use shell features
await $.with({ shell: '/bin/bash' })`
set -euo pipefail
source ~/.bashrc
alias ll='ls -la'
ll /tmp
`;

// Signal handling
const proc = $`sleep 100`;
proc.kill('SIGUSR1'); // Send custom signal

Windows​

Windows-specific features and configurations:

// PowerShell execution
await $.with({ shell: 'powershell.exe' })`
Get-Service |
Where-Object {$_.Status -eq "Running"} |
Select-Object -First 10
`;

// Command prompt
await $.with({ shell: 'cmd.exe' })`
echo off
set MY_VAR=value
echo %MY_VAR%
`;

// Windows-specific paths
await $.with({
cwd: 'C:\\Program Files'
})`dir /b`;

Security Considerations​

Command Injection Prevention​

Safely handle user input:

// DON'T do this - vulnerable to injection
const userInput = '; rm -rf /';
// await $`echo ${userInput}`; // DANGEROUS!

// DO this - use arguments array
await $.with({ shell: false })`echo ${userInput}`; // Safe - treated as single argument

// Or escape arguments
import { escapeArg } from '@xec-sh/core';
await $`echo ${escapeArg(userInput)}`; // Safe - properly escaped

Privilege Management​

Control process privileges:

// Drop privileges (Unix only)
await $.with({
uid: 65534, // nobody user
gid: 65534 // nogroup
})`whoami`; // Output: nobody

// Avoid running as root when possible
if (process.getuid() === 0) {
console.warn('Running as root - consider using lower privileges');
}

Resource Limits​

Prevent resource exhaustion:

// Limit execution time
await $.with({ timeout: 5000 })`potentially-infinite-loop`;

// Limit output buffer
await $.with({ maxBuffer: 1024 * 1024 })`cat /dev/random`;

// Limit concurrent processes
const semaphore = new Semaphore(5); // Max 5 concurrent
await Promise.all(
commands.map(cmd =>
semaphore.acquire().then(() => $`${cmd}`)
)
);

Troubleshooting​

Common Issues​

Command Not Found​

// Problem: Command not in PATH
try {
await $`custom-command`;
} catch (error) {
if (error.code === 'ENOENT') {
// Solution 1: Use full path
await $`/usr/local/bin/custom-command`;

// Solution 2: Update PATH
await $.with({
env: {
PATH: `${process.env.PATH}:/usr/local/bin`
}
})`custom-command`;
}
}

Working Directory Issues​

// Problem: Directory doesn't exist
try {
await $.with({ cwd: '/nonexistent' })`ls`;
} catch (error) {
// Solution: Create directory first
await $`mkdir -p /nonexistent`;
await $.with({ cwd: '/nonexistent' })`ls`;
}

Permission Denied​

// Problem: Insufficient permissions
try {
await $`cat /etc/shadow`;
} catch (error) {
if (error.stderr.includes('Permission denied')) {
// Solution: Use appropriate privileges
console.log('Requires elevated privileges');
}
}

Next Steps​