just-bash: a full shell in pure TypeScript for AI agents
Here’s an uncomfortable truth about AI agents: the moment you give them shell access, you’re handing over the keys. An agent that can run rm -rf / or curl your internal APIs is an agent you can’t trust in production. But without shell access, agents lose the most powerful composition model in computing — pipes, redirects, and the entire Unix toolbox.
Vercel Labs decided to split the difference. Their package just-bash reimplements the bash shell and over 70 Unix commands — grep, sed, awk, jq, find, curl, the works — entirely in TypeScript. No native binaries. No host filesystem access. Everything runs in memory, in a virtual filesystem, with configurable execution limits. It’s bash, but defanged.
The package is at version 2.13.1, has shipped over 80 releases, and pulls somewhere between 47,000 and 71,000 weekly downloads from npm. For something still labeled beta, that’s real adoption.
The premise: bash is all you need
The idea came from a practical observation. Vercel’s d0 data agent reportedly jumped from 80% to 100% success rate on certain tasks when they switched to a pure bash-based approach. Instead of teaching the model a custom API for file manipulation, text processing, and data pipelines, they gave it bash. The model already knew bash.
This makes intuitive sense. Bash’s pipe-and-filter model — take some input, transform it, pass it along — maps cleanly to how agents chain operations. cat data.csv | grep "error" | wc -l is a three-step pipeline that any LLM can compose without learning a proprietary SDK. The composability is built into the language.
The bet is that the universal interface beats the bespoke one. Rather than building yet another tool-calling API, you give agents the same shell every developer has used for decades.
How it works
The core API is minimal. You create a Bash instance with a virtual filesystem and call exec():
import { Bash } from "just-bash";
const bash = new Bash({
files: {
"/data/input.csv": "name,age\nAlice,30\nBob,25\nCharlie,35",
},
env: { PROJECT: "analysis" },
cwd: "/app",
});
const result = await bash.exec('cat /data/input.csv | grep Alice');
// result.stdout → "Alice,30\n"
// result.exitCode → 0Each exec() call shares the filesystem — file changes persist across calls — but environment variables and function definitions are isolated per invocation. This means an agent can build up files over multiple steps without worrying about polluted shell state leaking between commands.
The filesystem layer is pluggable with four backends:
- InMemoryFs — the default, everything lives in memory
- OverlayFs — copy-on-write over a real directory, so you can seed it from disk without risking modifications
- ReadWriteFs — direct disk access, for when you actually want persistence
- MountableFs — a unified namespace that combines multiple backends under different mount points
File values can even be lazy-loaded functions that only execute on first read — handy for expensive initialization you might not need.
The whole thing runs in Node.js and browsers, requires no WASM for core functionality, and is licensed Apache-2.0.
What’s in the box
Seventy-plus commands is a lot. They break down roughly like this:
File operations: cat, cp, ln, ls, mkdir, mv, rm, stat, touch, tree — the standard toolkit for navigating and manipulating a filesystem.
Text processing: This is where it gets impressive. awk, grep (plus egrep and fgrep), sed, sort, uniq, cut, diff, jq, yq, wc, and even ripgrep (rg). These aren’t toy implementations — they handle real-world text processing pipelines.
Navigation and environment: cd, find, pwd, env, export, du, tee, hostname.
Shell utilities: alias, chmod, date, seq, sleep, timeout, history, which.
Network: curl with configurable allow-lists, plus an html-to-markdown converter.
Shell syntax coverage is broad too. Pipes, redirections (including 2>&1), command chaining with &&, ||, and ;, variable expansion with defaults (${VAR:-fallback}), glob patterns, conditionals, functions with local variables, for/while/until loops, and arithmetic expressions all work.
The gaps are deliberate. No job control (&, bg, fg), no running external binaries — only built-in commands execute — and arithmetic is 32-bit signed integers only. These aren’t limitations so much as boundaries. An agent doesn’t need background jobs. It needs to process text and manipulate files.
For cases where bash isn’t enough, optional WASM-based runtimes add Python (via CPython WASM), JavaScript/TypeScript (via QuickJS WASM), and SQLite (via sql.js). That covers a surprising range of what agents actually need to do.
The security model
This is really the point of the whole project. The security model has several layers:
No host access by default. The shell operates on a virtual filesystem. Unless you explicitly use ReadWriteFs, nothing touches disk.
Execution limits. Configurable caps prevent runaway behavior:
maxCallDepth: 100 (prevents infinite recursion)maxCommandCount: 10,000 (caps total commands per execution)maxLoopIterations: 10,000 (prevents infinite loops)- Similar limits for awk and sed operations
Network is opt-in. curl doesn’t work unless you configure explicit URL prefix allow-lists, and you can restrict HTTP methods per prefix. An agent can’t phone home or probe your internal network unless you specifically permit it.
Compare this to the naive alternative — child_process.exec() in Node.js. That gives an agent full shell access with zero sandboxing. Every command runs as whatever user started the process, with access to the real filesystem, network, and any credentials in the environment. For untrusted AI-generated commands, that’s a non-starter.
Extending with custom commands
The defineCommand() API lets you add domain-specific commands that integrate with the full shell:
import { Bash, defineCommand } from "just-bash";
const deploy = defineCommand({
name: "deploy",
run: async ({ args, fs, env, stdout }) => {
const target = args[0] || env.DEPLOY_TARGET;
const config = await fs.readFile(`/config/${target}.json`, "utf-8");
stdout.write(`Deploying to ${target}...\n`);
return 0;
},
});
const env = new Bash({
customCommands: [deploy],
files: { "/config/staging.json": '{"region": "us-east-1"}' },
});
await env.exec("deploy staging | tee /logs/deploy.log");Custom commands receive filesystem access, environment variables, stdin, and the ability to invoke subcommands. They work with pipes, redirections, and all standard shell features — they’re first-class citizens, not bolt-ons.
The ecosystem and alternatives
just-bash anchors a small ecosystem. bash-tool (~40,700 weekly downloads) wraps it as an AI SDK tool with bash, readFile, and writeFile capabilities. @ai-code-agents/just-bash integrates with the ai-code-agents SDK. The package also exposes a Sandbox class that’s API-compatible with @vercel/sandbox, letting you develop locally with just-bash and swap in a full VM for production.
How does it compare to the alternatives?
@vercel/sandbox is Vercel’s own full VM solution. Stronger isolation, supports arbitrary binaries, but heavier overhead. Vercel positions just-bash as the development companion to swap out for sandbox in production.
E2B offers cloud-hosted sandboxes for AI agents. Similar goals, but it’s a managed service — you’re paying per execution and adding network latency.
WebContainers by StackBlitz provide browser-based Node.js environments. More general-purpose, not specifically designed for the agent use case.
Docker containers offer battle-tested isolation but require infrastructure that doesn’t work in browsers.
just-bash’s sweet spot is clear: lightweight, in-process, runs anywhere JavaScript runs, purpose-built for AI agents. If you need full Linux compatibility or arbitrary binary execution, reach for a real sandbox. If you need fast, safe text processing and file manipulation for an agent, just-bash is probably enough.
The “Malte and Claude” footnote
One detail worth sitting with: the package.json lists the author as “Malte and Claude.” Malte Ubl — the creator of Google AMP, now at Vercel — co-authored this with Anthropic’s Claude. It’s right there in the metadata, not hidden in a README footnote.
There’s something recursive about this. An AI helped build a sandboxed shell designed for AIs to use. The tool and its builder share a feedback loop. I don’t know if that’s profound or just practical — probably both — but it does say something about where we are. The infrastructure layer for AI agents is increasingly being built by the agents themselves.
The repo has over 2,000 stars on GitHub and more than 300 commits, with Malte as the dominant contributor. For a project that’s only been public for a few months, that’s a remarkable pace.
When to reach for it
If you’re building an AI agent that needs to process text, manipulate files, or run data pipelines — and you don’t want to give it real shell access — just-bash is worth evaluating. Install it, seed a virtual filesystem with your data, and let the agent compose Unix commands against it.
Start with the in-memory filesystem and strict execution limits. Only open up OverlayFs or curl allow-lists when you’ve validated the agent’s behavior. Treat the security model like a ratchet: start tight, loosen deliberately.
The CLI binaries (just-bash for single commands, just-bash-shell for interactive sessions) are useful for testing your setup before wiring it into an agent. There’s also a live demo at justbash.dev if you want to poke at it without installing anything.
It’s beta software with rapid iteration — 81 releases and counting. Pin your version.