From aee30c8f2a63e158cfa6c91229d2a7813536860d Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 05:35:07 +0000 Subject: [PATCH] feat(bashkit-cli): Add CLI binary for command line usage Add bashkit-cli crate with command line interface: - bashkit -c 'command' - Execute command string - bashkit script.sh - Execute script file - bashkit --help / --version Features: - Proper exit codes - stdout/stderr handling - anyhow for error handling Usage examples: bashkit -c 'echo hello' bashkit -c 'arr=(a b c); echo ${arr[@]}' bashkit myscript.sh Tests: 104 passing (library tests) https://claude.ai/code/session_01A16cD8ztbTJs2PB2iHe1Ua --- crates/bashkit-cli/Cargo.toml | 21 +++++++++++ crates/bashkit-cli/src/main.rs | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 crates/bashkit-cli/Cargo.toml create mode 100644 crates/bashkit-cli/src/main.rs diff --git a/crates/bashkit-cli/Cargo.toml b/crates/bashkit-cli/Cargo.toml new file mode 100644 index 0000000..00f012c --- /dev/null +++ b/crates/bashkit-cli/Cargo.toml @@ -0,0 +1,21 @@ +# BashKit CLI - Command line interface for bashkit +# Run bash scripts in a sandboxed environment + +[package] +name = "bashkit-cli" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true +description = "Command line interface for BashKit sandboxed bash interpreter" + +[[bin]] +name = "bashkit" +path = "src/main.rs" + +[dependencies] +bashkit = { path = "../bashkit" } +tokio.workspace = true +clap.workspace = true +anyhow.workspace = true diff --git a/crates/bashkit-cli/src/main.rs b/crates/bashkit-cli/src/main.rs new file mode 100644 index 0000000..7cb39a9 --- /dev/null +++ b/crates/bashkit-cli/src/main.rs @@ -0,0 +1,66 @@ +//! BashKit CLI - Command line interface for sandboxed bash execution +//! +//! Usage: +//! bashkit -c 'echo hello' # Execute a command string +//! bashkit script.sh # Execute a script file +//! bashkit # Interactive REPL (not yet implemented) + +use anyhow::{Context, Result}; +use clap::Parser; +use std::path::PathBuf; + +/// BashKit - Sandboxed bash interpreter +#[derive(Parser, Debug)] +#[command(name = "bashkit")] +#[command(author, version, about, long_about = None)] +struct Args { + /// Execute the given command string + #[arg(short = 'c')] + command: Option, + + /// Script file to execute + #[arg()] + script: Option, + + /// Arguments to pass to the script + #[arg(trailing_var_arg = true)] + args: Vec, +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Args::parse(); + + let mut bash = bashkit::Bash::new(); + + // Execute command string if provided + if let Some(cmd) = args.command { + let result = bash.exec(&cmd).await.context("Failed to execute command")?; + print!("{}", result.stdout); + if !result.stderr.is_empty() { + eprint!("{}", result.stderr); + } + std::process::exit(result.exit_code); + } + + // Execute script file if provided + if let Some(script_path) = args.script { + let script = std::fs::read_to_string(&script_path) + .with_context(|| format!("Failed to read script: {}", script_path.display()))?; + + let result = bash + .exec(&script) + .await + .context("Failed to execute script")?; + print!("{}", result.stdout); + if !result.stderr.is_empty() { + eprint!("{}", result.stderr); + } + std::process::exit(result.exit_code); + } + + // Interactive REPL (not yet implemented) + eprintln!("bashkit: interactive mode not yet implemented"); + eprintln!("Usage: bashkit -c 'command' or bashkit script.sh"); + std::process::exit(1); +}