rust-bash 0.3.0

A sandboxed bash interpreter for AI Agents with a virtual filesystem
Documentation
# Migrating from just-bash

## Overview

`rust-bash` is designed as a drop-in replacement for `just-bash` with an expanded feature set. This guide covers the key differences and how to migrate your code.

## Installation

```bash
# Remove just-bash
npm uninstall just-bash

# Install rust-bash
npm install rust-bash
```

## API Comparison

### Creating a Bash Instance

**just-bash:**

```typescript
import { Bash } from 'just-bash';

const bash = new Bash({
  files: { '/data.txt': 'hello' },
  env: { USER: 'agent' },
});
```

**rust-bash:**

```typescript
import { Bash, tryLoadNative, createNativeBackend, initWasm, createWasmBackend } from 'rust-bash';

// Choose a backend (native addon for speed, WASM for portability)
let createBackend;
if (await tryLoadNative()) {
  createBackend = createNativeBackend;
} else {
  await initWasm();
  createBackend = createWasmBackend;
}

const bash = await Bash.create(createBackend, {
  files: { '/data.txt': 'hello' },
  env: { USER: 'agent' },
});
```

**Key difference:** `Bash.create()` is async and requires a backend factory. The backend determines whether commands execute via the native Rust addon (near-native speed) or WASM.

### Executing Commands

**just-bash:**

```typescript
const result = bash.exec('echo hello');
console.log(result.stdout);   // "hello\n"
console.log(result.exitCode); // 0
```

**rust-bash:**

```typescript
const result = await bash.exec('echo hello');
console.log(result.stdout);   // "hello\n"
console.log(result.exitCode); // 0
```

**Key difference:** `exec()` returns a `Promise`. The result shape (`stdout`, `stderr`, `exitCode`) is the same.

### Custom Commands

**just-bash:**

```typescript
import { Bash, defineCommand } from 'just-bash';

const greet = defineCommand('greet', async (args, ctx) => {
  return { stdout: `Hello, ${args[0]}!\n`, stderr: '', exitCode: 0 };
});

const bash = new Bash({ customCommands: [greet] });
```

**rust-bash:**

```typescript
import { Bash, defineCommand } from 'rust-bash';

const greet = defineCommand('greet', async (args, ctx) => {
  return { stdout: `Hello, ${args[0]}!\n`, stderr: '', exitCode: 0 };
});

const bash = await Bash.create(createBackend, { customCommands: [greet] });
```

**Key difference:** The `defineCommand()` API is identical. Only the `Bash` construction changes.

### Execution Limits

**just-bash:**

```typescript
const bash = new Bash({
  executionLimits: { maxCommandCount: 1000, maxExecutionTimeSecs: 5 },
});
```

**rust-bash:**

```typescript
const bash = await Bash.create(createBackend, {
  executionLimits: { maxCommandCount: 1000, maxExecutionTimeSecs: 5 },
});
```

The `executionLimits` interface is the same. All 10 limits are supported:

| Limit | Default |
|-------|---------|
| `maxCallDepth` | 100 |
| `maxCommandCount` | 10,000 |
| `maxLoopIterations` | 10,000 |
| `maxExecutionTimeSecs` | 30 |
| `maxOutputSize` | 10 MB |
| `maxStringLength` | 10 MB |
| `maxGlobResults` | 100,000 |
| `maxSubstitutionDepth` | 50 |
| `maxHeredocSize` | 10 MB |
| `maxBraceExpansion` | 10,000 |

## Quick Migration Checklist

| Step | Change |
|------|--------|
| 1. Package | `just-bash``rust-bash` |
| 2. Import | `from 'just-bash'``from 'rust-bash'` |
| 3. Backend setup | Add backend detection (see above) |
| 4. Construction | `new Bash(opts)``await Bash.create(createBackend, opts)` |
| 5. Execution | `bash.exec(cmd)``await bash.exec(cmd)` |
| 6. Custom commands | No change — `defineCommand()` API is identical |
| 7. Limits | No change — same `executionLimits` interface |
| 8. Files | No change — same `files` option (eager + lazy supported) |

## New Features in rust-bash

Features available in `rust-bash` that aren't in `just-bash`:

### AI Tool Integration (Framework-Agnostic)

```typescript
import { bashToolDefinition, createBashToolHandler, formatToolForProvider } from 'rust-bash';

const { handler } = createBashToolHandler(createNativeBackend, {
  files: myFiles,
  maxOutputLength: 10000,
});

// Works with any AI framework — not locked to Vercel AI SDK
const openaiTool = formatToolForProvider(bashToolDefinition, 'openai');
const anthropicTool = formatToolForProvider(bashToolDefinition, 'anthropic');
```

### MCP Server

```bash
# Built-in MCP server for Claude Desktop, Cursor, VS Code
rust-bash --mcp
```

### Network Policy

```typescript
const bash = await Bash.create(createBackend, {
  network: {
    enabled: true,
    allowedUrlPrefixes: ['https://api.example.com/'],
    allowedMethods: ['GET', 'POST'],
  },
});

await bash.exec('curl https://api.example.com/data');
```

### Direct Filesystem Access

```typescript
bash.fs.writeFileSync('/output.txt', 'content');
const data = bash.fs.readFileSync('/output.txt');
bash.fs.mkdirSync('/dir', { recursive: true });
const entries = bash.fs.readdirSync('/');
```

### Native Node.js Addon

When the native addon is available, commands execute at near-native speed via napi-rs — significantly faster than the pure TypeScript interpreter in `just-bash`.

### Multiple Filesystem Backends (Rust)

When using the Rust API directly, you get additional filesystem backends:

- **OverlayFs** — copy-on-write over real files
- **ReadWriteFs** — direct host filesystem access
- **MountableFs** — compose backends at mount points

## Browser Usage

**just-bash:**

```typescript
import { Bash } from 'just-bash';
const bash = new Bash({ files: { '/data.txt': 'hello' } });
```

**rust-bash:**

```typescript
import { Bash, initWasm, createWasmBackend } from 'rust-bash/browser';

await initWasm();
const bash = await Bash.create(createWasmBackend, {
  files: { '/data.txt': 'hello' },
});
```

**Key difference:** Browser usage requires initializing the WASM module first with `initWasm()`. Use the `/browser` entry point for tree-shaking.

## Troubleshooting

### "Cannot find module 'rust-bash'"

Ensure you've installed the package:

```bash
npm install rust-bash
```

### "tryLoadNative is not available" / native addon not loading

The native addon requires platform-specific binaries. If they're not available for your platform, the package falls back to WASM automatically. This is normal — WASM provides the same functionality, just slightly slower.

### TypeScript types not resolving

Ensure your `tsconfig.json` has `"moduleResolution": "bundler"` or `"node16"` for proper ESM support:

```json
{
  "compilerOptions": {
    "moduleResolution": "bundler",
    "module": "ESNext",
    "target": "ES2022"
  }
}
```