Builtin Library
README.md
shell Module
Public Luau surface over the __shell Internal FFI namespace — same
emulated Unix shell that powers the MCP bash tool.
Purpose
Wrap the raw __shell.* FFI namespace in a typed Luau table that gets
auto-injected as _G.shell via the prelude. 60+ POSIX-style builtins
(ls, cat, grep, find, echo, sed, awk, ...) run against the virtual
scene filesystem. Pipes, redirects, variables, and control flow all
work; shell.run returns the result table, shell.runAsync returns a
promise.
Where the command runs
shell.run runs the command on the calling thread — cheap, since the
VFS is in memory, and an ls or a cat costs microseconds.
Four builtins — run, luau, zm, zero — execute Luau, which the
engine runs on its frame loop, and the calling thread is that frame
loop. From a coroutine shell.run queues such a command to run off the
frame loop and yields until it finishes, so the caller still gets one
result table. From a thread that cannot yield it comes back as a result
whose stderr says so and names shell.runAsync.
That handoff runs the whole command line from the start, so it is taken
only for a line where every command reached one of those four builtins.
A line that mixes them with a command that already ran — zm add x; echo done >> log — returns the same refusal instead, since running the
line again would repeat the echo. Issue such a line through
shell.runAsync + task.await, which runs off the frame loop from the
first command.
Finding out what a builtin reads
help with no argument lists every builtin the shell carries, and a
builtin's own --help prints its usage. awk --help prints the grammar
the awk builtin parses: its options, the statements (print, printf,
assignment and the compound forms += -= *= /= %= ^=, if / else,
while, do ... while, the three-clause for, break, continue,
next, exit), the operators (arithmetic, ++ and -- in prefix and
postfix position, comparison, logical, ~ and !~, concatenation by
juxtaposition, $expr), the variables (NR, NF, FS, OFS, ORS,
OFMT, CONVFMT) and the functions it carries: the string set
(length, substr, index, split, sub, gsub, match,
sprintf, tolower, toupper) and the arithmetic set (int, sqrt,
log, exp, sin, cos, atan2, rand, srand). An argument
outside an arithmetic function's domain answers a value with no finite
decimal form and names itself on stderr, leaving the program running.
srand seeds the stream rand draws from and reports the seed it was
running from, so a program that names a seed replays its own sequence.
Usage
-- Returns the result table once the command has completed
local r = shell.run("ls /zero/source")
print(r.stdout)
if not r.ok then warn(r.stderr, r.exitCode) end
-- Async — returns a promise; the resolved value is a JSON string
local json = task.await(shell.runAsync("find /zero -name '*.luau'"))
Exports
shell.run(command) -> ShellResult— returns{ stdout, stderr, exitCode, ok }.shell.runAsync(command) -> string— async promise ID resolving to the JSON-encoded result string.