README.md

# JustBash

A simulated bash environment with an in-memory virtual filesystem, written in Elixir.

Designed for AI agents that need a secure, sandboxed bash environment.

Supports optional network access via `curl` with secure-by-default URL filtering.

> **Note**: This is an Elixir port of [just-bash](https://github.com/vercel-labs/just-bash) by Vercel. The entire codebase was generated through conversational prompting with Claude Opus 4.5 via [OpenCode](https://opencode.ai).

## Security Model

- The shell only has access to the provided virtual filesystem
- No access to the real filesystem by default
- No network access by default
- Network access can be enabled with URL allowlists

## Installation

```elixir
def deps do
  [{:just_bash, "~> 0.1.0"}]
end
```

## Usage

### Basic API

```elixir
bash = JustBash.new()
{result, _} = JustBash.exec(bash, ~s(echo "Hello" > greeting.txt))
{result, _} = JustBash.exec(bash, "cat greeting.txt")
result.stdout  #=> "Hello\n"
result.exit_code  #=> 0
```

### Configuration

```elixir
bash = JustBash.new(
  files: %{"/data/file.txt" => "content"},  # Initial files
  env: %{"MY_VAR" => "value"},              # Environment variables
  cwd: "/app"                                # Starting directory
)
```

### Network Access

Network access is disabled by default. Enable it with allowlists:

```elixir
# Allow specific hosts
bash = JustBash.new(
  network: %{
    enabled: true,
    allow_list: ["api.github.com", "*.example.com"]
  }
)

# Custom HTTP client for testing
bash = JustBash.new(
  network: %{enabled: true},
  http_client: MyMockHttpClient
)
```

### Execute Script Files

```elixir
# Run a script from the real filesystem in the sandbox
{result, bash} = JustBash.exec_file("script.sh")

# With options
JustBash.exec_file("script.sh",
  files: %{"/data/input.txt" => "hello"},
  network: %{enabled: true}
)
```

### Sigil

```elixir
import JustBash.Sigil

result = ~b"echo hello"
result.stdout  #=> "hello\n"

# Modifiers
~b"echo hello"t  # trimmed output
~b"echo hello"s  # stdout only
~b"exit 42"e     # exit code
```

## Supported Commands

### File Operations

`cat`, `cp`, `file`, `find`, `ln`, `ls`, `mkdir`, `mv`, `readlink`, `rm`, `stat`, `touch`, `tree`, `du`

### Text Processing

`awk`, `base64`, `comm`, `cut`, `diff`, `expand`, `fold`, `grep`, `head`, `md5sum`, `nl`, `paste`, `rev`, `sed`, `sort`, `tac`, `tail`, `tr`, `uniq`, `wc`, `xargs`

### Data Processing

`jq` (JSON), `markdown` (Markdown → HTML)

### Network

`curl`

### Shell Builtins

`echo`, `printf`, `cd`, `pwd`, `export`, `unset`, `set`, `test`, `[`, `[[`, `true`, `false`, `:`, `source`, `.`, `read`, `exit`, `return`, `local`, `declare`, `break`, `continue`, `shift`, `getopts`, `trap`

### Utilities

`basename`, `dirname`, `date`, `env`, `hostname`, `printenv`, `seq`, `sleep`, `tee`, `which`

## Shell Features

- **Pipes**: `cmd1 | cmd2`
- **Redirections**: `>`, `>>`, `2>`, `&>`, `<`, `<<<`, heredocs
- **Command chaining**: `&&`, `||`, `;`
- **Variables**: `$VAR`, `${VAR}`, `${VAR:-default}`, `${VAR:=value}`, `${#VAR}`, `${VAR:start:len}`, `${VAR#pattern}`, `${VAR%pattern}`, `${VAR/old/new}`, `${VAR^^}`, `${VAR,,}`
- **Brace expansion**: `{a,b,c}`, `{1..10}`, `{a..z}`
- **Arithmetic**: `$((expr))` with full operators
- **Glob patterns**: `*`, `?`, `[...]`
- **Control flow**: `if/elif/else/fi`, `for/while/until`, `case/esac`
- **Functions**: `function name { ... }` or `name() { ... }`
- **Arrays**: `arr=(...)`, `${arr[0]}`, `${arr[@]}`, `${#arr[@]}`
- **Subshells**: `(cmd)` and command groups `{ cmd; }`

## Default Layout

When created without options, JustBash provides a Unix-like directory structure:

- `/home/user` - Default working directory (and `$HOME`)
- `/bin`, `/usr/bin` - Binary directories
- `/tmp` - Temporary files

## API Reference

```elixir
# Create environment
bash = JustBash.new(opts)

# Execute command
{result, bash} = JustBash.exec(bash, "command")
result.stdout      # String
result.stderr      # String
result.exit_code   # Integer
result.env         # Updated environment

# Parse without executing
{:ok, ast} = JustBash.parse("echo hello")

# Format script
{:ok, formatted} = JustBash.format("if true;then echo yes;fi")
```

## Development

```bash
mix deps.get
mix test           # 2400+ tests
mix dialyzer       # Type checking
mix credo          # Linting
```

## License

MIT