pub mod fickling;
pub mod mcp_scan;
pub mod modelscan;
pub mod picklescan;
#[derive(Debug)]
pub enum CliError {
NotInstalled(String),
CallFailed(String),
}
impl std::fmt::Display for CliError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotInstalled(msg) => write!(f, "tool not installed: {}", msg),
Self::CallFailed(msg) => write!(f, "tool call failed: {}", msg),
}
}
}
impl std::error::Error for CliError {}
pub struct CliToolBridge {
pub binary: String,
}
impl CliToolBridge {
pub fn new(binary: impl Into<String>) -> Self {
Self {
binary: binary.into(),
}
}
pub fn is_available(&self) -> bool {
which::which(&self.binary).is_ok()
}
pub async fn run(&self, args: &[&str]) -> Result<(i32, String, String), CliError> {
if !self.is_available() {
return Err(CliError::NotInstalled(format!(
"{} not found on PATH",
self.binary
)));
}
let output = tokio::process::Command::new(&self.binary)
.args(args)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.output()
.await
.map_err(|e| CliError::CallFailed(format!("Failed to spawn {}: {}", self.binary, e)))?;
let exit_code = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
Ok((exit_code, stdout, stderr))
}
}