High-Level Architecture
Lifo is organized as a stack of layers, each building on the one below:Layer Responsibilities
Terminal Layer
The terminal handles raw user input/output using xterm.js. It provides:- Character-by-character input processing
- ANSI escape sequence rendering
- Terminal emulation (VT100/xterm-256color)
ITerminal interface and can be either visual (attached to a DOM element) or headless (for programmatic use).
Shell Layer
The shell is a full POSIX-like command interpreter with:- Lexer: Tokenizes input, handles quotes, escapes, variables, and command substitution
- Parser: Builds an AST from tokens, supporting compound commands (if/for/while/case)
- Interpreter: Executes the AST, managing pipes, redirections, and background jobs
- Line editing: Cursor movement, history navigation, tab completion
- Job control: Background jobs (
&), job table,fg/bgbuiltins
Command Registry
The command registry is a map from command names to implementations. Commands are async functions that receive aCommandContext:
ls, cat, grep, sed, awk, tar, git, npm, node, and more.
Commands can be dynamically registered at runtime, making Lifo extensible. The
lifo package manager installs commands by writing them to /usr/share/pkg/node_modules and updating the registry.Node.js Compatibility Layer
Lifo provides shims for Node.js core modules, allowing unmodified npm packages to run:fs- Delegates to VFSpath- POSIX path utilitiesprocess- Environment variables, argv, cwd, exit codesstream- Readable/Writable streamsbuffer- Uint8Array-based Buffer implementationhttp- Virtual HTTP server using the port registrycrypto- Backed by Web Crypto APIevents- EventEmitter implementation
Virtual Filesystem (VFS)
The VFS provides a Unix-like filesystem tree:- INodes: In-memory directory tree with files and directories
- Mount system: Overlay external data sources at any path
- Persistence: Automatic save/restore via IndexedDB
- Large file support: Files ≥1MB are chunked into a content-addressable store
- Virtual providers: Dynamic filesystems (e.g.,
/proc,/dev)
Kernel Layer
The kernel coordinates the system and provides core services:- Boot sequence: Initialize VFS, restore persisted state, mount virtual providers
- Filesystem initialization: Create
/bin,/etc,/home,/tmp,/var,/usr - Port registry: Map TCP ports to JavaScript request handlers for the
httpmodule - Persistence: Debounced filesystem snapshots to IndexedDB
Browser APIs (The Real Kernel)
At the bottom of the stack, browser APIs provide the primitives:- IndexedDB: Persistent storage
- Web Crypto: Cryptographic operations
- Web Workers: (future) Process isolation
- Service Workers: (future) Network interception
- localStorage/sessionStorage: Configuration
- fetch: Network access
By treating the browser as the kernel, Lifo gains instant portability: it runs anywhere JavaScript runs (Chrome, Firefox, Safari, Node.js, Deno, Bun).
Data Flow Example: Running ls -la
Let’s trace a command through the stack:
- Terminal: User types
ls -la<Enter>, terminal captures keypresses - Shell: Line buffer is passed to the shell’s
executeLine()method - Lexer: Tokenizes input into
[{kind: Word, value: 'ls'}, {kind: Word, value: '-la'}] - Parser: Builds AST:
SimpleCommandNode { words: [['ls'], ['-la']] } - Interpreter:
- Expands words (no variables/globs here)
- Looks up
lsin the command registry - Creates
CommandContextwithargs: ['-la'],cwd,vfs, etc. - Calls
ls(ctx)
- ls command:
- Calls
ctx.vfs.readdirStat(ctx.cwd) - Formats output with ANSI colors
- Writes to
ctx.stdout
- Calls
- Interpreter: Captures exit code, returns to shell
- Shell: Prints prompt, waits for next command
Pipes and Redirections
Pipes and redirections work at the interpreter level:- Lexer/Parser: Recognizes
|and>operators - Interpreter:
- Creates
PipeChannel(in-memory buffer with reader/writer streams) - Runs
grepwithstdout= pipe writer - Runs
wcwithstdin= pipe reader,stdout= file writer forcount.txt - Waits for both commands to complete
- Creates
Sandbox: High-Level API
TheSandbox class provides a batteries-included API:
Performance Characteristics
Memory
- Filesystem tree: Stored in-memory as JavaScript objects (INodes)
- Small files (<1MB): Stored inline as
Uint8Arrayin INodes - Large files (≥1MB): Chunked into 256KB blocks in a
ContentStore(Map) - Persistence overhead: ~10-20% larger than raw data (JSON serialization)
Execution
- Command dispatch: ~0.1ms (hash map lookup)
- Pipe overhead: ~0.5ms per pipe (in-memory stream setup)
- VFS operations: ~0.01-0.1ms (in-memory tree traversal)
- Parser: ~1ms per command (tokenize + parse)
- IndexedDB save: Debounced, ~50-200ms for full filesystem
Scalability
- ✅ 10,000 files: Works well
- ⚠️ 100,000 files: Noticeable slowdown in
ls -R, persistence - ❌ 1,000,000 files: Not recommended (memory pressure)
For large-scale data processing, use the native filesystem mount feature to bypass in-memory VFS.
Extension Points
Lifo is designed for extensibility:- Custom commands:
registry.register('mycommand', async (ctx) => { ... }) - Virtual providers:
vfs.mount('/mymount', new MyProvider()) - Native filesystem mounts:
sandbox.mountNative('/project', '/host/path') - HTTP port handlers:
kernel.portRegistry.set(3000, handler) - Custom builtins:
shell.builtins.set('mybuiltin', fn)