bashkit 0.5.0

Awesomely fast virtual sandbox with bash and file system
Documentation
# Clap Builtins

`ClapBuiltin` lets Rust applications define custom Bashkit commands with
`#[derive(clap::Parser)]` argument structs. Handlers receive a
`BashkitContext` that captures stdout, stderr, and exit code for the virtual
shell. `clap` is an unconditional dependency of `bashkit`, so this trait is
always available.

## Basic Usage

```rust
use bashkit::clap::Parser;
use bashkit::{Bash, BashkitContext, ClapBuiltin, async_trait};

#[derive(Parser)]
#[command(name = "greet", about = "Print a greeting")]
struct GreetArgs {
    #[arg(short, long, default_value = "World")]
    name: String,

    #[arg(short, long)]
    shout: bool,
}

struct Greet;

#[async_trait]
impl ClapBuiltin for Greet {
    type Args = GreetArgs;

    async fn execute_clap(
        &self,
        args: Self::Args,
        ctx: &mut BashkitContext<'_>,
    ) -> bashkit::Result<()> {
        let greeting = format!("Hello, {}!", args.name);
        let greeting = if args.shout {
            greeting.to_uppercase()
        } else {
            greeting
        };
        ctx.write_stdout(format!("{greeting}\n"));
        Ok(())
    }
}

#[tokio::main]
async fn main() -> bashkit::Result<()> {
    let mut bash = Bash::builder().builtin("greet", Box::new(Greet)).build();

    let result = bash.exec("greet --name Alice --shout").await?;
    assert_eq!(result.stdout, "HELLO, ALICE!\n");

    let help = bash.exec("greet --help").await?;
    assert_eq!(help.exit_code, 0);
    assert!(help.stdout.contains("Usage: greet"));
    assert!(help.stderr.is_empty());

    let error = bash.exec("greet --unknown").await?;
    assert_eq!(error.exit_code, 2);
    assert!(error.stderr.contains("unexpected argument"));
    Ok(())
}
```

## Subcommands

Use normal clap subcommands for nested command surfaces.

```rust
use bashkit::clap::{Parser, Subcommand};
use bashkit::{Bash, BashkitContext, ClapBuiltin, async_trait};

#[derive(Parser)]
#[command(name = "math")]
struct MathArgs {
    #[command(subcommand)]
    command: MathCommand,
}

#[derive(Subcommand)]
enum MathCommand {
    Add { left: i64, right: i64 },
    StdinLen,
}

struct Math;

#[async_trait]
impl ClapBuiltin for Math {
    type Args = MathArgs;

    async fn execute_clap(
        &self,
        args: Self::Args,
        ctx: &mut BashkitContext<'_>,
    ) -> bashkit::Result<()> {
        let value = match args.command {
            MathCommand::Add { left, right } => left + right,
            MathCommand::StdinLen => ctx.stdin().unwrap_or("").len() as i64,
        };
        ctx.write_stdout(format!("{value}\n"));
        Ok(())
    }
}

#[tokio::main]
async fn main() -> bashkit::Result<()> {
    let mut bash = Bash::builder().builtin("math", Box::new(Math)).build();

    let sum = bash.exec("math add 20 22").await?;
    assert_eq!(sum.stdout, "42\n");

    let stdin_len = bash.exec("printf abc | math stdin-len").await?;
    assert_eq!(stdin_len.stdout, "3\n");
    Ok(())
}
```

## Behavior

- The clap parser receives only the command arguments, not the shell command
  name from the script.
- `BashkitContext::write_stdout()` and `write_stderr()` append virtual command
  output; direct `println!`/`eprintln!` writes to the host process instead.
- Set non-zero command status with `ctx.set_exit_code(code)` or
  `ctx.fail(message, code)`.
- `--help` and `--version` return exit code `0` with clap output on stdout.
- Parse failures return clap's exit code, usually `2`, with diagnostics on
  stderr.
- Diagnostics are capped at 1 KB so custom builtins keep Bashkit's builtin
  error-output safety guarantees.

## See Also

- [`crates/bashkit/examples/clap_builtin.rs`]../examples/clap_builtin.rs
- [`crates/bashkit/examples/clap_builtin_subcommands.rs`]../examples/clap_builtin_subcommands.rs
- [`crates/bashkit/docs/custom_builtins.md`]./custom_builtins.md