Skip to content

mylocalgpt/shell

Repository files navigation

@mylocalgpt/shell

Virtual bash interpreter for AI agents. Pure TypeScript, zero runtime dependencies. Runs in any JavaScript runtime - browsers, Node.js, Deno, Bun, and Cloudflare Workers. Ships with 65+ commands, a full jq implementation, and an under 40KB gzipped entry point.

  • Pure JS, under 40KB gzipped, zero dependencies, runs anywhere
  • 65+ commands including grep, sed, awk, find, xargs, curl, and a full jq implementation
  • Pipes, redirections, variables, control flow, functions, arithmetic
  • Configurable execution limits, regex guardrails, no eval
  • OverlayFs: read-through overlay on real directories with change tracking

Install

npm install @mylocalgpt/shell

Quick Start

import { Shell } from '@mylocalgpt/shell';

const shell = new Shell({
  files: { '/data.json': '{"name": "alice"}' },
});

const result = await shell.exec('cat /data.json | jq .name');
console.log(result.stdout); // "alice"\n

API Reference

Shell Constructor

const shell = new Shell(options?: ShellOptions);
Option Type Description
fs FileSystem Custom filesystem implementation. When provided, files is ignored.
files Record<string, string | (() => string | Promise<string>)> Initial filesystem contents. Values can be strings or lazy-loaded functions. Ignored when fs is provided.
env Record<string, string> Environment variables. Merged with defaults (HOME, USER, PATH, SHELL).
limits Partial<ExecutionLimits> Execution limits. Merged with safe defaults.
commands Record<string, CommandHandler> Custom commands to register.
onUnknownCommand (name, args, ctx) => CommandResult Handler for unregistered commands.
onOutput (result) => ExecResult Post-processing hook for exec results.
hostname string Virtual hostname (used by hostname command).
username string Virtual username (used by whoami command).
enabledCommands string[] Restrict available commands to this allowlist.
network NetworkConfig Network handler for curl. See Network Config.
onBeforeCommand (cmd, args) => boolean | void Hook before each command (return false to block).
onCommandResult (cmd, result) => CommandResult Hook after each command (can modify result).

shell.exec(command, options?)

Execute a shell command. Never throws; all errors are returned in the result.

const result = await shell.exec('echo hello', {
  env: { EXTRA: 'value' },   // per-call env vars
  cwd: '/workspace',          // override working directory
  stdin: 'input data',        // provide stdin
  signal: controller.signal,  // AbortSignal for cancellation
  timeout: 5000,              // timeout in milliseconds
});

ExecResult:

interface ExecResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}

State persistence: environment exports, functions, working directory, and filesystem persist across exec() calls. Shell options (set -e, etc.) reset per call.

shell.defineCommand(name, handler)

Register a custom command that works in pipes and redirections.

shell.defineCommand('fetch-url', async (args, ctx) => {
  const response = await fetch(args[0]);
  return { stdout: await response.text(), stderr: '', exitCode: 0 };
});

await shell.exec('fetch-url https://api.example.com | jq .data');

shell.reset()

Clear environment and functions, reset working directory. Filesystem is kept intact.

Accessors

  • shell.fs - the virtual FileSystem instance
  • shell.cwd - current working directory
  • shell.env - environment variables (Map)

Supported Bash Features

  • Pipes and redirections (|, >, >>, <, 2>&1, &>)
  • Variables and expansion ($VAR, ${VAR:-default}, ${VAR/pattern/replace})
  • Control flow (if/elif/else/fi, for/while/until, case/esac)
  • Functions with local variables
  • Arrays and arithmetic ($((expr)))
  • Command substitution ($(cmd), `cmd`)
  • Here documents (<<EOF)
  • Glob expansion (*, ?, [...])
  • Brace expansion ({a,b}, {1..10})
  • set -euo pipefail
  • Subshells ((cmd))
  • Conditional expressions ([[ ]], [ ])

Commands

Command Description
cat Concatenate and print files
cp Copy files
mv Move/rename files
rm Remove files
mkdir Create directories
rmdir Remove empty directories
touch Create empty files or update timestamps
chmod Change file permissions
ln Create symbolic links
stat Display file status
file Determine file type
grep Search file contents (-i, -v, -c, -l, -n, -r, -w, -E)
sed Stream editor (s, d, p, i, a, c, y commands; -i, -n, -e)
awk Pattern scanning (print, if/else, variables, field splitting)
head Output first lines (-n, -c)
tail Output last lines (-n, -c, -f)
sort Sort lines (-r, -n, -u, -k, -t)
uniq Filter duplicate lines (-c, -d, -u)
wc Count lines, words, characters (-l, -w, -c, -m)
cut Remove sections from lines (-d, -f, -c)
tr Translate characters (-d, -s)
rev Reverse lines
tac Reverse file line order
paste Merge lines from files (-d)
fold Wrap lines (-w, -s)
comm Compare sorted files
join Join lines on a common field
nl Number lines
expand Convert tabs to spaces
unexpand Convert spaces to tabs
strings Print printable strings
column Format into columns (-t, -s)
find Search for files (-name, -type, -path, -maxdepth)
xargs Build commands from stdin (-I, -n)
diff Compare files
base64 Encode/decode base64 (-d)
md5sum Compute MD5 hash
sha1sum Compute SHA-1 hash
sha256sum Compute SHA-256 hash
expr Evaluate expressions
od Octal dump
ls List directory contents (-l, -a, -R, -1)
pwd Print working directory
tree Display directory tree
du Estimate file space usage
basename Strip directory from path
dirname Strip filename from path
readlink Read symbolic link target
realpath Resolve canonical path
echo Print arguments (-n, -e)
printf Format and print
env Display environment
printenv Print environment variables
date Display date/time
seq Print number sequence
hostname Print hostname
whoami Print current user
which Locate a command
tee Duplicate stdin to file and stdout
sleep Pause execution
yes Repeat a string (output-capped)
timeout Run command with time limit
xxd Hex dump (-l, -s)
curl HTTP requests via network handler
jq JSON processor (full implementation)

jq Support

Full jq implementation built from scratch. Supports identity, field access, array/object indexing, pipe, array/object construction, conditionals, try/catch, reduce, foreach, label/break, string interpolation, user-defined functions, and 80+ builtins including map, select, keys, values, has, in, contains, test, match, sub, gsub, split, join, ascii_downcase, ascii_upcase, tostring, tonumber, length, type, empty, error, env, path, getpath, setpath, delpaths, to_entries, from_entries, with_entries, group_by, sort_by, unique_by, min_by, max_by, flatten, range, floor, ceil, round, pow, log, sqrt, nan, infinite, isinfinite, isnan, isnormal, add, any, all, limit, first, last, nth, indices, inside, ltrimstr, rtrimstr, startswith, endswith, ascii, explode, implode, tojson, fromjson, @base64, @base64d, @uri, @csv, @tsv, @html, @json, @text, now, todate, fromdate, strftime, strptime, gmtime, mktime, builtins, debug, input, inputs, $ENV.

Available as a standalone import:

import { jq } from '@mylocalgpt/shell/jq';

Execution Limits

All limits are configurable via the limits constructor option.

Limit Default Prevents
maxLoopIterations 10,000 Infinite loops
maxCallDepth 100 Stack overflow from recursion
maxCommandCount 10,000 Excessive command execution
maxStringLength 10,000,000 Memory exhaustion from string growth
maxArraySize 100,000 Memory exhaustion from array growth
maxOutputSize 10,000,000 Unbounded output accumulation
maxPipelineDepth 100 Deeply nested pipes

Custom Commands

const shell = new Shell({
  commands: {
    'fetch-data': async (args, ctx) => {
      const url = args[0];
      const response = await fetch(url);
      return { stdout: await response.text(), stderr: '', exitCode: 0 };
    },
  },
});

// Or after construction:
shell.defineCommand('transform', async (args, ctx) => {
  return { stdout: ctx.stdin.toUpperCase(), stderr: '', exitCode: 0 };
});

Custom commands receive the full CommandContext and participate in pipes, redirections, and all shell features.

Hooks

onUnknownCommand

Handle commands not in the registry:

const shell = new Shell({
  onUnknownCommand: async (name, args, ctx) => {
    if (name === 'python') {
      return { stdout: '', stderr: `${name}: use jq for data processing\n`, exitCode: 127 };
    }
    return { stdout: '', stderr: `${name}: command not found\n`, exitCode: 127 };
  },
});

onOutput

Post-process all exec results (truncate, redact, log):

const shell = new Shell({
  onOutput: (result) => ({
    ...result,
    stdout: result.stdout.slice(0, 30000), // truncate large output
  }),
});

OverlayFs

Read-through overlay that reads from a real host directory and writes to memory. The host filesystem is never modified.

import { Shell } from '@mylocalgpt/shell';
import { OverlayFs } from '@mylocalgpt/shell/overlay';

const overlay = new OverlayFs('/path/to/project', {
  denyPaths: ['*.env', '*.key', 'node_modules/**'],
});
const shell = new Shell({ fs: overlay });

await shell.exec('cat src/index.ts | wc -l');
await shell.exec('echo "new file" > output.txt');

const changes = overlay.getChanges();
// { created: [{ path: '/output.txt', content: 'new file\n' }], modified: [], deleted: [] }

Available as a separate entry point at @mylocalgpt/shell/overlay. Requires Node.js (uses node:fs).

Network Config

curl delegates all HTTP requests to a consumer-provided handler. The shell never makes real network requests.

const shell = new Shell({
  network: {
    handler: async (url, opts) => {
      const res = await fetch(url, { method: opts.method, headers: opts.headers, body: opts.body });
      return { status: res.status, body: await res.text(), headers: {} };
    },
    allowlist: ['api.example.com', '*.internal.corp'],
  },
});

await shell.exec('curl -s https://api.example.com/data | jq .results');

Security Model

See THREAT_MODEL.md for the full security model, threat analysis, and explicit non-goals.

What we do:

  • All user-provided regex goes through pattern complexity checks and input-length caps
  • Execution limits prevent infinite loops, deep recursion, and memory exhaustion
  • No eval() or new Function() code paths
  • Path traversal prevention via normalized absolute paths
  • Environment variables stored in Map (no prototype pollution)
  • No node: imports; runs in sandboxed environments

What we don't do:

  • This is not an OS-level sandbox. The shell executes within your JavaScript runtime's security context.
  • Custom commands have full access to the JavaScript environment. Use OS-level isolation for untrusted code.

License

Apache-2.0

About

Virtual bash interpreter for AI agents. Pure TypeScript, zero runtime dependencies. Runs in any JavaScript runtime. 60+ commands.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages