{"_id":"@acepad/shell","name":"@acepad/shell","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"main":"shell.js","name":"@acepad/shell","type":"module","version":"1.0.0","description":"A Unix-like shell environment for Acepad.","dependencies":{"@hoge1e3/lru":"^1.0.0","petit-node":"^1.0.0","maybe-monada":"^1.0.0","@hoge1e3/is-plain-object":"^1.0.0"},"gitHead":"7bb2af3db5cc8b87fa1f0815dc438f6ac107c2b0","_id":"@acepad/shell@1.0.0","_nodeVersion":"22.17.0","_npmVersion":"11.7.0","dist":{"integrity":"sha512-iZ4/r7ZBRd6ncFJhYHO+87WUbwLdU+K9rJ/C1wwMI1CaPKGNnS/AfdSpsT4G6mBRmYSdJmVUuk9X3H4MKMbi5g==","shasum":"3a61607e77185c350c5b10026c976bc1ed1d2bfd","tarball":"https://registry.npmjs.org/@acepad/shell/-/shell-1.0.0.tgz","fileCount":6,"unpackedSize":34259,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDFTk33lcCfd/ns1x4Up0Es0GGZ5oVUj+wntVWRl6WJNwIgHU5LiapQEruEiptiLdvsi7W+6UsxKtyw/+jaHbqwQOo="}]},"_npmUser":{"name":"acepad","email":"pnode.acepad@gmail.com"},"directories":{},"maintainers":[{"name":"acepad","email":"pnode.acepad@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/shell_1.0.0_1769321120164_0.2973609726840283"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-25T06:05:20.088Z","1.0.0":"2026-01-25T06:05:20.308Z","modified":"2026-01-25T06:05:20.485Z"},"maintainers":[{"name":"acepad","email":"pnode.acepad@gmail.com"}],"description":"A Unix-like shell environment for Acepad.","readme":"# @acepad/shell\n\nA Unix-like shell environment for browser-based programming with [petit-node](https://www.npmjs.com/package/petit-node). This package provides the core shell functionality for [acepad](https://hoge1e3.github.io/acepad/), enabling file system operations, command execution, and shell scripting directly in the browser.\n\n## Features\n\n- **Unix-like commands**: cd, pwd, ls, cp, mv, rm, mkdir, cat, grep, etc.\n- **File system operations**: Navigate and manipulate the virtual file system provided by petit-fs\n- **Custom command creation**: Write your own shell commands in JavaScript\n- **Command execution**: Execute commands from both files and built-in functions\n- **Glob pattern support**: Use wildcards (`*`, `?`) in file paths\n- **Environment variables**: Access via `this.$variableName` syntax\n- **Command history**: Automatic LRU-based command history per directory\n- **Proxy-based command resolution**: Dynamically resolve commands from PATH\n\n\n**Note**: This package is designed to run in acepad/petit-node environment, not in standard Node.js.\n\n## Basic Usage\n\n### Importing and Initialization\n\nThe shell object is automatically initializd,\nNormally explicit import of \"@acepad/shell\" is not needed.\n\n(press F2 to create file and F5 to run, in acepad)\n```js\n#!run\nexport async function main(){ \n    // The shell is already initialized and assigned to 'this'\n    this.pwd(); // Shows current directory\n}\n```\n\n### Executing Built-in Commands\n\n```js\n#!run\nexport async function main(){ \n    const sh=this;\n    // Change directory\n    sh.cd(\"/tmp/\");\n    // List files\n    sh.ls();\n    // Create a file\n    sh.touch(\"myfile.txt\");\n    // Read file content\n    sh.cat(\"myfile.txt\");\n    // Create directory\n    sh.mkdir(\"mydir/\");\n    // Copy files\n    sh.cp(\"myfile.txt\", \"mydir/\");\n}\n```\n\n### Working with Files\n\n```js\n#!run\nexport async function main(){ \n    const sh=this;\n    // Resolve a file path (returns SFile object)\n    const file = sh.resolve(\"./myfile.txt\");\n    // Check if file exists\n    if (file.exists()) {\n        sh.echo(\"File exists!\");\n    }\n    // File test operators (similar to bash)\n    sh._e(\"file.txt\");  // exists\n    sh._f(\"file.txt\");  // is regular file\n    sh._d(\"mydir/\");    // is directory\n    sh._s(\"file.txt\");  // file size (0 if empty)\n}\n```\n\n### Shell Variables / Environment variables\n- Shell variables are field bounded to the shell object, NOT environment variables.\n- Shell variables are referred by `sh.$var_name`\n- If `sh.$var_name` is missing, fallbacks to parent shell object and then environment variables.\n- Environment variables can be accessed via `process.env` which is polyfilled in global scope.\n\n```js\n#!run\nexport async function main(){ \n    const sh=this;\n    // Set variable\n    sh.set(\"myvar\", \"hello\");\n    sh.$myvar = \"hello\";  // Alternative syntax\n\n    // Get variable\n    const value = sh.get(\"myvar\");\n    const value2 = sh.$myvar;  // Alternative syntax\n\n    // Use in commands\n    sh.echo(sh.$myvar);  // Outputs: hello\n    process.env.envvar=\"good\";\n    sh.echo(sh.$envvar);  // Outputs: good\n}\n```\n\n## Creating Custom Commands\n\n### File-based Commands\n\nCommands are JavaScript files placed in directories listed in the `$path` variable. The file must start with `#!run` or `//!run` and export a `main` function.\n\n**Example**: Create a greeting command in `/idb/run/bin/greet`\n\nTips: You can also create command by typing `sh: newcmd greet` in 'Directory List' in acepad.\n\n```js\n#!run\n\nexport async function main(name) {\n    if (!name) name = \"World\";\n    this.echo(`Hello, ${name}!`);\n    this.echo(`Current directory: ${this.getcwd()}`);\n}\n```\n\nUsage:\n\n```js\n// Assuming /idb/run/bin is in $path\nsh.greet(\"Alice\");  // Outputs: Hello, Alice!\n```\n\nFrom 'Directory List' of acepad, line starting with `sh:` is interpreted shell command. \n```shell\nsh: greet Alice\nHello, Alice!\n```\n\n### The `this` Context\n\nInside `main`, the command functions, `this` refers to a cloned shell object with:\n\n- **File operations**: `this.resolve()`, `this.exists()`, `this.directorify()`\n- **Output**: `this.echo()`, `this.err()`\n- **Directory navigation**: `this.getcwd()`, `this.cd()`\n- **Variables**: `this.$variableName` or `this.get(\"variableName\")`\n- **All built-in commands**: `this.ls()`, `this.cp()`, etc.\n- **Command execution**: Call other commands via `this.commandName()`\n\n**Example**: A command that copies a file and shows its size\n\n**Warning!** The code below has not been tested yet and remains as Claude wrote it :-(\n\n```js\n#!run\n\nexport async function main(src, dst) {\n    const srcFile = this.resolve(src, true);  // true = must exist\n    const dstFile = this.resolve(dst);\n    \n    if (!srcFile.exists()) {\n        throw new Error(`${src}: no such file`);\n    }\n    \n    await srcFile.copyTo(dstFile);\n    this.echo(`Copied ${src} to ${dst}`);\n    this.echo(`Size: ${dstFile.size()} bytes`);\n}\n```\n\n### Handling Options and Arguments\n\nUse `this.collectOptions(args)` to separate options from arguments:\n\n```js\n#!run\n\nexport async function main(...args) {\n    args = this.collectOptions(args);\n    const options = args.pop();  // Last element is always the options object\n    \n    if (options.v || options.verbose) {\n        this.echo(\"Verbose mode enabled\");\n    }\n    \n    // Process remaining arguments\n    for (let arg of args) {\n        this.echo(`Processing: ${arg}`);\n    }\n}\n```\n\nUsage:\n\n```bash\nsh: mycommand file1.txt file2.txt -v\nsh: mycommand -verbose file.txt\n```\n\n### Adding Synchronous Commands\n\nFor commands that need to be synchronous (non-async), register them directly:\n\n```js\n// Method 1: Using newCommand\nsh.newCommand(\"twice\", function(n) {\n    return parseInt(n) * 2;\n});\n\n// Method 2: Using addCmd with file argument mapping\nsh.addCmd(\"showsize\", function(file) {\n    this.echo(`Size: ${file.size()}`);\n}, \"f\");  // \"f\" means first argument is a file path\n```\n\nThe second parameter of `addCmd` is a spec string where:\n- `\"f\"` at position i means argument i should be resolved as a file\n\n```js\nsh.twice(5);      // Returns: 10\nsh.showsize(\"myfile.txt\");  // Shows file size\n```\n\n## Command Path and Resolution\n\n### PATH Variable\n\nCommands are searched in directories specified by `$path`:\n\n**Note:** For convenience of typing in smartphone, PATH variable is referred as `$path`, not capital letters.\n\n```js\n// View current PATH\nsh.echo(sh.$path);  // e.g., \"/bin:/sbin:/home/user/bin\"\n\n// Add directory to PATH\nsh.addPath(\"/home/user/scripts/\");\n```\n\n### Command Resolution\n\nWhen you call `sh.commandName()`, the shell:\n\n1. Checks if it's a built-in command\n2. Searches for an executable file in PATH directories\n3. Executes the file's `main` function with a cloned shell as `this`\n\n### Finding Commands\n\n```js\n// List all available commands\nconst cmds = sh.commandList();\n\n// Find where a command is located\nconst cmdFile = sh.which(\"ls\");  // Returns SFile object\n```\n\n## Glob Patterns\n\nUse wildcards to match multiple files:\n\n```js\n// Match all .js files in current directory\nsh.ls(\"*.js\");\n\n// Match all files starting with \"test\"\nsh.cp(\"test*\", \"backup/\");\n\n// Single character wildcard\nsh.rm(\"file?.txt\");  // Matches file1.txt, file2.txt, etc.\n```\n\n**Note**: Glob patterns do NOT match across directory separators.\n\n## Command History\n\nCommand history is automatically tracked per directory:\n\n```js\n// Add command to history\nsh.addHist(\"ls -la\");\n\n// Get history for current directory\nconst hist = sh.history();\n\n// Get history for specific directory\nconst hist2 = sh.history(\"/home/user/\");\n```\n\nHistory is stored in `.meta/` directory and limited to 16 most recent commands per directory (LRU).\n\n## Parsing and Executing Commands\n\n### Parse Command String\n\n```js\nconst args = sh.parseCommand('echo \"Hello World\" $myvar');\n// Returns: [\"echo\", \"Hello World\", <value of $myvar>]\n```\n\nFeatures:\n- Double quotes preserve spaces\n- `${varname}` expands variables\n- Glob patterns are expanded\n- Options (e.g., `-v`, `-option=value`) are parsed into objects\n\n### Execute Commands\n\n```js\n// Execute parsed command\nsh.evalCommand([\"echo\", \"Hello\"]);\n\n// Execute command string\nsh.enterCommand(\"ls /home/\");\n\n// Alternative\nsh.exec(\"cp file1.txt file2.txt\");\n```\n\n## Advanced Features\n\n### File System Utilities\n\n```js\n// Convert path to directory (ensure trailing slash)\nconst dir = sh.directorify(\"/home/user\");\n\n// Get filesystem root\nconst root = sh.getRoot();\n\n// Mount/unmount filesystems\nawait sh.mount({t: \"ram\"}, \"/tmp/\");\nsh.unmount(\"/tmp/\");\n\n// Show mounted filesystems\nsh.fstab();\n```\n\n### Cloning Shell Environment\n\n```js\n// Create isolated shell environment with inherited variables\nconst newShell = sh.clone();\nnewShell.$myvar = \"isolated value\";\nsh.$myvar;  // Still has original value\n```\n\n### Zip Operations\n\n```js\n// Create zip file\nsh.zip(\"archive.zip\", sh.resolve(\"/home/user/data/\"));\n\n// Extract zip file\nsh.unzip(\"archive.zip\", sh.resolve(\"/home/user/restore/\"));\n```\n\n## Examples\n\n### Complete Command: Enhanced File Copy\n\n```js\n#!run\n\nexport async function main(...args) {\n    args = this.collectOptions(args);\n    const options = args.pop();\n    \n    if (args.length < 2) {\n        this.err(\"Usage: mycopy [-v] <source> <destination>\");\n        return 1;\n    }\n    \n    const dst = args.pop();\n    const verbose = options.v || options.verbose;\n    \n    for (let srcPath of args) {\n        const src = this.resolve(srcPath, true);\n        const dest = this.resolve(dst);\n        \n        if (verbose) {\n            this.echo(`Copying ${src.path()} to ${dest.path()}...`);\n        }\n        \n        await src.copyTo(dest);\n        \n        if (verbose) {\n            this.echo(`Done. Size: ${dest.size()} bytes`);\n        }\n    }\n    \n    return 0;\n}\n```\n\n### Interactive Shell Script\n\n```js\n#!run\n\nexport async function main() {\n    const home = this.$home;\n    this.echo(`Welcome to ${home}`);\n    \n    // List all JavaScript files\n    const files = this.glob(\"*.js\");\n    this.echo(`Found ${[...files].length} JavaScript files`);\n    \n    // Find large files\n    for (let fileName of this.glob(\"*\")) {\n        const file = this.resolve(fileName);\n        if (this._f(file) && file.size() > 10000) {\n            this.echo(`Large file: ${fileName} (${file.size()} bytes)`);\n        }\n    }\n}\n```\n\n### Command with Variable Usage\n\n```js\n#!run\n\nexport async function main() {\n    // Set a variable\n    this.$lastBackup = new Date().toISOString();\n    \n    // Create backup directory\n    const backupDir = this.resolve(`${this.$home}/backups/`);\n    if (!backupDir.exists()) {\n        backupDir.mkdir();\n    }\n    \n    // Copy all .txt files to backup\n    for (let file of this.glob(\"*.txt\")) {\n        const src = this.resolve(file);\n        const dst = backupDir.rel(src.name());\n        await src.copyTo(dst);\n        this.echo(`Backed up: ${file}`);\n    }\n    \n    this.echo(`Backup completed at ${this.$lastBackup}`);\n}\n```\n\n## Common Patterns\n\n### Check File/Directory Existence\n\n```js\nif (this._d(\"mydir/\")) {\n    this.echo(\"Directory exists\");\n} else {\n    this.mkdir(\"mydir/\");\n}\n```\n\n### Process All Files in Directory\n\n```js\nconst dir = this.resolve(\"./data/\");\nfor (let fileName of dir.ls()) {\n    const file = dir.rel(fileName);\n    if (this._f(file)) {\n        // Process regular file\n        this.echo(`Processing ${fileName}`);\n    }\n}\n```\n\n### Error Handling\n\n```js\ntry {\n    const file = this.resolve(\"config.json\", true);  // Must exist\n    const config = JSON.parse(file.text());\n} catch (e) {\n    this.err(\"Error:\", e.message);\n    return 1;  // Return non-zero exit code\n}\n```\n\n## API Reference\n\n### Core Methods\n\n- `cd(dir)` - Change directory\n- `pwd()` - Print working directory\n- `getcwd()` - Get current working directory (returns SFile)\n- `resolve(path, mustExist?)` - Resolve path to SFile object\n- `directorify(path)` - Ensure path ends with `/`\n- `exists(path)` - Check if file/directory exists\n\n### File Operations\n\n- `mkdir(path)` - Create directory\n- `touch(path)` - Create empty file or update timestamp\n- `cat(...files)` - Display file contents\n- `cp(src, dst)` - Copy file (async)\n- `mv(src, dst)` - Move file\n- `rm(path, options?)` - Remove file/directory\n- `ln(target, link)` - Create symbolic link\n\n### Output\n\n- `echo(...args)` - Print to output\n- `err(...args)` - Print error message\n\n### Variables\n\n- `get(name)` - Get variable value\n- `set(name, value)` - Set variable value\n- `getenv(name)` - Get environment variable\n- `setenv(name, value)` - Set environment variable\n\n### Commands\n\n- `parseCommand(string)` - Parse command string to arguments\n- `evalCommand(args)` - Execute parsed command\n- `enterCommand(string, extraArgs?)` - Parse and execute command\n- `exec(string)` - Alias for enterCommand\n- `commandList()` - List all available commands\n- `which(command)` - Find command file location\n\n## Dependencies\n\n- `petit-node`: Browser-based Node.js runtime\n- `@hoge1e3/lru`: LRU cache for command history\n- `@hoge1e3/is-plain-object`: Plain object detection\n- `maybe-monada`: Optional value handling (minimal usage)\n\n## License\n\nISC\n\n## See Also\n\n- [petit-node](https://www.npmjs.com/package/petit-node) - The underlying runtime\n- [acepad](https://hoge1e3.github.io/acepad/) - Programming environment using this shell\n- [@acepad/files](https://www.npmjs.com/package/@acepad/files) - File management utilities\n","readmeFilename":"README.md","_rev":"1-dd940c75f0db8673347c7a54064b4faa"}