# Bashkit

[![CI](https://github.com/everruns/bashkit/actions/workflows/ci.yml/badge.svg)](https://github.com/everruns/bashkit/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Crates.io](https://img.shields.io/crates/v/bashkit.svg)](https://crates.io/crates/bashkit)
[![docs.rs](https://img.shields.io/docsrs/bashkit)](https://docs.rs/bashkit)
[![Repo: Agent Friendly](https://img.shields.io/badge/Repo-Agent%20Friendly-blue)](AGENTS.md)

Virtual bash interpreter for multi-tenant environments. Written in Rust.

## Features

- **POSIX compliant** - Substantial IEEE 1003.1-2024 Shell Command Language compliance
- **Sandboxed, in-process execution** - No real filesystem access by default
- **Virtual filesystem** - InMemoryFs, OverlayFs, MountableFs
- **Resource limits** - Command count, loop iterations, function depth
- **Network allowlist** - Control HTTP access per-domain
- **Custom builtins** - Extend with domain-specific commands
- **Async-first** - Built on tokio
- **Experimental: Git support** - Virtual git operations on the virtual filesystem (`git` feature)
- **Experimental: Python support** - Embedded Python interpreter via [Monty](https://github.com/pydantic/monty) (`python` feature)

## Install

```bash
cargo add bashkit
```

Or add to `Cargo.toml`:

```toml
[dependencies]
bashkit = "0.1"
```

Optional features:

```bash
cargo add bashkit --features git      # Virtual git operations
```

## Quick Start

```rust
use bashkit::Bash;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut bash = Bash::new();
    let result = bash.exec("echo hello world").await?;
    println!("{}", result.stdout); // "hello world\n"
    Ok(())
}
```

## LLM Tool Contract

`BashTool` follows the toolkit-library contract: builder for reusable config,
immutable tool metadata for discovery, and single-use executions for each call.

```rust
use bashkit::{BashTool, Tool};
use futures::StreamExt;

# #[tokio::main]
# async fn main() -> anyhow::Result<()> {
let tool = BashTool::builder()
    .username("agent")
    .hostname("sandbox")
    .build();

println!("{}", tool.description());
println!("{}", tool.system_prompt());

let execution = tool.execution(serde_json::json!({
    "commands": "printf 'hello\nworld\n'"
}))?;
let mut stream = execution.output_stream().expect("stream available");

let handle = tokio::spawn(async move { execution.execute().await });
while let Some(chunk) = stream.next().await {
    println!("{}: {}", chunk.kind, chunk.data);
}

let output = handle.await??;
assert_eq!(output.result["stdout"], "hello\nworld\n");
# Ok(())
# }
```

## Overview

<div align="center">
  <a href="https://www.youtube.com/watch?v=0rIGX7mSlMg">
    <img src="assets/overview-thumb.jpg" alt="Watch the overview video" width="600">
    <br>
    <strong>▶ Watch the 10-minute overview</strong>
  </a>
</div>

## Built-in Commands (150)

| Category | Commands |
|----------|----------|
| Core | `echo`, `printf`, `cat`, `nl`, `read` |
| Navigation | `cd`, `pwd`, `ls`, `find`, `pushd`, `popd`, `dirs` |
| Flow control | `true`, `false`, `exit`, `return`, `break`, `continue`, `test`, `[` |
| Variables | `export`, `set`, `unset`, `local`, `shift`, `source`, `.`, `eval`, `readonly`, `times`, `declare`, `typeset`, `let` |
| Shell | `bash`, `sh` (virtual re-invocation), `:`, `trap`, `caller`, `getopts`, `shopt` |
| Text processing | `grep`, `sed`, `awk`, `jq`, `head`, `tail`, `sort`, `uniq`, `cut`, `tr`, `wc`, `paste`, `column`, `diff`, `comm`, `strings`, `tac`, `rev`, `seq`, `expr` |
| File operations | `mkdir`, `mktemp`, `rm`, `cp`, `mv`, `touch`, `chmod`, `chown`, `ln`, `rmdir`, `realpath` |
| File inspection | `file`, `stat`, `less` |
| Archives | `tar`, `gzip`, `gunzip` |
| Byte tools | `od`, `xxd`, `hexdump` |
| Utilities | `sleep`, `date`, `basename`, `dirname`, `timeout`, `wait`, `watch`, `yes`, `kill` |
| Disk | `df`, `du` |
| Pipeline | `xargs`, `tee` |
| System info | `whoami`, `hostname`, `uname`, `id`, `env`, `printenv`, `history` |
| Network | `curl`, `wget` (requires allowlist) |
| Experimental | `python`, `python3` (requires `python` feature), `git` (requires `git` feature) |

## Shell Features

- Variables and parameter expansion (`$VAR`, `${VAR:-default}`, `${#VAR}`)
- Command substitution (`$(cmd)`)
- Arithmetic expansion (`$((1 + 2))`)
- Pipelines and redirections (`|`, `>`, `>>`, `<`, `<<<`, `2>&1`)
- Control flow (`if`/`elif`/`else`, `for`, `while`, `case`)
- Functions (POSIX and bash-style)
- Arrays (`arr=(a b c)`, `${arr[@]}`, `${#arr[@]}`)
- Glob expansion (`*`, `?`)
- Here documents (`<<EOF`)

## Configuration

```rust
use bashkit::{Bash, ExecutionLimits, InMemoryFs};
use std::sync::Arc;

let limits = ExecutionLimits::new()
    .max_commands(1000)
    .max_loop_iterations(10000)
    .max_function_depth(100);

let mut bash = Bash::builder()
    .fs(Arc::new(InMemoryFs::new()))
    .env("HOME", "/home/user")
    .cwd("/home/user")
    .limits(limits)
    .build();
```

### Virtual Identity

Configure the virtual username and hostname for `whoami`, `hostname`, `id`, and `uname`:

```rust
let mut bash = Bash::builder()
    .username("deploy")      // Sets whoami, id, and $USER env var
    .hostname("my-server")   // Sets hostname, uname -n
    .build();

// whoami → "deploy"
// hostname → "my-server"
// id → "uid=1000(deploy) gid=1000(deploy)..."
// echo $USER → "deploy"
```

## Experimental: Git Support

Enable the `git` feature for virtual git operations on the virtual filesystem.
All git data lives in the VFS — no host filesystem access.

```toml
[dependencies]
bashkit = { version = "0.1", features = ["git"] }
```

```rust
use bashkit::{Bash, GitConfig};

let mut bash = Bash::builder()
    .git(GitConfig::new()
        .author("Deploy Bot", "deploy@example.com"))
    .build();

// Local operations: init, add, commit, status, log
// Branch operations: branch, checkout, diff, reset
// Remote operations: remote add/remove, clone/push/pull/fetch (virtual mode)
```

See [specs/010-git-support.md](specs/010-git-support.md) for the full specification.

## Experimental: Python Support

Enable the `python` feature to embed the [Monty](https://github.com/pydantic/monty) Python interpreter (pure Rust, Python 3.12).
Python code runs in-memory with configurable resource limits and VFS bridging — files created
by bash are readable from Python and vice versa.

```toml
[dependencies]
bashkit = { version = "0.1", features = ["python"] }
```

```rust
use bashkit::Bash;

let mut bash = Bash::builder().python().build();

// Inline code
bash.exec("python3 -c \"print(2 ** 10)\"").await?;

// Script files from VFS
bash.exec("python3 /tmp/script.py").await?;

// VFS bridging: pathlib.Path operations work with the virtual filesystem
bash.exec(r#"python3 -c "
from pathlib import Path
Path('/tmp/data.txt').write_text('hello from python')
""#).await?;
bash.exec("cat /tmp/data.txt").await?; // "hello from python"
```

Stdlib modules: `math`, `re`, `pathlib`, `os` (getenv/environ), `sys`, `typing`.
Limitations: no `open()` (use `pathlib.Path`), no network, no classes, no third-party imports.
See [crates/bashkit/docs/python.md](crates/bashkit/docs/python.md) for the full guide.

## Virtual Filesystem

```rust
use bashkit::{InMemoryFs, OverlayFs, MountableFs, FileSystem};
use std::sync::Arc;

// Layer filesystems
let base = Arc::new(InMemoryFs::new());
let overlay = Arc::new(OverlayFs::new(base));

// Mount points
let mut mountable = MountableFs::new(Arc::new(InMemoryFs::new()));
mountable.mount("/data", Arc::new(InMemoryFs::new()));
```

## CLI Usage

```bash
# Run a script
bashkit-cli run script.sh

# Interactive REPL
bashkit-cli repl
```

## Development

```bash
just build        # Build project
just test         # Run tests
just check        # fmt + clippy + test
just pre-pr       # Pre-PR checks
```

## LLM Eval Results

Bashkit includes an [eval harness](crates/bashkit-eval/) that measures how well LLMs use bashkit as a bash tool in agentic workloads — 58 tasks across 15 categories.

| Model | Score | Tasks Passed | Tool Call Success | Duration |
|-------|-------|-------------|-------------------|----------|
| Claude Haiku 4.5 | **97%** | **54/58** | 88% | 8.6 min |
| Claude Sonnet 4.6 | 93% | 48/58 | 85% | 20.5 min |
| Claude Opus 4.6 | 91% | 50/58 | 88% | 20.1 min |
| GPT-5.3-Codex | 91% | 51/58 | 83% | 19.6 min |
| GPT-5.2 | 77% | 41/58 | 67% | 7.0 min |

**Delta from v0.1.7** (on shared 37 tasks): Haiku 98%→100%, Opus 93%→96%, GPT-5.2 86%→86% (3 more tasks). Interpreter fixes unblocked `json_to_csv_export` and `script_function_lib` across models. See the [detailed analysis](crates/bashkit-eval/README.md#results).

```bash
just eval                    # Run eval with default model
just eval-save               # Run and save results
```

## Benchmarks

Bashkit includes a benchmark tool to compare performance against bash and just-bash.

```bash
just bench              # Quick benchmark run
just bench --save       # Save results with system identifier
just bench-verbose      # Detailed output
just bench-list         # List all benchmarks
```

See [crates/bashkit-bench/README.md](crates/bashkit-bench/README.md) for methodology and assumptions.

## Python Bindings

Python bindings with LangChain integration are available in [crates/bashkit-python](crates/bashkit-python/README.md).

```python
from bashkit import BashTool

tool = BashTool()
print(tool.description())
print(tool.help())
result = await tool.execute("echo 'Hello, World!'")
print(result.stdout)
```

## Security

Bashkit is designed as a virtual interpreter with sandboxed execution for untrusted scripts. See the [security policy](SECURITY.md) for reporting vulnerabilities and the [threat model](specs/006-threat-model.md) for detailed analysis of 60+ identified threats.

## Other Virtual Bash Implementations

- **[just-bash](https://github.com/vercel-labs/just-bash)** (TypeScript, Apache-2.0) — Virtual bash interpreter for AI agents by Vercel Labs. Custom recursive descent parser, 75+ reimplemented commands (including full awk/sed/jq), in-memory VFS, defense-in-depth sandboxing, AST transform plugins. Runs in Node.js and browser.
- **[gbash](https://github.com/ewhauser/gbash)** (Go, Apache-2.0) — Deterministic, sandbox-only bash runtime for AI agents. Delegates parsing to `mvdan/sh`. Registry-backed commands, policy enforcement, structured tracing, JSON-RPC server mode.

## Acknowledgments

Bashkit is an independent implementation that draws design inspiration from several open source projects:

- **[just-bash](https://github.com/vercel-labs/just-bash)** (Vercel Labs, Apache-2.0) — Pioneered the idea of a virtual bash interpreter for AI-powered environments. Bashkit's sandboxing architecture and multi-tenant design was inspired by their approach.
- **[Oils](https://github.com/oilshell/oil)** (Andy Chu, Apache-2.0) — Comprehensive bash compatibility testing approach inspired our spec test methodology.
- **[One True AWK](https://github.com/onetrueawk/awk)** (Lucent Technologies) — AWK language semantics reference for our awk builtin.
- **[jq](https://github.com/jqlang/jq)** (Stephen Dolan, MIT) — jq query syntax and behavior reference. Our implementation uses the [jaq](https://github.com/01mf02/jaq) Rust crates.

No code was copied from any of these projects. See [NOTICE](NOTICE) for full details.

## Ecosystem

Bashkit is part of the [Everruns](https://everruns.com) ecosystem.

## License

MIT
