npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@acepad/shell

v1.0.0

Published

A Unix-like shell environment for Acepad.

Readme

@acepad/shell

A Unix-like shell environment for browser-based programming with petit-node. This package provides the core shell functionality for acepad, enabling file system operations, command execution, and shell scripting directly in the browser.

Features

  • Unix-like commands: cd, pwd, ls, cp, mv, rm, mkdir, cat, grep, etc.
  • File system operations: Navigate and manipulate the virtual file system provided by petit-fs
  • Custom command creation: Write your own shell commands in JavaScript
  • Command execution: Execute commands from both files and built-in functions
  • Glob pattern support: Use wildcards (*, ?) in file paths
  • Environment variables: Access via this.$variableName syntax
  • Command history: Automatic LRU-based command history per directory
  • Proxy-based command resolution: Dynamically resolve commands from PATH

Note: This package is designed to run in acepad/petit-node environment, not in standard Node.js.

Basic Usage

Importing and Initialization

The shell object is automatically initializd, Normally explicit import of "@acepad/shell" is not needed.

(press F2 to create file and F5 to run, in acepad)

#!run
export async function main(){ 
    // The shell is already initialized and assigned to 'this'
    this.pwd(); // Shows current directory
}

Executing Built-in Commands

#!run
export async function main(){ 
    const sh=this;
    // Change directory
    sh.cd("/tmp/");
    // List files
    sh.ls();
    // Create a file
    sh.touch("myfile.txt");
    // Read file content
    sh.cat("myfile.txt");
    // Create directory
    sh.mkdir("mydir/");
    // Copy files
    sh.cp("myfile.txt", "mydir/");
}

Working with Files

#!run
export async function main(){ 
    const sh=this;
    // Resolve a file path (returns SFile object)
    const file = sh.resolve("./myfile.txt");
    // Check if file exists
    if (file.exists()) {
        sh.echo("File exists!");
    }
    // File test operators (similar to bash)
    sh._e("file.txt");  // exists
    sh._f("file.txt");  // is regular file
    sh._d("mydir/");    // is directory
    sh._s("file.txt");  // file size (0 if empty)
}

Shell Variables / Environment variables

  • Shell variables are field bounded to the shell object, NOT environment variables.
  • Shell variables are referred by sh.$var_name
  • If sh.$var_name is missing, fallbacks to parent shell object and then environment variables.
  • Environment variables can be accessed via process.env which is polyfilled in global scope.
#!run
export async function main(){ 
    const sh=this;
    // Set variable
    sh.set("myvar", "hello");
    sh.$myvar = "hello";  // Alternative syntax

    // Get variable
    const value = sh.get("myvar");
    const value2 = sh.$myvar;  // Alternative syntax

    // Use in commands
    sh.echo(sh.$myvar);  // Outputs: hello
    process.env.envvar="good";
    sh.echo(sh.$envvar);  // Outputs: good
}

Creating Custom Commands

File-based Commands

Commands are JavaScript files placed in directories listed in the $path variable. The file must start with #!run or //!run and export a main function.

Example: Create a greeting command in /idb/run/bin/greet

Tips: You can also create command by typing sh: newcmd greet in 'Directory List' in acepad.

#!run

export async function main(name) {
    if (!name) name = "World";
    this.echo(`Hello, ${name}!`);
    this.echo(`Current directory: ${this.getcwd()}`);
}

Usage:

// Assuming /idb/run/bin is in $path
sh.greet("Alice");  // Outputs: Hello, Alice!

From 'Directory List' of acepad, line starting with sh: is interpreted shell command.

sh: greet Alice
Hello, Alice!

The this Context

Inside main, the command functions, this refers to a cloned shell object with:

  • File operations: this.resolve(), this.exists(), this.directorify()
  • Output: this.echo(), this.err()
  • Directory navigation: this.getcwd(), this.cd()
  • Variables: this.$variableName or this.get("variableName")
  • All built-in commands: this.ls(), this.cp(), etc.
  • Command execution: Call other commands via this.commandName()

Example: A command that copies a file and shows its size

Warning! The code below has not been tested yet and remains as Claude wrote it :-(

#!run

export async function main(src, dst) {
    const srcFile = this.resolve(src, true);  // true = must exist
    const dstFile = this.resolve(dst);
    
    if (!srcFile.exists()) {
        throw new Error(`${src}: no such file`);
    }
    
    await srcFile.copyTo(dstFile);
    this.echo(`Copied ${src} to ${dst}`);
    this.echo(`Size: ${dstFile.size()} bytes`);
}

Handling Options and Arguments

Use this.collectOptions(args) to separate options from arguments:

#!run

export async function main(...args) {
    args = this.collectOptions(args);
    const options = args.pop();  // Last element is always the options object
    
    if (options.v || options.verbose) {
        this.echo("Verbose mode enabled");
    }
    
    // Process remaining arguments
    for (let arg of args) {
        this.echo(`Processing: ${arg}`);
    }
}

Usage:

sh: mycommand file1.txt file2.txt -v
sh: mycommand -verbose file.txt

Adding Synchronous Commands

For commands that need to be synchronous (non-async), register them directly:

// Method 1: Using newCommand
sh.newCommand("twice", function(n) {
    return parseInt(n) * 2;
});

// Method 2: Using addCmd with file argument mapping
sh.addCmd("showsize", function(file) {
    this.echo(`Size: ${file.size()}`);
}, "f");  // "f" means first argument is a file path

The second parameter of addCmd is a spec string where:

  • "f" at position i means argument i should be resolved as a file
sh.twice(5);      // Returns: 10
sh.showsize("myfile.txt");  // Shows file size

Command Path and Resolution

PATH Variable

Commands are searched in directories specified by $path:

Note: For convenience of typing in smartphone, PATH variable is referred as $path, not capital letters.

// View current PATH
sh.echo(sh.$path);  // e.g., "/bin:/sbin:/home/user/bin"

// Add directory to PATH
sh.addPath("/home/user/scripts/");

Command Resolution

When you call sh.commandName(), the shell:

  1. Checks if it's a built-in command
  2. Searches for an executable file in PATH directories
  3. Executes the file's main function with a cloned shell as this

Finding Commands

// List all available commands
const cmds = sh.commandList();

// Find where a command is located
const cmdFile = sh.which("ls");  // Returns SFile object

Glob Patterns

Use wildcards to match multiple files:

// Match all .js files in current directory
sh.ls("*.js");

// Match all files starting with "test"
sh.cp("test*", "backup/");

// Single character wildcard
sh.rm("file?.txt");  // Matches file1.txt, file2.txt, etc.

Note: Glob patterns do NOT match across directory separators.

Command History

Command history is automatically tracked per directory:

// Add command to history
sh.addHist("ls -la");

// Get history for current directory
const hist = sh.history();

// Get history for specific directory
const hist2 = sh.history("/home/user/");

History is stored in .meta/ directory and limited to 16 most recent commands per directory (LRU).

Parsing and Executing Commands

Parse Command String

const args = sh.parseCommand('echo "Hello World" $myvar');
// Returns: ["echo", "Hello World", <value of $myvar>]

Features:

  • Double quotes preserve spaces
  • ${varname} expands variables
  • Glob patterns are expanded
  • Options (e.g., -v, -option=value) are parsed into objects

Execute Commands

// Execute parsed command
sh.evalCommand(["echo", "Hello"]);

// Execute command string
sh.enterCommand("ls /home/");

// Alternative
sh.exec("cp file1.txt file2.txt");

Advanced Features

File System Utilities

// Convert path to directory (ensure trailing slash)
const dir = sh.directorify("/home/user");

// Get filesystem root
const root = sh.getRoot();

// Mount/unmount filesystems
await sh.mount({t: "ram"}, "/tmp/");
sh.unmount("/tmp/");

// Show mounted filesystems
sh.fstab();

Cloning Shell Environment

// Create isolated shell environment with inherited variables
const newShell = sh.clone();
newShell.$myvar = "isolated value";
sh.$myvar;  // Still has original value

Zip Operations

// Create zip file
sh.zip("archive.zip", sh.resolve("/home/user/data/"));

// Extract zip file
sh.unzip("archive.zip", sh.resolve("/home/user/restore/"));

Examples

Complete Command: Enhanced File Copy

#!run

export async function main(...args) {
    args = this.collectOptions(args);
    const options = args.pop();
    
    if (args.length < 2) {
        this.err("Usage: mycopy [-v] <source> <destination>");
        return 1;
    }
    
    const dst = args.pop();
    const verbose = options.v || options.verbose;
    
    for (let srcPath of args) {
        const src = this.resolve(srcPath, true);
        const dest = this.resolve(dst);
        
        if (verbose) {
            this.echo(`Copying ${src.path()} to ${dest.path()}...`);
        }
        
        await src.copyTo(dest);
        
        if (verbose) {
            this.echo(`Done. Size: ${dest.size()} bytes`);
        }
    }
    
    return 0;
}

Interactive Shell Script

#!run

export async function main() {
    const home = this.$home;
    this.echo(`Welcome to ${home}`);
    
    // List all JavaScript files
    const files = this.glob("*.js");
    this.echo(`Found ${[...files].length} JavaScript files`);
    
    // Find large files
    for (let fileName of this.glob("*")) {
        const file = this.resolve(fileName);
        if (this._f(file) && file.size() > 10000) {
            this.echo(`Large file: ${fileName} (${file.size()} bytes)`);
        }
    }
}

Command with Variable Usage

#!run

export async function main() {
    // Set a variable
    this.$lastBackup = new Date().toISOString();
    
    // Create backup directory
    const backupDir = this.resolve(`${this.$home}/backups/`);
    if (!backupDir.exists()) {
        backupDir.mkdir();
    }
    
    // Copy all .txt files to backup
    for (let file of this.glob("*.txt")) {
        const src = this.resolve(file);
        const dst = backupDir.rel(src.name());
        await src.copyTo(dst);
        this.echo(`Backed up: ${file}`);
    }
    
    this.echo(`Backup completed at ${this.$lastBackup}`);
}

Common Patterns

Check File/Directory Existence

if (this._d("mydir/")) {
    this.echo("Directory exists");
} else {
    this.mkdir("mydir/");
}

Process All Files in Directory

const dir = this.resolve("./data/");
for (let fileName of dir.ls()) {
    const file = dir.rel(fileName);
    if (this._f(file)) {
        // Process regular file
        this.echo(`Processing ${fileName}`);
    }
}

Error Handling

try {
    const file = this.resolve("config.json", true);  // Must exist
    const config = JSON.parse(file.text());
} catch (e) {
    this.err("Error:", e.message);
    return 1;  // Return non-zero exit code
}

API Reference

Core Methods

  • cd(dir) - Change directory
  • pwd() - Print working directory
  • getcwd() - Get current working directory (returns SFile)
  • resolve(path, mustExist?) - Resolve path to SFile object
  • directorify(path) - Ensure path ends with /
  • exists(path) - Check if file/directory exists

File Operations

  • mkdir(path) - Create directory
  • touch(path) - Create empty file or update timestamp
  • cat(...files) - Display file contents
  • cp(src, dst) - Copy file (async)
  • mv(src, dst) - Move file
  • rm(path, options?) - Remove file/directory
  • ln(target, link) - Create symbolic link

Output

  • echo(...args) - Print to output
  • err(...args) - Print error message

Variables

  • get(name) - Get variable value
  • set(name, value) - Set variable value
  • getenv(name) - Get environment variable
  • setenv(name, value) - Set environment variable

Commands

  • parseCommand(string) - Parse command string to arguments
  • evalCommand(args) - Execute parsed command
  • enterCommand(string, extraArgs?) - Parse and execute command
  • exec(string) - Alias for enterCommand
  • commandList() - List all available commands
  • which(command) - Find command file location

Dependencies

  • petit-node: Browser-based Node.js runtime
  • @hoge1e3/lru: LRU cache for command history
  • @hoge1e3/is-plain-object: Plain object detection
  • maybe-monada: Optional value handling (minimal usage)

License

ISC

See Also