use anyhow::{Context, Result};
use colored::Colorize;
use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
use crate::config::{WrapConfig, WrapMode};
use kindly_guard_server::{Config as ServerConfig, SecurityScanner, Severity};
pub async fn wrap_command(command: Vec<String>, server: String, block: bool) -> Result<()> {
if command.is_empty() {
anyhow::bail!("No command specified");
}
println!("{} Active", "🛡️ KindlyGuard Protection:".green().bold());
println!("{} {}", "Server:".dimmed(), server);
println!(
"{} {}",
"Mode:".dimmed(),
if block { "Blocking" } else { "Warning" }
);
println!();
let mut config = ServerConfig::default();
config.scanner.allow_text_control_chars = true;
let scanner = SecurityScanner::new(config.scanner)?;
let program = &command[0];
let args = &command[1..];
let mut child = Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.context("Failed to start command")?;
let mut stdin = child.stdin.take().context("Failed to get stdin")?;
let stdout = child.stdout.take().context("Failed to get stdout")?;
let stderr = child.stderr.take().context("Failed to get stderr")?;
let stdout_handle = tokio::spawn(async move {
let reader = BufReader::new(stdout);
for line in reader.lines() {
match line {
Ok(content) => println!("{}", content),
Err(e) => eprintln!("Error reading stdout: {}", e),
}
}
});
let stderr_handle = tokio::spawn(async move {
let reader = BufReader::new(stderr);
for line in reader.lines() {
match line {
Ok(content) => eprintln!("{}", content),
Err(e) => eprintln!("Error reading stderr: {}", e),
}
}
});
let stdin_reader = std::io::stdin();
let mut stdin_buf = String::new();
loop {
stdin_buf.clear();
match stdin_reader.read_line(&mut stdin_buf) {
Ok(0) => break, Ok(_) => {
let threats = scanner.scan_text(&stdin_buf)?;
let max_severity = threats
.iter()
.map(|t| t.severity)
.max()
.unwrap_or(Severity::Low);
let colored_input = match (threats.is_empty(), max_severity) {
(true, _) => {
format!("▶ {}", stdin_buf.trim()).green().to_string()
},
(false, Severity::Low | Severity::Medium) => {
format!("▶ {}", stdin_buf.trim()).yellow().to_string()
},
(false, Severity::High | Severity::Critical) => {
format!("▶ {}", stdin_buf.trim()).red().to_string()
},
};
eprintln!("{}", colored_input);
if !threats.is_empty() {
eprintln!("{}", "⚠️ THREAT DETECTED".red().bold());
for threat in &threats {
eprintln!(" {} {}", "•".red(), threat);
}
if block {
eprintln!("{}", "❌ Input blocked for safety".red());
eprintln!();
continue; } else {
eprintln!("{}", "⚠️ Proceeding with caution...".yellow());
eprintln!();
}
}
stdin.write_all(stdin_buf.as_bytes())?;
stdin.flush()?;
},
Err(e) => {
eprintln!("Error reading input: {}", e);
break;
},
}
}
drop(stdin);
stdout_handle.await?;
stderr_handle.await?;
let status = child.wait()?;
println!();
println!(
"{} Session ended",
"🛡️ KindlyGuard Protection:".green().bold()
);
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
Ok(())
}
pub async fn wrap_command_with_config(
command: Vec<String>,
config: Option<WrapConfig>,
) -> Result<()> {
if command.is_empty() {
anyhow::bail!("No command specified");
}
let config = match config {
Some(c) => c,
None => WrapConfig::load()?,
};
let cmd_name = &command[0];
if !config.should_wrap(cmd_name) {
let status = Command::new(cmd_name)
.args(&command[1..])
.status()
.context("Failed to execute command")?;
if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
return Ok(());
}
let block = matches!(config.mode, WrapMode::Blocking);
let server = config.server.clone();
wrap_command(command, server, block).await
}
pub async fn init_wrap_config() -> Result<()> {
WrapConfig::create_default_config()
}
pub async fn show_wrap_config() -> Result<()> {
let config = WrapConfig::load()?;
println!("{}", "KindlyGuard Wrap Configuration".green().bold());
println!();
println!(
"{}: {}",
"Enabled".dimmed(),
if config.enabled {
"Yes".green()
} else {
"No".red()
}
);
println!("{}: {}", "Mode".dimmed(), config.mode_string());
println!("{}: {}", "Server".dimmed(), config.server);
println!(
"{}: {}",
"Verbose".dimmed(),
if config.verbose { "Yes" } else { "No" }
);
println!(
"{}: {}",
"Log Sessions".dimmed(),
if config.log_sessions { "Yes" } else { "No" }
);
if let Some(log_dir) = &config.log_directory {
println!("{}: {}", "Log Directory".dimmed(), log_dir.display());
}
println!();
println!("{}", "Wrapped Commands:".dimmed());
let mut commands: Vec<_> = config.commands.iter().cloned().collect();
commands.sort();
for cmd in commands {
let is_custom = config.custom_commands.contains(&cmd);
if is_custom {
println!(" • {} {}", cmd, "(custom)".dimmed());
} else {
println!(" • {}", cmd);
}
}
Ok(())
}
pub async fn add_wrap_command(command: String) -> Result<()> {
let mut config = WrapConfig::load()?;
if config.commands.contains(&command) {
println!("{} {} is already in the wrap list", "ℹ".blue(), command);
return Ok(());
}
config.add_command(command.clone());
config.save()?;
println!(
"{} Added {} to wrap list",
"✓".green(),
command.green().bold()
);
Ok(())
}
pub async fn remove_wrap_command(command: String) -> Result<()> {
let mut config = WrapConfig::load()?;
if config.remove_command(&command) {
config.save()?;
println!(
"{} Removed {} from wrap list",
"✓".green(),
command.green().bold()
);
} else {
println!("{} {} is not in the wrap list", "ℹ".blue(), command);
}
Ok(())
}