Library API (Rust)
shuck is also available as a Rust library (shuck on crates.io). Embed PTY-based command execution directly in your Rust programs — ideal for building agent runtimes, automation frameworks, and custom tooling.
Add to Cargo.toml
[dependencies]
shuck = "0.1"
Basic Usage
use shuck::{run, Config};
fn main() -> Result<(), shuck::Error> {
let result = run(Config {
command: "python3".into(),
args: vec!["-c".into(), "print('hello from shuck')".into()],
..Config::default()
})?;
println!("stdout: {}", result.stdout);
println!("exit_code: {}", result.exit_code);
println!("duration_ms: {}", result.duration_ms);
Ok(())
}
Config Struct
pub struct Config {
/// The command to run
pub command: String,
/// Arguments to pass to the command
pub args: Vec<String>,
/// Input to feed to stdin
pub input: Option<String>,
/// Timeout in seconds (None = no timeout)
pub timeout_secs: Option<u64>,
/// Don't strip ANSI sequences
pub raw: bool,
/// PTY column width (default: 80)
pub cols: u16,
/// PTY row count (default: 24)
pub rows: u16,
/// Extra environment variables
pub env: Vec<(String, String)>,
/// Suppress stderr from child
pub quiet: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
command: String::new(),
args: vec![],
input: None,
timeout_secs: Some(30),
raw: false,
cols: 80,
rows: 24,
env: vec![],
quiet: false,
}
}
}
Result Struct
pub struct ShuckResult {
/// Captured stdout (ANSI-stripped unless raw=true)
pub stdout: String,
/// Captured stderr from child
pub stderr: String,
/// Child exit code (or shuck error code)
pub exit_code: i32,
/// Wall-clock execution time
pub duration_ms: u64,
/// Whether the timeout was reached
pub timed_out: bool,
}
Error Type
pub enum Error {
/// Command not found in PATH
CommandNotFound(String),
/// Command found but not executable
NotExecutable(String),
/// PTY allocation failed
PtyAllocation(std::io::Error),
/// I/O error during execution
Io(std::io::Error),
/// Child process error
Process(std::io::Error),
}
Examples
With Input
let result = run(Config {
command: "python3".into(),
input: Some("print(2 + 2)".into()),
..Config::default()
})?;
assert_eq!(result.stdout.trim(), "4");
With Timeout
let result = run(Config {
command: "sleep".into(),
args: vec!["10".into()],
timeout_secs: Some(1),
..Config::default()
})?;
assert_eq!(result.exit_code, 124);
assert!(result.timed_out);
Using the Stripper Directly
use shuck::strip;
let raw = "\x1b[31mred text\x1b[0m\nnormal text";
let clean = strip(raw);
assert_eq!(clean, "red text\nnormal text");
Async Usage
shuck’s run is synchronous. For async contexts, use tokio::task::spawn_blocking:
use tokio::task;
let result = task::spawn_blocking(|| {
shuck::run(Config {
command: "my-tool".into(),
..Config::default()
})
}).await??;
Embedding in Agent Runtimes
The library API is designed for embedding shuck in agent runtimes. Use it to give your agent framework direct access to interactive CLI tools:
use shuck::{run, Config};
/// Agent tool: run an interactive CLI and return structured output.
fn agent_run_cli(
command: &str,
args: &[&str],
input: Option<&str>,
timeout: u64,
) -> Result<shuck::ShuckResult, shuck::Error> {
run(Config {
command: command.into(),
args: args.iter().map(|s| s.to_string()).collect(),
input: input.map(|s| s.to_string()),
timeout_secs: Some(timeout),
..Config::default()
})
}
// Agent checks database schema
let schema = agent_run_cli("psql", &["-U", "postgres", "mydb"], Some("\\dt"), 30)?;
// Agent checks package health
let health = agent_run_cli("pip", &["check"], None, 30)?;
// Agent inspects container
let status = agent_run_cli("docker", &["compose", "ps"], None, 15)?;