Metadata-Version: 2.4
Name: just-bash
Version: 0.1.4
Summary: A pure Python bash interpreter with in-memory virtual filesystem
Project-URL: Homepage, https://github.com/dbreunig/just-bash-py
Project-URL: Repository, https://github.com/dbreunig/just-bash-py
Author: Drew Breunig
License-Expression: Apache-2.0
Keywords: bash,interpreter,sandbox,shell,virtual-filesystem
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Interpreters
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: aiofiles>=23.0
Requires-Dist: aiohttp>=3.9
Provides-Extra: dev
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Description-Content-Type: text/markdown

# just-bash-py (pre-release)

[![PyPI version](https://badge.fury.io/py/just-bash.svg)](https://pypi.org/project/just-bash/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)

A pure Python bash interpreter with an in-memory virtual filesystem, designed for AI agents needing a secure, sandboxed bash environment.

This is a Python port of [just-bash](https://github.com/vercel-labs/just-bash), the emulated bash interpreter for TypeScript, from Vercel.

**This is a pre-release.** This as much a demonstration of coding agents' ability to implement software given a tight spec and high test coverage, as [discussed here](https://www.dbreunig.com/2026/01/08/a-software-library-with-no-code.html) and [here](https://github.com/dbreunig/whenwords).

## Features

- **Pure Python** - No external binaries, no WASM dependencies
- **Flexible filesystems** - In-memory, real filesystem access, copy-on-write overlays, or mount multiple sources
- **70+ commands** - grep, sed, awk, jq, curl, and more
- **Full bash syntax** - Pipes, redirections, variables, arrays, functions, control flow
- **32 shell builtins** - cd, export, declare, test, and more
- **Async execution** - Built on asyncio for non-blocking operation
- **Security limits** - Prevent infinite loops, excessive recursion, runaway execution

## Installation

```bash
pip install just-bash
```

## Quick Start

```python
from just_bash import Bash

bash = Bash()

# Simple command
result = bash.run('echo "Hello, World!"')
print(result.stdout)  # Hello, World!

# Pipes and text processing
result = bash.run('echo "banana apple cherry" | tr " " "\\n" | sort')
print(result.stdout)  # apple\nbanana\ncherry\n

# Variables and arithmetic
result = bash.run('x=5; echo $((x * 2))')
print(result.stdout)  # 10

# Arrays
result = bash.run('arr=(a b c); echo "${arr[@]}"')
print(result.stdout)  # a b c

# In-memory files
result = bash.run('echo "test" > /tmp/file.txt; cat /tmp/file.txt')
print(result.stdout)  # test
```

For async contexts, use `await bash.exec()` instead of `bash.run()`.

## Demo

Run the interactive demo to see all features in action:

```bash
python examples/demo.py
```

This demonstrates variables, arrays, control flow, pipes, text processing, JSON handling with jq, functions, and more.

## API

### Bash Class

```python
from just_bash import Bash

# Create with optional initial files
bash = Bash(files={
    "/data/input.txt": "line1\nline2\nline3\n",
    "/config.json": '{"key": "value"}'
})

# Execute commands
result = await bash.exec("cat /data/input.txt | wc -l")

# Result object
print(result.stdout)     # Standard output
print(result.stderr)     # Standard error
print(result.exit_code)  # Exit code (0 = success)
```

### Configuration Options

```python
bash = Bash(
    files={...},           # Initial filesystem contents
    env={...},             # Environment variables
    cwd="/home/user",      # Working directory
    network=NetworkConfig(...),  # Network configuration (for curl)
)
```

### Filesystem Options

just-bash provides four filesystem implementations for different use cases:

#### InMemoryFs (Default)

Pure in-memory filesystem - completely sandboxed with no disk access.

```python
from just_bash import Bash

# Default: in-memory filesystem with optional initial files
bash = Bash(files={
    "/data/input.txt": "hello world\n",
    "/config.json": '{"key": "value"}'
})

result = bash.run("cat /data/input.txt")
print(result.stdout)  # hello world
```

#### ReadWriteFs

Direct access to the real filesystem, rooted at a specific directory. All paths are translated relative to the root.

```python
from just_bash import Bash
from just_bash.fs import ReadWriteFs, ReadWriteFsOptions

# Access real files under /path/to/project
fs = ReadWriteFs(ReadWriteFsOptions(root="/path/to/project"))
bash = Bash(fs=fs, cwd="/")

# /src/main.py in bash maps to /path/to/project/src/main.py on disk
result = bash.run("cat /src/main.py")
```

**Warning**: ReadWriteFs provides direct disk access. Use with caution.

#### OverlayFs

Copy-on-write overlay - reads from the real filesystem, but all writes go to an in-memory layer. The real filesystem is never modified.

```python
from just_bash import Bash
from just_bash.fs import OverlayFs, OverlayFsOptions

# Overlay real files at /home/user/project, changes stay in memory
fs = OverlayFs(OverlayFsOptions(
    root="/path/to/real/project",
    mount_point="/home/user/project"
))
bash = Bash(fs=fs)

# Read real files
result = bash.run("cat /home/user/project/README.md")

# Writes only affect the in-memory layer
bash.run("echo 'modified' > /home/user/project/README.md")
# Real file on disk is unchanged!
```

Use cases:
- Safe experimentation with real project files
- Testing scripts without modifying actual files
- AI agents that need to read real code but not write to disk

#### MountableFs

Mount multiple filesystems at different paths, similar to Unix mount points.

```python
from just_bash import Bash
from just_bash.fs import (
    MountableFs, MountableFsOptions, MountConfig,
    InMemoryFs, ReadWriteFs, ReadWriteFsOptions, OverlayFs, OverlayFsOptions
)

# Create a mountable filesystem with multiple sources
fs = MountableFs(MountableFsOptions(
    base=InMemoryFs(),  # Default for paths outside mounts
    mounts=[
        # Mount real project at /project (read-write)
        MountConfig(
            mount_point="/project",
            filesystem=ReadWriteFs(ReadWriteFsOptions(root="/path/to/project"))
        ),
        # Mount another project as overlay (read-only to disk)
        MountConfig(
            mount_point="/reference",
            filesystem=OverlayFs(OverlayFsOptions(
                root="/path/to/other/project",
                mount_point="/"
            ))
        ),
    ]
))

bash = Bash(fs=fs)

# Access different filesystems through unified paths
bash.run("ls /project")      # Real filesystem
bash.run("ls /reference")    # Overlay filesystem
bash.run("ls /tmp")          # In-memory (base)
```

#### Direct Filesystem Access

You can also access the filesystem directly through the `bash.fs` property:

```python
import asyncio
from just_bash import Bash

bash = Bash(files={"/data.txt": "initial content"})

# Async filesystem operations
async def main():
    # Read
    content = await bash.fs.read_file("/data.txt")

    # Write
    await bash.fs.write_file("/output.txt", "new content")

    # Check existence
    exists = await bash.fs.exists("/data.txt")

    # List directory
    files = await bash.fs.readdir("/")

    # Get file stats
    stat = await bash.fs.stat("/data.txt")
    print(f"Size: {stat.size}, Mode: {oct(stat.mode)}")

asyncio.run(main())
```

## Security

- **No native execution** - All commands are pure Python implementations
- **Network disabled by default** - curl requires explicit enablement
- **Execution limits** - Prevents infinite loops and excessive resource usage
- **Filesystem isolation** - Virtual filesystem keeps host system safe
- **SQLite sandboxed** - Only in-memory databases allowed

## Supported Features

### Shell Syntax
- Variables: `$VAR`, `${VAR}`, `${VAR:-default}`, `${VAR:+alt}`, `${#VAR}`
- Arrays: `arr=(a b c)`, `${arr[0]}`, `${arr[@]}`, `${#arr[@]}`
- Arithmetic: `$((expr))`, `((expr))`, increment/decrement, ternary
- Quoting: Single quotes, double quotes, `$'...'`, escapes
- Expansion: Brace `{a,b}`, tilde `~`, glob `*.txt`, command `$(cmd)`
- Control flow: `if/then/else/fi`, `for/do/done`, `while`, `until`, `case`
- Functions: `func() { ... }`, local variables, return values
- Pipes: `cmd1 | cmd2 | cmd3`
- Redirections: `>`, `>>`, `<`, `2>&1`, here-docs

### Parameter Expansion
- Default values: `${var:-default}`, `${var:=default}`
- Substring: `${var:offset:length}`
- Pattern removal: `${var#pattern}`, `${var##pattern}`, `${var%pattern}`, `${var%%pattern}`
- Replacement: `${var/pattern/string}`, `${var//pattern/string}`
- Case modification: `${var^^}`, `${var,,}`, `${var^}`, `${var,}`
- Length: `${#var}`, `${#arr[@]}`
- Indirection: `${!var}`, `${!prefix*}`, `${!arr[@]}`
- Transforms: `${var@Q}`, `${var@a}`, `${var@A}`

### Conditionals
- Test command: `[ -f file ]`, `[ "$a" = "$b" ]`
- Extended test: `[[ $var == pattern ]]`, `[[ $var =~ regex ]]`
- Arithmetic test: `(( x > 5 ))`
- File tests: `-e`, `-f`, `-d`, `-r`, `-w`, `-x`, `-s`, `-L`
- String tests: `-z`, `-n`, `=`, `!=`, `<`, `>`
- Numeric tests: `-eq`, `-ne`, `-lt`, `-le`, `-gt`, `-ge`

## Shell Builtins

```
:         .         [         alias     break     builtin   cd        command
continue  declare   eval      exec      exit      export    false     let
local     mapfile   readarray readonly  return    set       shift     shopt
source    test      true      type      typeset   unalias   unset     wait
```

## Available Commands

### File Operations
```
cat       chmod     cp        find      ln        ls        mkdir     mv
rm        stat      touch     tree
```

### Text Processing
```
awk       column    comm      cut       diff      expand    fold      grep
egrep     fgrep     head      join      nl        od        paste     rev
rg        sed       sort      split     strings   tac       tail      tee
tr        unexpand  uniq      wc
```

### Data Processing
```
jq        yq        xan       sqlite3
```

#### xan - CSV Toolkit

The `xan` command provides CSV manipulation capabilities. Most commands are implemented:

**Implemented:**
```
headers     count       head        tail        slice       select
drop        rename      filter      search      sort        reverse
behead      enum        shuffle     sample      dedup       top
cat         transpose   fixlengths  flatten     explode     implode
split       view        stats       frequency   to json     from json
```

**Not Yet Implemented** (require expression evaluation):
```
join        agg         groupby     map         transform   pivot
```

Example usage:
```python
# Show column names
bash.run("xan headers data.csv")

# Filter and select
bash.run("xan filter 'age > 30' data.csv | xan select name,age")

# Convert to JSON
bash.run("xan to json data.csv")

# Sample random rows
bash.run("xan sample 10 --seed 42 data.csv")
```

### Path Utilities
```
basename  dirname   pwd       readlink  which
```

### Compression & Encoding
```
base64    gzip      gunzip    zcat      md5sum    sha1sum   sha256sum tar
```

### System & Environment
```
alias     clear     date      du        echo      env       expr      false
file      help      history   hostname  printenv  printf    read      seq
sleep     timeout   true      unalias   xargs
```

### Network
```
curl      (disabled by default)
```

### Shell
```
bash      sh
```

## License

Apache 2.0

## Acknowledgments

This project is a Python port of [just-bash](https://github.com/vercel-labs/just-bash) by Vercel. The TypeScript implementation provided the design patterns, test cases, and feature specifications that guided this Python implementation.
