Skip to content

hypexr/unix-shell-js

Repository files navigation

Unix Shell JS

Tests Build npm version

A browser-based Unix/Linux command emulator with vi editor support.

Try the Live Demo

Features

  • Common Unix commands (ls, cd, cat, mkdir, rm, etc.)
  • Add and modify commands
  • Customizable filesystem
  • Vi/Vim editor with modal editing
  • Tab completion
  • Command history
  • Pipe and redirection support
  • Wildcard expansion (* and ?)
  • Custom command support

Installation

From npm

npm install unix-shell-js

Local Development

If you're working locally and want to use this library in another project:

cd ../unix-shell-js
npm install
npm run build
npm link

cd ../your-project
npm link unix-shell-js

Development

Build Commands

  • npm run build - Compile TypeScript to JavaScript (outputs to dist/) and copy to demo
  • npm run copy-to-demo - Copy built files to docs/ directory for GitHub Pages demo
  • npm run watch - Watch mode for development (auto-recompiles on changes)
  • npm run prepublishOnly - Automatically runs before publishing to npm

Testing

  • npm test - Run all tests
  • npm run test:watch - Run tests in watch mode
  • npm run test:coverage - Run tests with coverage report

Project Structure

  • src/ - TypeScript source files
    • index.ts - Main Unix Shell implementation
    • vi-editor.ts - Vi/Vim editor
    • example-files.ts - Example filesystem generator
  • dist/ - Compiled JavaScript files (generated by TypeScript compiler)
    • Includes .js, .d.ts (type definitions), and .map (source maps) files

Usage

TypeScript

The library is written in TypeScript and includes full type definitions:

import { UnixShell } from 'unix-shell-js';
import { createExampleFiles } from 'unix-shell-js/dist/example-files';

const shell = new UnixShell({
    username: 'user',
    fileSystem: createExampleFiles('user'),
    persistence: {
        enabled: true,
        prefix: 'myapp'
    }
});

const output: string = shell.execute('ls -la');
console.log(output);

Type definitions included:

  • UnixShellOptions - Constructor options
  • FileSystem - Filesystem structure types
  • PersistenceOptions - Persistence configuration
  • CommandHandler - Custom command function signature
  • CompletionResult - Tab completion result type

JavaScript (Browser)

Include the library files in your HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Terminal</title>
</head>
<body>
    <script src="node_modules/unix-shell-js/dist/index.js"></script>
    <script src="node_modules/unix-shell-js/dist/vi-editor.js"></script>
    <script src="node_modules/unix-shell-js/dist/example-files.js"></script>
    <script>
        // Create the shell with example files
        const shell = new UnixShell({
            username: 'user',
            fileSystem: createExampleFiles('user')
        });

        // Execute a command
        const output = shell.execute('ls -la');
        console.log(output);
    </script>
</body>
</html>

Initialize with Custom Filesystem

const customFS = {
    '/': {
        'home': {
            'myuser': {
                'welcome.txt': 'Hello, world!\n',
                'projects': {
                    'app.js': 'console.log("Hello");\n'
                }
            }
        }
    }
};

const shell = new UnixShell({
    username: 'myuser',
    fileSystem: customFS
});

Enable localStorage Persistence

The library includes built-in localStorage persistence to automatically save and restore the filesystem, current path, and current user across page reloads:

const shell = new UnixShell({
    username: 'user',
    fileSystem: createExampleFiles('user'),
    persistence: {
        enabled: true,
        prefix: 'myapp'  // Uses 'myapp_filesystem', 'myapp_current_user', 'myapp_current_path'
    }
});

How it works:

  • When persistence is enabled, the shell automatically loads saved state from localStorage on initialization
  • After each command execution, the filesystem and current state are automatically saved
  • If no saved data exists, it uses the provided fileSystem and username options
  • Use a custom prefix to avoid conflicts with other apps on the same domain

Clear saved data:

// Clear localStorage for this shell
shell.clearStorage();

Initialize with Custom Commands

const customCommands = {
    // Example: ps aux command
    ps: function(args) {
        // Parse flags
        let showAll = false;

        for (const arg of args) {
            if (arg === 'aux' || arg === '-aux') {
                showAll = true;
            }
        }

        const processes = [
            { pid: 1, user: 'root', command: '/sbin/init' },
            { pid: 100, user: this.currentUser, command: '-bash' }
        ];

        if (showAll) {
            processes.push(
                { pid: 50, user: 'root', command: '/usr/sbin/sshd' },
                { pid: 75, user: 'www-data', command: 'nginx' }
            );
        }

        let output = 'USER       PID COMMAND\n';
        processes.forEach(p => {
            output += `${p.user.padEnd(10)} ${String(p.pid).padStart(4)} ${p.command}\n`;
        });

        return output;
    },

    // Example: custom greeting command
    hello: function(args) {
        const name = args[0] || 'World';
        return `Hello, ${name}!`;
    }
};

const shell = new UnixShell({
    username: 'user',
    fileSystem: createExampleFiles('user'),
    customCommands: customCommands
});

// Now you can use your custom commands
console.log(shell.execute('hello Alice')); // Output: Hello, Alice!
console.log(shell.execute('ps aux'));      // Shows process list

Full Example with Terminal UI

// Initialize the shell
const shell = new UnixShell({
    username: 'developer',
    fileSystem: createExampleFiles('developer'),
    customCommands: {
        status: function(args) {
            // Example custom command
            return 'System Status: OK\nUptime: 5 days\nLoad: 0.5';
        }
    }
});

// Handle user input
function handleCommand(inputText) {
    const output = shell.execute(inputText);

    // Handle special outputs
    if (output === '__CLEAR__') {
        // Clear the terminal display
        clearTerminal();
    } else if (output === '__VI_OPENED__') {
        // Vi editor was opened
    } else if (output && output.startsWith('__USER_SWITCHED__:')) {
        // User changed (su/sudo command)
        updatePrompt();
    } else {
        // Display normal output
        displayOutput(output);
    }
}

Integration Tips

Managing the Command Prompt

When building a terminal UI, you'll need to display a prompt that shows the current user and directory. The prompt typically needs to update after commands like cd or su that change the current path or user.

Pattern for updating the prompt:

function updatePrompt() {
    const user = shell.getCurrentUser();
    let path = shell.getCurrentPath();
    const home = shell.environment.HOME;

    // Replace home directory with ~
    if (path === home) {
        path = '~';
    } else if (path.startsWith(home + '/')) {
        path = '~' + path.substring(home.length);
    }

    // Use # for root, $ for regular users
    const promptChar = user === 'root' ? '#' : '$';

    // Update your prompt element
    promptElement.textContent = `${user}@hostname:${path}${promptChar}`;
}

Typical command execution flow:

  1. User enters a command
  2. Capture the current prompt text (for display in command history)
  3. Execute the command: const output = shell.execute(command)
  4. Display the command with its original prompt in history
  5. Display the command output
  6. Update the active prompt to reflect any changes (new path, new user, etc.)
  7. Clear the input field for the next command

Key point: When displaying command history, preserve the prompt as it was when the command was entered. Only update the active input prompt after command execution.

See the live demo source code for a complete working example.

API Reference

UnixShell Constructor

new UnixShell(options)

Options:

  • fileSystem (Object): Custom filesystem structure
  • username (String): Current user name (default: 'user')
  • customCommands (Object): Custom command handlers
  • persistence (Object): localStorage persistence configuration
    • enabled (Boolean): Enable/disable persistence
    • prefix (String): localStorage key prefix (default: 'unixshell')

Methods

  • execute(commandLine) - Execute a command and return output
  • getCurrentPath() - Get current working directory
  • getCurrentUser() - Get current user
  • getNode(path) - Get filesystem node at path
  • resolvePath(path) - Resolve relative/absolute path
  • getCompletions(partial) - Get tab completion suggestions
  • saveToStorage() - Manually save state to localStorage (auto-called after commands if persistence enabled)
  • loadFromStorage() - Load state from localStorage (auto-called during initialization if persistence enabled)
  • clearStorage() - Clear saved state from localStorage

Built-in Commands

  • help - Show available commands
  • ls - List directory contents
  • cd - Change directory
  • pwd - Print working directory
  • cat - Display file contents
  • echo - Display text
  • clear - Clear terminal
  • whoami - Print current user
  • date - Display date/time
  • uname - Print system information
  • env - Print environment variables
  • history - Show command history
  • mkdir - Create directory
  • touch - Create file
  • rm - Remove file/directory
  • tree - Display directory tree
  • ps - Report process status (basic - can be overridden)
  • vi/vim - Edit file
  • su - Switch user
  • sudo - Execute as superuser
  • exit - Exit user session

Note: All built-in commands can be overridden by providing a custom command with the same name in the customCommands option.

Vi Editor

The library includes a fully functional vi/vim modal editor with:

  • Normal, Insert, and Command modes
  • Movement keys (hjkl, arrows, 0, $, G)
  • Insert commands (i, a, o, O)
  • Delete commands (x, dd, dw, d$, etc.)
  • Yank and paste (Y, p)
  • Save and quit (:w, :q, :wq, :q!)

License

MIT

About

Browser-based Unix/Linux command emulator with vi editor support

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published