@poe-platform/safe-bash
v0.1.743
Published
Extensible virtual shell with pluggable filesystems and streaming commands
Readme
safe-bash
Run shell scripts and command-line tools in your application against an explicit filesystem, without launching a host shell.
Import ffmpegCommands from @poe-platform/safe-bash/commands/ffmpeg and register
it with shell.use(ffmpegCommands()) for in-memory media conversion and probing.
Pass cloudflareWorkerLimits() explicitly to bound media work in Workers.
Quickstart
Install in a Node.js 22+ ESM application:
npm install @poe-platform/safe-bashimport { Shell, agentCommands, createMemoryFileSystem } from "@poe-platform/safe-bash";
const fs = createMemoryFileSystem();
const encoder = new TextEncoder();
await fs.mkdir("/work");
await fs.writeFile("/work/names.txt", encoder.encode("Ada\nGrace\nAda\n"));
await fs.writeFile("/work/run.sh", encoder.encode(`#!/bin/sh
set -eu
sort names.txt | uniq > names.sorted.txt
printf 'Hello, %s!\\n' "$1"
cat names.sorted.txt
`));
const shell = new Shell({ fs, cwd: "/work" }).use(agentCommands());
try {
const result = await shell.exec("sh run.sh reader");
if (result.exitCode !== 0) throw new Error(result.stderr);
process.stdout.write(result.stdout);
} finally {
await shell.dispose();
}Output: Hello, reader!\nAda\nGrace\n. The script, input, and generated
names.sorted.txt stay in memory. Results contain exitCode, stdout, stderr,
stdoutBytes, and stderrBytes; use the byte fields for binary output.
Each exec() starts fresh shell variables, functions, and working-directory state;
filesystem changes persist in the supplied fs. The invocation-local umask
starts at 0022, accepts octal or symbolic modes, and is inherited by child shells.
Creation modes use the filesystem's capabilities; advisory modes do not enforce
physical permissions, and the host process mask remains unchanged.
Core and shell imports install a portable globalThis.Buffer when it is absent;
an existing host Buffer is preserved. Workers do not need nodejs_compat for this.
Supported features and commands
Shell syntax
- Quoting and escapes, variables and positional arguments (including
${@:offset:length}and${*:offset:length}slices), parameter expansion (including scalar${!name}indirection and^/^^and,/,,case conversion with optional patterns),$(command)and backtick substitution, arithmetic expansion, and pathname globs. - Pipelines (
|,|&), lists (;,&&,||,!), file redirection (<,>,>>,<>), combined output redirection (&>,>& file,&>>), descriptor redirection such as2>&1, here-documents, and here-strings.<>opens without truncation and requires a filesystem with descriptor support. if/elif/else,case,for name in …,while,until, functions, groups{ …; }, subshells( … )(including adjacent nested subshells),[[ … ]](including file age/identity, special-file, mode and nameref predicates; literal/glob and ASCII ERE comparisons also accept UTF-8 locales, with ranges limited to C/POSIX collation, including C.UTF-8), arithmetic commands(( … )), and indexed arrays with arithmetic and relative negative element indices, includingdeclare -a,local -a, andreadonly -aarray literals. Arithmetic array operands such asa[i]support reads, assignments, and increments in$(( … )),(( … )),for (( … )), andlet, including associative keys. Array expansions support member slices, element substrings, member-wise trimming and substitution, and lazy element default, alternate, assignment, and error operators by default. Ownership predicates-Oand-Gin[[ … ]],testand[use explicitcapabilities: { predicateIdentity: { effectiveUid, effectiveGid } }on the shell or execution options; missing caller or filesystem identity is refused. File predicates in[[ … ]],testand[evaluate symlink traversal loops as false;-L/-hstill recognize the link itself. Access checks-r/-w/-xevaluate denied access as false, and-Ncompares modification and access times.- Virtual script files through
sh,bash, or executable paths;source/.runs a script in the current shell.bash -n script.sh(alsosh -n) checks syntax without executing commands;set -n/set -o noexecparses the remaining input without execution.set -e,set -u, andset -o pipefailcontrol failures;set -a(orset -o allexport) exports subsequent variable assignments to child commands, andset +adisables automatic export. Bash-profileset -f/set -o noglobdisables pathname expansion, andset +frestores it.set -C(orset -o noclobber) protects existing output files from>redirection;>|overrides it and>>still appends.shopt -s dotglobincludes dotfiles in globs.
Shell builtins beyond the tools below: :, cd, pushd, popd, dirs, set,
shift, export, local, declare, readonly, unset, read, getopts, let, shopt, umask,
exit, return, break, continue, command, builtin, type, ., source,
eval. pwd, true, and false also work without a command bundle.
command -p bypasses functions and searches /bin:/usr/bin in the supplied
filesystem while preserving PATH; registered commands remain available.
Combine it with -v or -V for discovery, including command -pV printf.
unset -v NAME removes variables; unset -f NAME removes functions without changing
variables of the same name. Use -- to end option parsing.
read -a NAME replaces an indexed array with the record's IFS-separated fields.
The default read accepts -u 0 to select supplied stdin, plus -p PROMPT
and -s for nonterminal input; prompts are suppressed and silent mode has no
terminal effect. Other -u descriptors require the optional read extension.
export -n NAME removes a variable's export attribute while keeping its value;
export -f NAME passes a defined function to virtual child shells, and export -fn NAME
stops passing it. export -p prints reusable variable declarations; export -fp lists
exported functions.
declare supports integer (-i), ASCII case conversion (-l/-u), scalar
name references (-n), exports (-x), readonly (-r), arrays (-a/-A),
global declarations (-g), inherited locals (-I), declaration printing (-p),
and function inspection (-f/-F). typeset is an alias for declare.
local accepts variable attribute flags and combinations such as -ai and -ar.
declare, typeset, local, and export accept name+=value; integer variables
add the arithmetic value instead of concatenating text.
cd -L preserves symlinks in PWD (the default); cd -P resolves symlinks before
following .. and records the physical directory. Use cd -- <path> for paths
beginning with a dash.
Each invocation starts with umask 0022; numeric and symbolic masks affect new
files and default directory modes through the supplied filesystem. Host umask
is unchanged and may further restrict real file modes; remote modes can be advisory.
readonly -f name protects a function from redefinition and unset -f;
readonly -f lists protected functions. unset -v removes variables independently.
local -i count=2+3 evaluates scalar assignments as arithmetic; local -n ref=target
reads and writes the named variable. Both attributes follow function scope and restore
outer bindings on return. Indexed and associative arrays support readonly (-r),
integer (-i, including arithmetic +=), and ASCII case conversion (-l/-u)
on each element. Nameref targets must be simple variable names; combining nameref
with integer or array attributes is unsupported.
Command bundle
agentCommands() registers all 79 commands below. They operate on the supplied
filesystem and byte streams, not host executables. file recognizes bounded CSV,
HTML, XML and shell/Python shebang text alongside JSON and binary headers, preserving
the detected charset in MIME output; see its recognition limits.
hexdump and hd default to the BSD numeric and repeated-row profile.
Use agentCommands({ hexdump: { dialect: "util-linux" } }) or
hexdumpCommands({ dialect: "util-linux" }) for hexadecimal/octal counts,
binary size suffixes such as 1KiB, decimal suffixes such as 1KB, and
unsqueezed partial final rows. This selects those semantics; custom formats
and other unsupported options remain unavailable.
| Purpose | Commands |
| --- | --- |
| Browse | pwd, ls (including -i/--inode for provider-reported inode numbers, with ? for unknown metadata; -Q/--quote-name and --indicator-style=none/slash/file-type/classify), tree, find (including -H to follow argument symlinks while keeping descendant links physical, and -D tree for virtual expression diagnostics; other debug modes are unsupported), du, file, basename, dirname, realpath (including lexical -s/--strip/--no-symlinks), readlink (including -m/--canonicalize-missing), which |
| Files | mkdir, touch, cp, mv, rm, rmdir, ln, chmod, stat, mktemp. mkdir -m accepts octal and symbolic permission modes; symbolic modes start from 0777 masked by the current umask. rm --interactive=never removes without prompting; --interactive=always / -i reads a confirmation from stdin for each removal. --interactive=once / -I prompts once for recursive removal or more than three operands. cp -a / --archive copies directory trees without dereferencing symbolic links (the virtual archive profile is -RP; add preservation flags for metadata and hard links). cp -b / --backup[=simple\|numbered\|existing] preserves replaced files; -S / --suffix sets the simple backup suffix. cp -t DIR / --target-directory=DIR copies sources into a directory; -T / --no-target-directory treats the destination as a single path. mv -u / --update moves files only when the destination is absent or older; directories retain normal move behavior. mv -t DIR / --target-directory=DIR moves sources into an existing directory; -T / --no-target-directory treats the destination as an exact path. cp --remove-destination removes destination entries before copying, preserving other hard links and leaving destination symlink referents untouched. cp --preserve=mode retains permissions; -p retains modes and access/modification times and requires matching reported owners. -d copies symlinks and preserves links between copied entries. --attributes-only leaves existing bytes intact and creates empty missing files; combine it with preservation flags to update metadata. Unsupported metadata operations fail explicitly; ownership changes and symlink timestamps are unavailable. cp -v reports paths using the original operand spelling, including relative paths. ln -sr / --symbolic --relative computes a relative symlink target from the link directory, resolving existing symlinks and allowing missing sources. ln -v / --verbose reports each successful link as 'target' => 'source' (hard) or 'target' -> 'source' (symbolic). rmdir --ignore-fail-on-non-empty leaves nonempty directories untouched without reporting a failure; with -p, removal stops at the first nonempty parent. touch -h / --no-dereference accepts ordinary files and reads reference symlink metadata; symlink targets fail explicitly because symlink timestamp mutation is unavailable. touch -d / --date accepts epoch seconds (@0), ISO/RFC dates and the virtual date relative-date profile; -t [[CC]YY]MMDDhhmm[.ss] sets a calendar timestamp. |
| Filter/search | cat, head, tail, wc, tee, cut, tr, sort, uniq, sed, awk, grep, rg, egrep, fgrep, fd. fd finds virtual files and directories with regex/glob patterns, extension/type/size/time filters, ignore files, and literal command execution; see fd usage. awk accepts --field-separator (-F), --source (-e), and --characters-as-bytes (-b); its strings and records are byte-oriented. cut -c selects raw bytes in C/POSIX locales (LC_ALL, then LC_CTYPE, then LANG); otherwise it selects UTF-8 characters, including when no locale is set. head and tail accept -z / --zero-terminated for NUL-delimited records. tail --follow[=descriptor\|name] follows retained files; --retry retries inaccessible files (-F combines name following and retry). -s / --sleep-interval sets polling seconds, and --max-unchanged-stats controls name reopening after unchanged polls; --max-idle=0 emits only the initial selection. Following requires retained-read filesystem support. uniq -D / --all-repeated[=none\|prepend\|separate] prints every repeated record; --group[=separate\|prepend\|append\|both] prints all records with group separators. wc -L / --max-line-length counts display columns with eight-column tab stops; UTF-8 widths use the frozen GNU/Linux C.UTF-8 profile. |
| Format/combine | nl, seq, rev, tac, expand, unexpand, fold, fmt, strings, paste, comm, join, column, split, pr. pr --date-format=FORMAT customizes the UTC header date using the virtual date format directives. |
| Structured text | jq, html-to-markdown, xq, xmllint. xq converts XML to JSON and applies jq filters. xmllint supports --xpath, --noout well-formedness checks, --format, and --c14n with comments; DTDs and schema validation are unsupported. XML modes and limits. jq supports ASCII escapes (-a), indentation (--indent, --tab), explicit color (-C / -M), NUL-delimited raw output (--raw-output0), and unbuffered writes. jq -S / --sort-keys sorts object keys recursively in JSON output; --stream / --stream-errors emit path events, and --seq handles JSON sequences within the same resource limits. --rawfile NAME FILE binds virtual-file text; --slurpfile NAME FILE binds its JSON values as an array. --args and --jsonargs expose string or JSON arguments through $ARGS.positional. jq -L DIRECTORY loads virtual .jq modules with zero-argument definitions; see jq options and limits. |
| Bytes/checksums | base64, base32, xxd, od, md5sum, sha1sum, sha256sum, cksum. od -a prints ASCII character names, -f prints IEEE single-precision floats, and -i / -l print signed four/eight-byte integers. Explicit float types are -tf4 and -tf8. od --strings[=MIN] / -S[MIN] prints NUL-terminated strings with at least MIN characters (default 3). |
| Archives | Tar extraction to the filesystem requires atomic backend confinement; MemoryFileSystem supports it, while backends without it refuse extraction. tar -xO remains available. gzip, gunzip, zcat, tar, zip, unzip. ZIP inspection supports unzip -Z -1 ARCHIVE [FILES...] for names only and unzip -z ARCHIVE for the archive comment, without extracting members. Other ZipInfo formats are unsupported. Tar supports gzip (-z), bzip2 (-j), xz (-J), suffix-selected creation (-a), and compression detection when reading. Creation accepts --mtime=@SECONDS or ISO/RFC dates, numeric --owner/--group, and octal --mode; --atime-preserve[=replace] restores source file and directory access times on timestamp-capable backends. Extraction accepts --touch/-m, --same-permissions/--preserve-permissions/-p, --no-same-permissions (virtual 022 mask), --no-same-owner, and directory restoration policies. Special permission bits remain stripped; ownership restoration (--same-owner), name lookup, symbolic modes and no-atime reads (--atime-preserve=system) are unsupported. --full-time shows UTC timestamps in verbose listings; --utc also enables verbose listing. --record-size=SIZE / -b N sets output record padding, -B accepts full-record reads, and --ignore-zeros / -i reads past zero blocks and concatenated archives. --seek / -n, --no-seek, and --force-local accept local VFS archives with sequential reads. --quoting-style=literal\|escape\|c controls displayed names; --totals reports archive byte counts on stderr. Extraction supports -k / --keep-old-files (preserve conflicts and return status 2), --skip-old-files (preserve silently), and --overwrite (default safe replacement); the last policy wins. Existing directories are merged. Path, symlink and input-archive safety checks apply to every policy. |
| ZIP archives | zip, unzip. File extraction requires atomic ancestry verification; MemoryFileSystem supports it, while unsupported backends refuse extraction. unzip -n preserves existing regular files without prompting; -j flattens paths and skips directory entries. Selection uses original archive names. Overwrite, traversal, symlink and backend capability checks still apply. |
| Script helpers | echo, printf, true, false, test, [, env, printenv, xargs, expr, date, sleep, timeout. test / [ support special-node and mode predicates and live shell -v, -R, -o, -t queries. Byte-stream descriptors are nonterminal. Ownership queries require the command plugin option predicateIdentity: { effectiveUid, effectiveGid } and backend owner metadata; unavailable capabilities report an error. Remote modes remain advisory. env -v / --debug writes environment changes, the working directory change, and command/argument diagnostics to stderr. |
| Changes/review | diff, patch, apply_patch. Diff supports binary comparisons, byte-preserving text changes, symlinks, multi-file comparisons, explicit context widths, and --color[=auto\|never\|always]; diff options and limits describes configuration. |
printf returns status 1 for invalid formats and options, retaining any output written before a format error. Its Bash format dialect accepts zero flags on string conversions and rejects numbered conversions such as %1$s; the grouping flag is accepted without locale-specific separators.
uniq INPUT OUTPUT refuses aliases of the input, including symbolic and hard links, before opening the output. An existing output also requires the filesystem to establish that the files are distinct. cut accepts space/tab separators and padding in range lists, rejects empty comma items, and retains --output-delimiter between adjacent byte or character ranges. chmod -r removes read permissions; --reference=FILE copies a file's mode.
cp -l / --link creates hard links to source files; cp -s / --symbolic-link creates symbolic links using the source operand as the literal target. Relative symbolic targets require destinations in the current directory; use absolute source paths when copying into other directories. Link modes require filesystem link capabilities and support recursive copying, no-clobber and backups. cp -x / --one-file-system creates directories at known filesystem boundaries without copying their contents; unknown filesystem identities remain traversable.
ln -i / --interactive prompts on stderr before replacing an existing destination and reads one answer from stdin. Answers beginning with y or Y confirm replacement; refusal or EOF preserves the entry and returns status 1. The last -i or -f option selects interactive or forced replacement.
ln -L / --logical follows source symlinks for hard links; -P / --physical links the source entry (the default). The last source-mode option wins; both are ignored with -s. Physical symlink hard links depend on backend support and host link semantics on the real filesystem adapter.
Tar creation also accepts --sort=name (bytewise directory-child order; operand order stays unchanged), --sort=none (default), --dereference / -h (archive symbolic-link targets), and --exclude-caches (retain directories with a valid CACHEDIR.TAG and their tag files, omitting other contents). Dereferencing retains backend containment and output-archive checks and rejects directory cycles.
Tar reads newline-delimited exclusion patterns with -X FILE / --exclude-from=FILE. When listing or extracting, --wildcards enables anchored glob member selection; --no-wildcards restores literal selection. --occurrence[=NUM] selects only the requested occurrence of each member operand (default 1), and requires member operands.
Use cat --help, base64 --help, cp --help, sort --help, grep --help, rg --help or tar --help to discover supported options. base64 --version, cp --version and sort --version identify the safe-bash implementation. These informational requests exit before processing input files.
cp -u / --update copies missing files and replaces files only when the source
has a newer modification time. Recursive copies compare each file separately.
File-content copies (cp and cross-device mv) require a retained reader with
authoritative file identity. Destinations require streaming writes, exclusive
creation for missing or explicitly removed files, or guarded staging for an
existing-file move. Exclusive creation buffers the
inspected source size within the host input and memory budgets. The reader identity
is verified before bytes are read; backends without this guarantee refuse the
transfer. Ordinary same-device mv still uses rename.
mv -i / --interactive asks on stderr before replacing an existing destination
and reads one response from stdin. A response beginning with y or Y allows
the move; refusal or EOF keeps both paths and returns status 1. The last of
-i, -f, and -n wins. Skipped updates and missing destinations do not prompt,
and backups are created only after confirmation.
Cross-device mv requires atomic conditional source removal. Backends without
this capability refuse the fallback before copying. If a source entry or its
parent changes during transfer, cleanup fails and retains the copied data;
the command never deletes a replacement source entry.
Cross-device mv can replace an existing regular file using guarded staging
with atomic destination and ancestry checks and retained staging cleanup. Memory
mounts support this route. Source bytes are buffered under the input and
collection limits; supported modes and timestamps are prepared before publication.
Backends without these guarantees refuse the overwrite. Hardlinked destinations
are replaced without changing the contents of their sibling links. Opaque
destination identities remain unsupported. Backends with bound path resolution
support symlinked destination parents and reject path changes before publication.
Same-device replacement and cross-device moves to missing destinations remain supported.
Directory moves with multiple source links to one inode require atomic unlink
receipts to remove those links without accepting unrelated source changes.
cp -i / --interactive prompts on stderr before overwriting each existing file
and reads one response from stdin. Responses beginning with y or Y allow
replacement; refusal or EOF preserves the destination and returns status 1.
The last -i or -n wins; -f does not disable confirmation.
stat -t FILE / --terse prints file metadata in GNU field order, using ? for
fields the backend does not expose. -c / --format and --printf override
terse output. Filesystem terse output (-ft) remains unsupported.
factor --exponents 72 prints 72: 2^3 3^2; without operands, it reads numbers from stdin.
Integers are exact beyond JavaScript's safe-integer range. factor.limits.maxValue
accepts a positive safe integer, a positive bigint, or Infinity (the default).
zstd, unzstd, and zstdcat accept -q / --quiet, including combined
short options such as -qc. Repeating quiet suppresses processing errors on
stderr while preserving failure exit codes and validation.
Use --[no-]check to control frame checksums, --stream-size=BYTES to declare
and enforce input size, and --[no-]pass-through to copy unrecognized input
during decompression. --exclude-compressed skips compressed file suffixes.
Single-thread execution, I/O read-ahead, literal compression, row matching,
size hints and bounded long-distance matching are configurable; see the
compression options.
Named compression removes its source only through an atomic identity- and
ancestry-bound conditional delete. Backends without that guarantee refuse before
publishing output; use --keep or --stdout to retain the source.
Zstandard presets are parsed as whole numbers; the bounded codec supports levels
1–9. Higher presets and --fast[=NUM] fail explicitly instead of selecting a
different level. Native codecs enforce a 64 MiB allocation ceiling for encoding
and decoding, including ZIP LZMA; Zstandard windows are limited to 32 MiB.
XZ decompression flags can lower the ceiling. Output-byte limits remain separate.
Use grep -A NUM, -B NUM or -C NUM to include lines after, before or around each match; separated groups use --, and -n marks context lines with -.
grep -L / --files-without-match prints filenames without selected lines. Its exit status follows GNU grep: 0 if any input line is selected, 1 otherwise, even when it prints a filename. -v inverts line selection; -q suppresses output. This also applies to egrep and fgrep.
Use grep -r to search directories, with --include, --exclude, --exclude-from and --exclude-dir to filter basenames. -R follows nested symlinks; recursion is bounded to 128 levels and detects ancestor loops. -b prints byte offsets, -Z uses NUL after filenames, and --no-group-separator hides context separators. These options also work with egrep and fgrep.
--group-separator=SEP customizes context separators, --label=LABEL names stdin, and --initial-tab inserts a tab after prefixes. --binary-files=text searches raw bytes as text; other binary-detection modes are explicitly unsupported. --binary preserves bytes without Windows text-mode translation. --color=never and --colour=never disable colour; auto also emits plain text because output is captured rather than a terminal. Forced colour is unsupported.
The default bounded grep matcher rejects BRE groups, intervals, backreferences
and escape extensions such as \|. Use grep -E 'Remove upvote|Upvoted' for
alternation or grep -F -e 'Remove upvote' -e 'Upvoted' for literal alternatives.
Exit 1 means no match; exit 2 means filtering failed. If a preceding action
succeeded, inspect its resulting state and retry only the read-only verification
before repeating the action. A configured regex executor may support more syntax.
tee accepts -i / --ignore-interrupts, -p, and --output-error[=warn|warn-nopipe|exit|exit-nopipe] alongside -a / --append. File write failures continue with the remaining destinations in warn modes and stop in exit modes; the last error option wins. Bare --output-error and -p select warn-nopipe. Virtual commands have no native signal handlers or OS pipes: host cancellation and shell downstream-failure handling remain in effect.
iconv -f UTF-8 -t UTF-16BE -o result source writes converted bytes to a virtual file, replacing its existing contents. --output=result is equivalent; omit input files to convert stdin, and use --output=- to write to stdout. Conversion output limits also apply to file output.
iconv -s / --silent accepts the native silent control while retaining fatal error diagnostics. --verbose prints each named input as filename: on stderr before conversion; stdin has no progress label.
Use grep -w or --word-regexp to match whole words with C-locale byte matching (word characters are ASCII letters, digits and underscore), including fixed strings and -o output.
cut -c preserves and selects individual bytes in C/POSIX locales, using the first nonempty value of LC_ALL, LC_CTYPE, and LANG. Other locales and an unset locale retain UTF-8 character selection.
Use strings -s ':' or --output-separator=: to separate extracted strings with custom text, including after the final string. An empty separator joins the strings.
Default rg accepts UTF-8 literals and bounded ASCII regex operators (., anchors,
classes, groups, alternation and greedy repetition), including -o and match counts.
It preserves original UTF-8 byte offsets and supports case/word selection on ASCII
subjects, plus bounded ASCII globs for path and ignore filtering. Unicode case/word
selection, Unicode regex syntax, escape extensions, lazy repetition and invalid
UTF-8/NUL subjects require a configured regex executor; unsupported profiles fail
explicitly. Literal replacement, trimming, file-size limits, depth aliases and
explicit virtual ignore files are supported. --threads accepts a count while
execution stays serial; --multiline admits line-compatible searches, with
cross-line patterns still rejected by the bounded matcher.
Default grep, egrep and fgrep preserve arbitrary subject bytes for fixed
matching and ASCII patterns without regex syntax, including NUL and invalid UTF-8
records with -a. Patterns retain UTF-8 validation; regex operators retain the
bounded engine's UTF-8 subject restrictions.
/commands/fmt exports parseFmtArguments, the pure byte coroutine
createFmtEngine, and equivalent fmtCommand({ limits?, profile? }) /
fmt(context, { width?, goal?, crown?, tagged?, split?, uniform?, prefix?, files?, limits?, profile? })
execution APIs, with literal byte arguments available as an alternative to typed
formatting options. fmtCommands({ limits?, profile?, replace? }) registers an
explicit plugin. Formatting defaults to GNU coreutils 9.10 byte lengths and
bounded paragraph optimization;
an explicit historical 8.30 profile retains its older width boundary. Supports
-w, -g, -c, -t, -s, -u and -p; for example,
printf 'aa bb cc dd ee' | fmt -w8 produces aa bb cc\ndd ee\n. Private
implementation and declarations ship inside safe-bash. See the
fmt contract
for spacing, prefix, cancellation and resource limits.
Opt-in commands and storage
These plugins are separate from agentCommands(); pass them to shell.use(...).
mikeYqCommands() from /commands/yq enables bounded Mike-style format
conversion: yq -p csv -o json . records.csv reads a table, and
yq -o csv . records.yaml writes one. Input and output support CSV, TSV,
properties, XML, INI, TOML, base64 and URI; shell and Lua are output only.
See the format profile for supported shapes and limits.
Import csvcutCommands from @poe-platform/safe-bash/commands/csvcut and pass it
to shell.use(csvcutCommands()) to enable CSV projection. For example,
printf 'a,b\nx,y\n' | csvcut -c2,1,2 emits b,a,b\ny,x,y\n.
The same subpath exports the typed SDK and bounded record/selector APIs; see the
flags, limits and profiles.
Packed Node ESM consumers are verified; actual browser/workerd execution remains
unqualified.
| Command | Plugin and configuration |
| --- | --- |
| dos2unix, unix2dos | lineEndingCommands({ limits?, replace? }) from /commands/line-endings: byte and UTF-16 line-ending conversion. Use -e / --add-eol to terminate the final line, -O / --to-stdout to convert without rewriting inputs, and -i[FLAGS] / --info[=FLAGS] to inspect line endings, BOM and text/binary status. -v prints conversion details. In-place conversion requires safe publication capabilities and preserves reported ownership; --allow-chown permits publication when ownership cannot be preserved, and --no-allow-chown restores the default. |
| curl, wget | networkCommands({ authorize, transport?, limits?, replace? }): required authorization on every request, redirect, and retry. Curl's -r/--range requests byte ranges. Wget supports --header 'NAME: VALUE', --user-agent, --referer, --post-data/--post-file and --method with --body-data/--body-file; body files come from the VFS and retain their bytes. Repeated wget headers replace earlier values by name; --header='' clears custom headers, and NAME: sends an empty value. Transport-controlled headers are rejected, and custom headers are dropped after crossing origins. Node uses the native HTTP transport and supports curl's --connect-timeout for DNS/TCP/TLS setup. Workers can inject createFetchTransport(), which cannot enforce a separate connection timeout. createOriginAuthorizer([...]) provides exact origin/hostname policy; its omitted allowlist is deliberately * (allow all). Options and limits. |
| node | nodeCommands({ runtime, limits?, replace? }): runs JavaScript with an injected SafeJS runtime, virtual files, and shell streams. Usage and supported subset. |
| python, python3 | pythonCommands({ createExecutor }) from @poe-platform/safe-bash/commands/python: supply an explicit executor for invocation-local Python, filesystem I/O and shell streams. Open-file, directory, worker-capacity, input-fragment, package-download and cache quotas are unlimited by default; configure individual budgets when needed. Executor and ownership contract. |
| llm | llmCommands({ providers, defaultModel?, replace? }): opt-in model routing, sandbox attachments and streamed text/binary output. Includes injected-transport OpenAI and ElevenLabs reference providers. Configuration and provider contract. |
| pdfinfo | /commands/pdfinfo: opt-in pdfinfoCommands({ replace? }) registering pdfinfo, pdftoppm, pdfimages, pdfunite, and pdfseparate powered by @poe-code/pdf-ast. Supports Poppler metadata, page geometry, -box, -isodates/-rawdates, -custom, -meta, -url, -js, -struct/-struct-text, -dests, page rasterization (pdftoppm), image listing/extraction (pdfimages), PDF merging (pdfunite), and page splitting (pdfseparate). |
| pdftoppm | /commands/pdftoppm: opt-in pdftoppmPlugin({ replace? }) and createPdftoppmCommand powered by @poe-code/pdf-ast. Renders PDF pages to .png, .ppm, .pgm, .pbm, or .svg with configurable -r/-rx/-ry DPI, -f/-l page ranges, -singlefile, -sep, and -x/-y/-W/-H/-cropbox sub-region cropping. |
| pdfimages | /commands/pdfimages: opt-in pdfimagesPlugin({ replace? }) and createPdfimagesCommand powered by @poe-code/pdf-ast. Lists (-list) and extracts embedded XObject, nested Form XObject, and inline images to .png, .jpg (-j/-all), .ppm, or .pbm with CTM PPI metadata and -p page numbering. |
| pdftotext | /commands/pdftotext: opt-in pdftotextCommands({ replace? }) registering pdftotext and pdftohtml powered by @poe-code/pdf-ast. Supports logical reading order with dehyphenation, -layout, -raw, -bbox/-bbox-layout XHTML, Poppler -tsv, -htmlmeta, and pdftohtml (-xml, -stdout, -s, -c). |
| pdftk | /commands/pdftk: opt-in pdftkPlugin({ replace? }) and createPdftkCommand powered by @poe-code/pdf-ast. Supports cat/shuffle multi-handle page assembly and rotations, dump_data_fields_utf8, fill_form (FDF/XFDF/stanzas) with flatten, burst, rotate, background, and stamp. |
| qpdf | /commands/qpdf: opt-in qpdfCommands({ replace? }) and runQpdfCli powered by @poe-code/pdf-ast. Supports --check, --show-npages, --show-xref, --show-object, --json, --is-encrypted, --requires-password, --show-encryption, --encrypt/--decrypt, --empty --pages ... --, --split-pages, --rotate, --qdf, and --replace-input. |
| magick, convert, mogrify, composite, montage, compare | /commands/imagemagick: opt-in imagemagickPlugin({ replace? }) (imagemagickCommands) registering ImageMagick magick, convert, mogrify, composite, montage, compare, and identify powered by @poe-code/image-ast. Supports xc:/canvas:/gradient:/radial-gradient:/pattern:checkerboard/label: generators, geometry modifiers (!, >, <, ^, %, @), left-to-right image stack operators (-resize, -crop, -extent, -border, -shave, -splice, -chop, -roll, -trim, -rotate, -flip, -flop, -shear, -distort (SRT, Perspective, Affine, Barrel), -swirl, -implode, -wave, -shadow, -vignette, -negate, -colorspace, -sepia-tone, -solarize, -posterize, -colors, -modulate, -gamma, -level, -threshold, -opaque/-transparent, -evaluate, -function, -clut, -fx, -blur, -sharpen, -edge, -emboss, -charcoal, -draw (rectangle, circle, polygon, polyline, bezier, path, text), -annotate, ( ... ), +clone, -morph, +append, -append, -composite, -flatten), compare -metric AE\|MAE\|MSE\|RMSE\|PSNR\|SSIM, in-place mogrify, montage grids, out-%d.png scene output, and stdin/stdout byte streams (-, png:-). |
| sips | /commands/sips: opt-in sipsPlugin({ replace? }) (sipsCommands) registering macOS sips and ImageMagick identify powered by @poe-code/image-ast (sharp). Supports -g, -1, -Z, -z, --resampleWidth, --resampleHeight, -c, --cropOffset, -p, --padColor, -r, -f, -s format, -s formatOptions, -o/--out, and identify (-ping, -format, -verbose). |
| soffice | /commands/soffice: opt-in sofficeCommands({ replace? }) registering soffice and libreoffice powered by @poe-code/pdf-ast. Supports headless --convert-to pdf[:writer_pdf_Export\|calc_pdf_Export\|impress_pdf_Export] (including JSON FilterData), StarCalc --convert-to csv:..., --cat, and conversion across .docx, .odt, .rtf, .xlsx, .pptx, .csv, .html, .png, .txt, and .pdf. |
| wkhtmltopdf | /commands/wkhtmltopdf: opt-in CLI/SDK adapter with VFS byte I/O and explicit limits. Requires a supplied first-party static renderer; none is included. wkhtmltopdfCommands({ limits, renderer? }) and runWkhtmltopdf(context, options) share behavior. Exported switches lists all 122 flags and rejections; wkhtmltopdfLimits defaults to 16 MiB PDF output, 64 objects and 128 batch jobs. --help, --extended-help and --version work without a renderer; TOC and dynamic execution are unavailable. |
| unrtf | /commands/unrtf: opt-in unrtfCommands({ limits?, replace? }), equivalent unrtf(context, { format?, file?, limits? }) SDK, and bounded tokenizeRtf, extractRtf, renderRtf streams. Strict UTF-8 text/HTML supports scoped font/color/emphasis and flat tables; objects, pictures and field instructions stay inert. Use unrtf --text /document.rtf or unrtf --html /document.rtf (HTML default); output is UTF-8 with no invented text separator/final LF. Accepted flags are --text, --html, --quiet, --nopict, -n, --; unsupported profiles fail with status 1. Opt into scoped GNU 0.21.10 text/HTML/LaTeX wrappers and character aliases with --profile=gnu-0.21.10 or SDK profile; this admits --latex, --noremap (SDK noremap) and banner suppression via --quiet (SDK quiet). Strict extraction remains in effect; full native parser/rendering parity and picture exports remain unimplemented. Private implementation/types ship inside safe-bash; see the flags, limits and runtime profile. |
| exiftool | /commands/exiftool: opt-in exiftoolCommands({ limits? }) for uncompressed PNG text and tIME inspection/selected writes. Use exiftool -j -Title /image.png, -csv for union headers, or -Title=Example to edit with an _original backup. Typed SDK argv uses createExiftoolArguments. Defaults: 64 files, 16 MiB cumulative input/output, 8 MiB decoded and 32 MiB retained bytes. Private implementation/types ship inside safe-bash. PDF, Office, EXIF/XMP, broader timestamps, import and execute protocols remain unsupported; see the supported profile. |
| csvcut | /commands/csvcut: opt-in csvcutCommands({ limits?, replace? }) and equivalent csvcut(context, { include?, exclude?, zero?, names?, headerless?, deleteEmptyRows?, lineNumbers?, addBom?, dialect?, filePath?, encoding?, help?, version? }, { limits? }). Bounded UTF-8-sig byte input, comma/LF output and literal VFS paths; csvkit 2.2.0 selector candidate, permissive-v1 reader, quoting 0/3 only, no Sniffer or full Python compatibility. Defaults: 16 MiB input, 32 MiB output, 64 MiB retained, 1 MiB fields and 100,000 cells. Private implementation/types ship inside safe-bash; see the flags, limits and runtime profile. |
| csvgrep | /commands/csvgrep: opt-in csvgrepCommands({ limits?, replace? }) and equivalent csvgrep(context, { columns, match?, regex?, file?, any?, invert?, dialect?, filePath? }, { limits? }). Preserves CSV rows/headers with UTF-8-sig input, comma/LF output and the bounded bounded-sequence-v1 Python regex subset; full csvkit compatibility is unqualified. Private implementation/types ship inside safe-bash; see the supported profile. |
| fold | /commands/fold: opt-in foldCommands({ locale?, limits?, replace? }) and fold(context, { width?, mode?, spaces?, files?, locale?, limits? }) for byte streams and literal VFS paths. Supports column, character and byte counting with a pinned Unicode 17 profile or explicit C decoding. Use replace: true with agentCommands() to replace its existing fold implementation; defaults are unchanged. Private implementation/types ship inside safe-bash; see the supported profile. |
| playwright-cli | Standard browser commands, storage state, native snapshots, recordings, and traces through a host-owned adapter. Completed actions retain their live session when a failed checkpoint confirms safe cleanup. Sessions and host capabilities. Optional Cloudflare adapter and portable profiles. installPlaywrightNetworkPolicy from /playwright supports browser-native redirects with per-hop bounded host HTTP fetch and independent direct HTTP/WebSocket denial. bindPlaywrightRoutePolicy(context, { ownsRequest, admit, fetch }, limits) lets standard route mocks and header rewrites use that host policy, with admission before matching and bounded response leases. Cloudflare guardrails do not establish WebRTC/UDP denial or all-protocol accounting; hosts requiring those guarantees must refuse this integration. Network policy and lifecycle. |
/commands/diff3 provides opt-in diff3Commands({ limits?, replace? }), the
equivalent diff3(context, { files, merge?, selector?, labels?, ... }) SDK, and
pure byte report/merge/ed and analysis APIs. Its GNU 3.12 qualified profile uses
VFS files and bounded stdin spooling; private implementation and declarations
ship inside safe-bash. diff3 -m /ours /base /theirs emits merge bytes (flagged
conflicts return 1); the default report returns 0 for differences. -e emits an
ed script without executing it. GNU 3.12's -X is unflagged; one stdin operand
is supported in any position, and external --diff-program selection is refused.
The command is qualified for Node ESM; actual browser/workerd engines remain
unverified. See the flags, limits and supported profile.
/commands/htmlq exports opt-in htmlqCommands({ limits?, replace? }),
equivalent htmlq(context, { selector: "p", text: true }) SDK execution (or
literal argv) and the inert HTML
byte-stream engine. Use htmlq 'div > p' -t -f /input.html; attribute, text and
HTML projections preserve pinned separators and lazy first-match removals.
Explicit engine limits and cancellation are required. Private implementation and
types ship inside safe-bash. Full HTML5 recovery, selector grammar and Rust URL
parity remain unqualified; modern :is/:where/:has/:lang are explicitly rejected.
Use --attributes (plural) for attribute output; no-match succeeds with empty
output. Scripts/styles remain inert and preserved; interior BOMs are retained
regardless of input chunking, correcting the pinned upstream defect.
Node.js 22+ is qualified; browser/workerd conditional graphs are checked
in Node, while actual engines remain unverified. See the
supported flags and limits.
For example, load the optional PDF adapter to inspect its supported options:
import { Shell, createMemoryFileSystem } from "@poe-platform/safe-bash";
import { wkhtmltopdfCommands } from "@poe-platform/safe-bash/commands/wkhtmltopdf";
const shell = new Shell({ fs: createMemoryFileSystem() }).use(wkhtmltopdfCommands());
try {
console.log((await shell.exec("wkhtmltopdf --help")).stdout);
} finally {
await shell.dispose();
}For a custom same-isolate Python JSPI host, use createPythonJspiExecutor from
the same Python entry with an explicit loader, precompiled Wasm modules and
pinned, authenticated runtime assets; follow the static host recipe.
Native I/O uses the caller's asynchronous filesystem without workspace copying,
Node worker threads or a SAB request/reply bridge. This path is qualified with
installed public-package artifacts in local workerd, not a verified Cloudflare
deployment or a managed Python native-filesystem integration. JSPI cancellation
is cooperative; it neither preempts CPU-only loops nor establishes confinement.
Storage can be in memory, a rooted host directory, S3-compatible storage, or WebDAV, with read-only wrappers, mounts, and overlays. Choose and configure it explicitly; see the filesystem guide.
Run JavaScript with SafeJS
Plug SafeJS into node; nothing starts a native Node.js subprocess or loads a
runtime automatically. SafeJS is the execution engine, not a separate shell command.
import { Shell, agentCommands, createMemoryFileSystem, nodeCommands } from "@poe-platform/safe-bash";
import { Budget, run, makeFsModule, declareHostOperation, parseSourceModule } from "@poe-platform/safe-js";
const fs = createMemoryFileSystem();
await fs.writeFile("/transform.js", new TextEncoder().encode(`
import { writeFile } from "fs";
const text = await process.stdin.readText();
await writeFile("/result.txt", text.toUpperCase());
console.log(process.argv[2]);
`));
const shell = new Shell({ fs }).use(agentCommands()).use(nodeCommands({
runtime: {
run, makeFsModule, declareHostOperation, parseSourceModule,
createBudget: options => new Budget(options),
},
}));
try {
const result = await shell.exec("printf 'hello\\n' | node /transform.js done; cat /result.txt; node -p '1 + 2'");
console.log(result.stdout);
} finally {
await shell.dispose();
}Output: done\nHELLO\n3\n.
node -e SOURCE evaluates a program; node -p EXPRESSION prints an expression.
node FILE, node -, and bare node accept virtual-file or stdin source.
--env-file loads virtual dotenv files; --version uses supplied runtime identity
and --completion-bash lists supported options. Runtime flags require explicit
adapter capabilities; see the Node configuration.
Programs get console, virtual process.argv, process.env, process.cwd(),
process.exitCode, guest-owned Buffer bytes with string encodings and shared
views, and shell streams. Await process.stdout.write(text) and
process.stderr.write(text); read input with process.stdin.readText() or
readBytes(size?). These are bounded async helpers, not native Node streams.
setTimeout(callback, delay?, ...args) and clearTimeout(id) support cancellable
guest timers, including during top-level await. Pending callbacks finish before
the command exits and share its deadline and interpreter budgets.
require("fs").readFileSync(path, "utf8") reads text from the VFS before the next
guest statement; node:fs and named/default/namespace imports work too. An encoding
is required. Async helpers remain available through fs.promises and
fs/promises.
Import async filesystem functions from "fs" or "node:fs/promises", or use
const fs = require("node:fs/promises"). require("./data.json") loads virtual
JSON relative to the entry file's directory, or virtual cwd for inline and stdin
source. node --require ./setup.cjs / node -r ./setup.cjs preloads virtual
CommonJS modules before the program; repeated flags run in order from virtual cwd.
Explicit .cjs, .js, and .json module paths share an invocation-local cache,
including nested relative dependencies, and retain source limits, interpreter
budgets, and cancellation. Other synchronous fs operations, package search,
ESM loading, process.exit(), and native module fallback are unavailable.
Pass limits for source/input/output bytes, timeout, and interpreter budgets;
see defaults and configuration.
Wire an individual MCP tool
There is no generic safe-bash MCP server. Define each server and tool around the
specific operation it grants, then call Shell.exec() with fixed shell source.
For example, this server exposes name normalization without accepting arbitrary
Bash from the MCP client:
import { Shell, agentCommands, createMemoryFileSystem } from "poe-code/safe-bash";
import { createServer, defineSchema } from "tiny-stdio-mcp-server";
const shell = new Shell({ fs: createMemoryFileSystem() }).use(agentCommands());
const input = defineSchema({ names: { type: "string" } });
const server = createServer({ name: "contacts-tools", version: "1.0.0" })
.tool("normalize_names", "Sort and deduplicate newline-separated names", input,
async ({ names }) => {
const result = await shell.exec("sort | uniq", { stdin: names });
if (result.exitCode !== 0) throw new Error(result.stderr);
return result.stdout;
});
try {
await server.listen();
} finally {
await shell.dispose();
}Install poe-code and the MCP transport package in that server's own project.
Choose its filesystem, command bundle, limits, and opt-in capabilities there;
do not expose caller-supplied shell source unless arbitrary shell execution is
the deliberate API.
Add a command
A CommandDefinition has a name and an execute(context) handler. This example
adds file-bytes, which reports a virtual file's size without reading its contents:
import {
Shell, agentCommands, createMemoryFileSystem, resolvePath, writeText,
type CommandDefinition,
} from "@poe-platform/safe-bash";
const fileBytes: CommandDefinition = {
name: "file-bytes",
async execute({ args, cwd, fs, stdout, stderr, signal }) {
if (args.length !== 1) {
await writeText(stderr, "Usage: file-bytes FILE\n");
return { exitCode: 2 };
}
const stat = await fs.stat(resolvePath(cwd, args[0]!), { signal });
await writeText(stdout, `${stat.size}\n`);
return { exitCode: 0 };
},
};
const shell = new Shell({ fs: createMemoryFileSystem() }).use(agentCommands());
shell.use({
name: "file-tools",
setup(host) { host.commands.register(fileBytes); },
});
try {
const result = await shell.exec("printf 'hello\\n' > message.txt; file-bytes message.txt | cat");
if (result.exitCode !== 0) throw new Error(result.stderr);
process.stdout.write(result.stdout);
} finally {
await shell.dispose();
}Output: 6\n. For a single command, use shell.register(fileBytes) instead of a
plugin. Duplicate names fail unless registration explicitly sets { replace: true }.
Handlers receive args, stdin, stdout, stderr, cwd, env, fs, and signal;
return { exitCode } with an integer from 0–255, await writes, and pass the signal to I/O.
Use context.invoke to call another command with literal arguments rather than
interpolating shell source. Command contract.
shell.use(middleware) wraps command dispatch for logging or policy checks;
middleware must await or return next(). Plugins can also register filesystem
factories and provide a dispose() hook. Plugin contract.
For SafeJS host integration, makeSafeJsShellModule exposes shell execution and
makeSafeJsFsModule adapts the filesystem through injected runtime hooks.
Integration contracts.
Options
Shell and execution
| new Shell(...) option | Behavior |
| --- | --- |
| fs | Required filesystem; no implicit host access. |
| deviceView | "default" (the default) adds synthetic /dev/null and shadows ordinary backing files there. "provided" uses the supplied filesystem's paths and capabilities, including an exec filesystem override, without adding devices. Nested commands retain the selected view. |
| cwd | Initial virtual directory; defaults to /. |
| env | Initial exported variables; defaults to an empty map, with PWD set from cwd. No host environment inheritance. Never pass host process.env or any secret-bearing object: everything in env is readable by executed scripts (env, printenv, $VAR), and on Cloudflare Workers with nodejs_compat process.env contains the Worker's secret bindings. The shell warns only for the identical host process.env object, not copies, and does not filter values. |
| commands | Existing CommandRegistry; defaults to an empty registry. |
| limits | Resource limits listed below. |
exec(source, options) can override fs, cwd, and limits, and merge env for
one execution. stdin accepts a string, Uint8Array, or async byte source;
stdout/stderr accept byte sinks. Results still buffer output when sinks are
provided. Pass an AbortSignal as signal to cancel. Option types.
Execution quotas are unlimited by default. Set individual limits to opt in;
supplying one does not enable other quotas. Available quotas are maxParseUnits,
maxInputBytes, maxOutputBytes, maxCommands, maxFileSystemOperations,
maxPathComponents, maxPathnameComponents, maxRedirects, maxPipelineStages, maxLoopIterations,
maxSubstitutionDepth, maxSourceBytes, maxExpansionFields, maxExpansionBytes,
maxWallClockMs, and maxCpuMs. The CPU deadline measures elapsed time including
waits at cooperative checkpoints. pipeHighWaterMark defaults to 64 KiB for
streaming backpressure and does not cap total work. cloudflareWorkerLimits is
an explicit restrictive preset. Background job quotas (maxJobs, maxWaiters,
maxCleanupsPerJob) are also unlimited unless supplied.
Always call dispose() when finished. Shell failures normally produce an exit
code and stderr; limit violations, cancellation, and host failures can reject exec().
The command budget counts compound commands and loop conditions as well as body
commands. With both work budgets set to 10,000, while true; do :; done reaches
maxCommands first. Work budgets bound execution counts, not elapsed latency.
maxPathComponents counts PATH search directories consulted per lookup;
maxPathnameComponents separately bounds components within each filesystem path.
Await execution settlement and shell disposal before closing backing storage,
including after a caller timeout; cancellation is cooperative.
Portable browser profiles restore blank tabs by default, preserving storage,
settings, tab count, and selection. Only pass tabRestoration: 'navigate' to
restoreBrowserProfile when replaying saved URLs is authorized; action URLs can
repeat effects. Use recovery: true to also suppress configuration and provider scripts.
Owner-bound hosts can enable namedSessionAttachment: true on
createPlaywrightCli({ adapter, persistence, ... }) to support attach NAME.
It selects an existing live session or restores its committed resumable profile
for subsequent invocations with the same PLAYWRIGHT_CLI_SESSION default,
including an authenticated agent ID different from the target name. Explicit
-s=NAME overrides selection; detach retains the browser, while close retires it. See the
host capability contract.
Hosts accepting direct browser activity can call renewSession({ name, context })
to renew that exact retained session's idle deadline without browser commands.
Browser snapshots have no byte limit, including automatic snapshots after
navigation. Legacy maxSnapshotBytes settings are ignored.
playwright-cli --help and playwright-cli show --help report dashboard
availability. Without a host dashboard ability, interactive local browser login is unavailable;
use the authentication flow supplied by the host application.
For Cloudflare Workers, the exported cloudflareWorkerLimits profile also sets
commandLimits.archive: 4 MiB archive inputs, an 8 MiB ZIP input collection peak,
4 MiB entries, 8 MiB total payload and 1,000 members. These ceilings apply to
tar, zip and unzip, including nested calls and agentCommands(); tighter
registration limits still win. Oversized ZIP file metadata is rejected before
reading, and growing streams remain byte-bounded. ZIP decoding retains a bounded
archive in memory; codec workspace, parsed entries, filesystem storage and other
requests need additional headroom. Configure other command-family buffers at no
more than 8 MiB and bound concurrent requests at the host. Create a separate
Shell, environment object, and quota-wrapped filesystem view for each tenant or
request. Never reuse tenant state across requests; import withFileSystemQuota
from poe-code/safe-fs to bound cumulative writes, including command-initiated
copies and streaming output. Admission control and rate limiting remain host
responsibilities.
Command configuration
agentCommands() accepts replace (default false), an execute fallback for
nested command dispatch, and regex worker limits. Per-family options are
text, structured, search, diffPatch, metadata, archive, tableText,
streamInspection, streamFormat, split, timeEnv, tree, file, column,
htmlToMarkdown, du, expr, which, timeout, and applyPatch.
Use the typed options and linked family interfaces for
their individual limits and hooks, including clocks and schedulers. Family budgets
are separate from shell counters; limits.commandLimits.archive supplies
invocation ceilings. Per-execution family overrides merge with the shell's
profile, and non-Worker hosts can configure larger limits. replace applies
across the entire bundle.
Text, search, structured queries, directory inspection, table/stream tools,
time/environment commands, metadata, diff/patch, LLM providers, yes, less,
sponge, bc, htmlq, which, getopt, factor, and tsort
have unlimited resource budgets by default. Set individual family limits to opt
in; setting one limit leaves the others unlimited. Explicit Infinity also
disables a limit. xargs -s Infinity and --max-chars=Infinity explicitly remove
the command-size quota. Stream chunk sizes and polling intervals control execution
independently of these quotas. diff -u /dev/null FILE and its reverse produce
creation/deletion patches; top-level readable character and FIFO inputs are
read to EOF. Regular files retain identity-checked reads.
The package root exports createBoundedRegexProvider, BoundedRegexProvider,
and BoundedRegexProviderOptions. agentCommands() uses this provider by default;
pass regexExecutor to configure its resource limits explicitly:
import { agentCommands, createBoundedRegexProvider } from "@poe-platform/safe-bash";
shell.use(
agentCommands({
regexExecutor: createBoundedRegexProvider({ maxWorkers: 1, maxInputBytes: 65_536 }),
regex: { maxWorkers: 1 }
})
);Provider limits bound pattern/input/result bytes, matches, work, allocations,
states, and active workers. regex configures executor queue and timeout limits.
The default provider runs cooperatively; it does not provide native-worker or
process-memory isolation. No internal-module import is needed.
Environment variables
There are no package-specific runtime environment switches. Supply these through
env or set/export them inside a script; they refer to the virtual environment:
| Variables | Effect |
| --- | --- |
| HOME, CDPATH, PWD, OLDPWD | Home expansion, directory search, current and previous directory. The shell maintains PWD/OLDPWD on directory changes. |
| PATH | Virtual script lookup and which; never a host executable search. |
| IFS | Field splitting and read; defaults to space, tab, and newline. |
| LC_ALL, LC_CTYPE, LC_COLLATE, LANG | Character and collation behavior where supported; locale support varies by command. |
| TMPDIR | mktemp directory; defaults to /tmp, which must exist in the VFS. |
| TZ | date and touch timezone. date otherwise uses timeEnv.defaultTimeZone; touch defaults to UTC. |
| QUOTING_STYLE | stat filename quoting: literal, shell-always, or shell-escape-always. |
getopts starts with OPTIND=1 and OPTERR=1, updates OPTIND/OPTARG, and
honors changes made in the script. PIPESTATUS exposes pipeline stage statuses.
curl does not read proxy variables, host credentials, .curlrc, or .netrc.
curl --cacert VFSFILE reads a bounded PEM CA bundle from the virtual filesystem
and scopes its trust to that transfer, retaining certificate and hostname
verification. The Node transport supports it; Fetch rejects custom CA trust.
Explicit -K/--config files come from the VFS. Curl also supports --url-query,
negated boolean flags and --variable/--expand-* value options. HTTP version
selection and ignored Content-Length require explicit transport capabilities;
Node supports --http1.1. See the network options.
Following curl 8.5/8.10, --data-urlencode name@file sends no field when the
file is empty; use --data-urlencode name= to send an explicit empty value.
Repeated curl data options use curl 8.10.1 joining semantics: & is added only
when earlier fragments have produced bytes, so empty leading fragments add no separator.
curl --json retains its Content-Type: application/json and Accept: application/json
headers when -G or a 301/302/303 redirect removes the body. Explicit -H overrides
and suppression still apply within the original origin; cross-origin custom-header
and credential protections remain in effect.
Limitations
- This is a Bash-like interpreter, not full Bash or POSIX certification. No
background jobs/job control,
exec, process substitution, associative arrays, or C-stylefor ((…))loops.shoptsupportsdotglob,globstar,nullglob,nocaseglob, andnocasematch, plus-ofor supportedsetoptions.extglobcan be queried, printed, or unset; enabling it is unsupported. - Utilities implement subsets of their native counterparts' flags and behavior.
There is no
git,npm,npx, or fallback to installed host programs. The opt-innodecommand is not a general Node.js runtime. - Plugins, filesystem adapters, and runtime providers are trusted host JavaScript, not sandboxed code. Real storage and network plugins grant real access; URL allowlisting alone does not pin DNS or prevent access to private addresses.
- Cancellation is cooperative; it cannot undo completed effects or stop opaque
host work. Node
Shellruns activetimeout -k/--kill-afterchildren in a terminable worker and returns 137 after hard escalation. Custom commands, middleware and extensions require explicitworkerModulesfactories. Portable hosts require their own escalation policy. Nested worker escalation and finite shared interpreter quotas are refused; see the timeout profile. Limits do not bound total process memory.timeout --preserve-statusretains the child's status;--signal(-s) accepts Linux signal names and numbers and sets the cancellation exit status.trapsupports cleanup and inherited trap inspection by default. Host signal delivery requires the optional trap extension signal host. Signals fromtimeoutuse cooperative cancellation unless a kill-after worker policy is active; signal0lets the child finish, andKILLreports status 137 on expiry.
