Skip to main content

Overview

The Shell class provides an interactive bash-like shell with full command execution, pipes, redirects, line editing, history, tab completion, and job control. It can operate in both visual mode (attached to a terminal) and headless mode (programmatic execution).

Creating a Shell

import { Shell, Kernel, createDefaultRegistry } from '@lifo-sh/core';
import { Terminal } from '@xterm/xterm';

const kernel = new Kernel();
await kernel.boot();

const registry = createDefaultRegistry();
const terminal = new Terminal();
terminal.open(document.getElementById('terminal'));

const env = kernel.getDefaultEnv();
const shell = new Shell(terminal, kernel.vfs, registry, env);
shell.start();

Constructor

constructor(
  terminal: ITerminal,
  vfs: VFS,
  registry: CommandRegistry,
  env: Record<string, string>
)
terminal
ITerminal
required
Terminal instance for input/output. Can be a visual terminal (xterm.js) or headless terminal.
vfs
VFS
required
Virtual filesystem instance
registry
CommandRegistry
required
Command registry for resolving commands
env
Record<string, string>
required
Initial environment variables

Instance Methods

execute()

Programmatic command execution with captured stdout/stderr. Used for headless mode.
async execute(
  cmd: string,
  options?: ExecuteOptions
): Promise<{ stdout: string; stderr: string; exitCode: number }>
cmd
string
required
Shell command to execute. Supports pipes, redirects, variables, etc.
options
ExecuteOptions
result
object
Example:
const result = await shell.execute('ls -la /home/user');
console.log(result.stdout);
console.log('Exit code:', result.exitCode);

// With pipes
const result = await shell.execute('ls | grep .txt | wc -l');
console.log('Text files:', result.stdout.trim());

// With redirects
await shell.execute('echo "Hello" > /tmp/output.txt');

// With streaming
await shell.execute('npm install', {
  cwd: '/home/user/project',
  onStdout: (data) => console.log('OUT:', data),
  onStderr: (data) => console.error('ERR:', data)
});

// With stdin
const result = await shell.execute('grep foo', {
  stdin: 'foo\nbar\nbaz\n'
});

start()

Start the shell in visual mode. Attaches input handlers to the terminal and displays the prompt.
start(): void
Example:
import { Terminal } from '@xterm/xterm';

const terminal = new Terminal();
terminal.open(document.getElementById('terminal'));

const shell = new Shell(terminal, vfs, registry, env);
shell.start(); // Shell is now interactive

sourceFile()

Source a shell script file, executing its contents in the current shell context.
async sourceFile(path: string): Promise<void>
path
string
required
Path to shell script file
Example:
// Create a config file
vfs.writeFile('/home/user/.bashrc', `
export PATH=/custom/bin:$PATH
alias ll='ls -la'
echo "Welcome, $USER!"
`);

// Source it
await shell.sourceFile('/home/user/.bashrc');

// Environment is now updated
console.log(shell.getEnv().PATH); // '/custom/bin:/usr/bin:/bin'

getCwd()

Get current working directory.
getCwd(): string
cwd
string
Current working directory
Example:
console.log(shell.getCwd()); // '/home/user'

setCwd()

Set current working directory.
setCwd(cwd: string): void
cwd
string
required
New working directory
Example:
shell.setCwd('/tmp');
console.log(shell.getCwd()); // '/tmp'

getEnv()

Get current environment variables.
getEnv(): Record<string, string>
env
Record<string, string>
Environment variables
Example:
const env = shell.getEnv();
console.log(env.HOME);  // '/home/user'
console.log(env.PATH);  // '/usr/bin:/bin'

getVfs()

Get VFS instance.
getVfs(): VFS
vfs
VFS
Virtual filesystem instance

getRegistry()

Get command registry.
getRegistry(): CommandRegistry
registry
CommandRegistry
Command registry instance

getJobTable()

Get job control table (for background jobs).
getJobTable(): JobTable
jobTable
JobTable
Job table instance
Example:
const jobTable = shell.getJobTable();
const jobs = jobTable.list();
for (const job of jobs) {
  console.log(`[${job.id}] ${job.status} ${job.command}`);
}

tokenize()

Tokenize a command line string (legacy, kept for backward compatibility).
tokenize(input: string): string[]
input
string
required
Command line string to tokenize
tokens
string[]
Array of tokens
Example:
const tokens = shell.tokenize('ls -la "my file.txt"');
console.log(tokens); // ['ls', '-la', 'my file.txt']

Built-in Commands

The shell provides these built-in commands:

cd

Change directory.
cd [directory]
cd ~          # Home directory
cd -          # Previous directory
cd ..         # Parent directory

pwd

Print working directory.
pwd

echo

Print arguments.
echo Hello, World!
echo $HOME
echo "Line 1\nLine 2"

export

Set environment variables.
export PATH=/custom/bin:$PATH
export NODE_ENV=production

clear

Clear the terminal screen.
clear

exit

Exit the shell.
exit

history

Show command history.
history

jobs

List background jobs.
jobs

fg

Bring background job to foreground.
fg [job_id]

bg

Resume background job.
bg [job_id]

source / .

Execute commands from a file.
source ~/.bashrc
. /etc/profile

alias

Create command aliases.
alias ll='ls -la'
alias gs='git status'
alias        # List all aliases

unalias

Remove command aliases.
unalias ll

test / [

Evaluate conditional expressions.
test -f /home/user/file.txt && echo "exists"
[ -d /tmp ] && echo "directory exists"

true

Always succeeds (exit code 0).
true

false

Always fails (exit code 1).
false

Shell Features

Pipes

Chain commands together:
ls | grep .txt | wc -l
cat file.txt | sort | uniq

Redirects

Redirect input/output:
ls > files.txt              # Redirect stdout to file
cat < input.txt             # Redirect file to stdin
command 2> errors.txt       # Redirect stderr to file
command &> output.txt       # Redirect both stdout and stderr
command >> append.txt       # Append to file

Background Jobs

Run commands in background:
sleep 10 &                  # Run in background
jobs                        # List jobs
fg 1                        # Bring job 1 to foreground

Command Chaining

Chain multiple commands:
command1 && command2        # Run command2 if command1 succeeds
command1 || command2        # Run command2 if command1 fails
command1 ; command2         # Run both regardless

Variable Expansion

Expand variables:
echo $HOME
echo ${USER}_backup
PATH=/custom:$PATH

Command Substitution

Substitute command output:
echo "Today is $(date)"
files=$(ls | wc -l)

Glob Patterns

Filename expansion:
ls *.txt
cat file?.log
rm [abc]*.tmp

Quotes

Control word splitting and expansion:
echo "$HOME expansion"      # Double quotes: expand variables
echo '$HOME literal'        # Single quotes: no expansion
echo "Line 1\nLine 2"       # Escape sequences work

History Expansion

Recall previous commands:
!!                          # Repeat last command
!10                         # Repeat command 10
!grep                       # Repeat last command starting with 'grep'

Tab Completion

Press Tab to autocomplete:
  • Command names
  • File paths
  • Directory names
  • Environment variables
Press Tab twice to show all completions.

Line Editing

Keyboard shortcuts:
  • Ctrl+A - Move to beginning of line
  • Ctrl+E - Move to end of line
  • Ctrl+U - Clear line
  • Ctrl+C - Cancel current command
  • Ctrl+D - EOF / exit
  • Arrow Up/Down - Navigate history
  • Arrow Left/Right - Move cursor
  • Home/End - Jump to start/end
  • Backspace/Delete - Delete characters

Type Definitions

interface ExecuteOptions {
  cwd?: string;
  env?: Record<string, string>;
  onStdout?: (data: string) => void;
  onStderr?: (data: string) => void;
  stdin?: string;
}

interface ITerminal {
  write(data: string): void;
  onData(handler: (data: string) => void): void;
  clear(): void;
  focus(): void;
}

Source Location

// src/shell/Shell.ts (926 lines)
export class Shell {
  constructor(
    terminal: ITerminal,
    vfs: VFS,
    registry: CommandRegistry,
    env: Record<string, string>
  );
  
  async execute(cmd: string, options?: ExecuteOptions): Promise<{
    stdout: string;
    stderr: string;
    exitCode: number;
  }>;
  
  start(): void;
  async sourceFile(path: string): Promise<void>;
  getCwd(): string;
  setCwd(cwd: string): void;
  getEnv(): Record<string, string>;
  getVfs(): VFS;
  getRegistry(): CommandRegistry;
  getJobTable(): JobTable;
  tokenize(input: string): string[];
}

Build docs developers (and LLMs) love