Mirage TypeScript implementsDocumentation Index
Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
Use this file to discover all available pages before exploring further.
python3 as a shell builtin, backed by Pyodide (CPython compiled to WebAssembly). Behavior matches Python Mirage’s reference, with a few WASM-runtime divergences noted below. The same code path runs in Node and in the browser.
What works
- -c inline code
- Script file from any mount
- Piped stdin code
export FOO=bar is visible via os.environ, sys.argv[1:] reflects shell args, sys.exit(n) is honored, uncaught exceptions return exit 1 with traceback on stderr, missing script returns exit 1 with python3: <path>: No such file.
Setup
Pyodide is an optional peer dependency of@struktoai/mirage-core. Workspaces that never call python3 never load it.
npm install and yarn add work too.
If pyodide isn’t installed, python3 returns exit=127 with a helpful stderr message, and the workspace keeps running.
Limitations
Pyodide runs CPython in WebAssembly on the same JS thread. That creates these divergences from Python Mirage’s subprocess model:1. Shared module cache (sys.modules)
A single Pyodide interpreter serves all python3 calls in one workspace, so imports persist across calls.
import numpy is paid once) with no correctness impact, since Python imports are idempotent. User-level globals (foo = 1 at top level) do not leak; each call gets a fresh globals().
2. No true CPU parallelism within a workspace
Pyodide is single-interpreter-per-JS-thread, so concurrentpython3 calls in one workspace serialize via a JS queue.
sys.modules are fully isolated across workspaces.
3. No real OS file descriptors
sys.stdin, sys.stdout, sys.stderr are Python-level wrappers over in-memory buffers. Byte-level IO works:
sys.stdin.read(), input(), print(), .buffer.read/write() works. select, poll, fcntl, and os.read(fd, ...) on fd 0/1/2 don’t apply in WASM.
Reading and writing Mirage mounts from Python
Python code underpython3 can open() paths inside any Mirage-mounted prefix. Reads and writes route through the workspace’s mount layer (RAM, S3, OPFS, Slack, anything you’ve registered).
open() work too:
How it works
- Eager preload:
addMount(prefix, ...)walks the resource and populates Pyodide’s MEMFS atprefix. Subsequent reads from Python are sync and fast. - Flush on close: when Python
close()s a file under a mounted prefix, the bytes are flushed back through the workspace bridge. - Python
open()and friends:open(),pathlib.Path.write_text(),numpy.save,PIL.Image.save,pandas.to_csv. Anything that ultimately calls Python-levelopen()works. - C extensions calling
fopendirectly: see only the preloaded MEMFS snapshot (sqlite3, h5py). Most data-science libs use Pythonopenand just work; native FFI database drivers don’t.
Runtime requirements
The shim uses JSPI (JavaScript Promise Integration) so sync Python calls can drive async JS bridge ops.- Browser: Chrome 137+ (May 2025); Firefox behind
javascript.options.wasm_js_promise_integration. - Node: 24+ with
--experimental-wasm-jspi(the vitest config in this repo sets it). - No JSPI: reads of preloaded files still work, but
close()on a write under a mounted prefix throwsRuntimeError: Cannot stack switch.
What doesn’t work with the shim
- C extensions calling
fopendirectly (sqlite3, h5py): they only see the preloaded MEMFS state. Don’t read or modify mount-backed databases through these. - Stale listings: changes made to the underlying resource from outside this workspace aren’t picked up until the next
addMountcycle. - Concurrent writers: last-flush wins; no conflict detection.
What you cannot do
pip install at runtime
Pre-bundle what you need. Pyodide’s micropip isn’t wired into the python3 builtin yet.
Native CPython fallback
Mirage TS always uses Pyodide, neverchild_process.spawn('python3', ...), so behavior is identical in Node and in the browser.
Shell parser quirk (not python3-specific)
The tree-sitter-bash grammar strips newlines inside"...". For multi-line -c, use single quotes or a heredoc:
Quick reference
| Feature | Status |
|---|---|
python3 -c "..." | matches Python Mirage |
python3 -c multi-line | use single quotes or heredoc |
python3 /path/script.py (any mount) | matches Python Mirage |
echo code | python3 | matches Python Mirage |
python3 << EOF ... EOF (all variants) | matches Python Mirage |
os.environ reads session.env | matches Python Mirage |
sys.argv[1:] reflects shell args | matches Python Mirage |
sys.exit(n) | matches Python Mirage |
os.getcwd() reflects session.cwd | matches Python Mirage |
| Cross-workspace env isolation | own Pyodide per workspace |
| Cross-call env isolation | snapshot/restore per call |
sys.modules fresh per call | shared within workspace |
| True CPU parallelism within one workspace | serialized; use separate workspaces |
select() / poll() / fcntl() on stdin | no real fds in WASM |
open('/<mount>/...') inside Python | via FS shim, eager preload + flush on close |
pip install at runtime | pre-bundle instead |
| Native CPython fallback | always Pyodide |
| Browser support | Chrome 137+, Node 24+ with JSPI |