use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use std::io::Write;
#[derive(Debug, Clone, Args)]
pub struct ShieldCommand {
#[command(subcommand)]
pub command: ShieldSubcommand,
}
#[derive(Debug, Clone, Subcommand)]
pub enum ShieldSubcommand {
AutoWrap(AutoWrapArgs),
}
#[derive(Debug, Clone, Args)]
pub struct AutoWrapArgs {
#[arg(short = 's', long, default_value = "bash")]
shell: String,
#[arg(short = 'o', long)]
output: Option<String>,
#[arg(short = 'c', long)]
commands: Vec<String>,
#[arg(long)]
no_defaults: bool,
}
impl ShieldCommand {
pub async fn run(self) -> Result<()> {
match self.command {
ShieldSubcommand::AutoWrap(args) => execute_auto_wrap(args).await,
}
}
}
async fn execute_auto_wrap(args: AutoWrapArgs) -> Result<()> {
let mut ai_commands = if args.no_defaults {
vec![]
} else {
vec![
"claude".to_string(),
"openai".to_string(),
"gemini".to_string(),
"gpt".to_string(),
"chatgpt".to_string(),
"llm".to_string(),
"ai".to_string(),
"ollama".to_string(),
"anthropic".to_string(),
"bard".to_string(),
]
};
ai_commands.extend(args.commands);
ai_commands.sort();
ai_commands.dedup();
let shell_script = match args.shell.as_str() {
"bash" | "zsh" => generate_bash_functions(&ai_commands),
shell => {
return Err(anyhow::anyhow!(
"Unsupported shell: {}. Currently only bash and zsh are supported.",
shell
))
},
};
if let Some(output_path) = args.output {
std::fs::write(&output_path, &shell_script)
.with_context(|| format!("Failed to write to {}", output_path))?;
println!("✓ Generated shield wrapper functions in: {}", output_path);
println!("\nTo activate, add this to your shell config:");
println!(" source {}", output_path);
} else {
std::io::stdout()
.write_all(shell_script.as_bytes())
.with_context(|| "Failed to write to stdout")?;
}
Ok(())
}
fn generate_bash_functions(commands: &[String]) -> String {
let mut script = String::new();
script.push_str("#!/bin/bash\n");
script.push_str("# KindlyGuard Shield - AI CLI Security Wrappers\n");
script.push_str("# Generated by: kindly shield auto-wrap\n");
script.push_str("# This file wraps AI CLI commands with security scanning\n\n");
script.push_str("# Check if kindly is installed\n");
script.push_str("if ! command -v kindly &> /dev/null; then\n");
script.push_str(
" echo \"Warning: kindly command not found. Shield protection disabled.\" >&2\n",
);
script.push_str(" echo \"Install with: cargo install kindly-tools\" >&2\n");
script.push_str(" KINDLY_SHIELD_DISABLED=1\n");
script.push_str("fi\n\n");
script.push_str("# Core shield function that wraps AI commands\n");
script.push_str("__kindly_shield_wrap() {\n");
script.push_str(" local cmd=\"$1\"\n");
script.push_str(" shift\n");
script.push_str(" \n");
script.push_str(" # If shield is disabled, just run the command\n");
script.push_str(" if [[ -n \"$KINDLY_SHIELD_DISABLED\" ]]; then\n");
script.push_str(" command \"$cmd\" \"$@\"\n");
script.push_str(" return $?\n");
script.push_str(" fi\n");
script.push_str(" \n");
script.push_str(" # Check if we should scan (look for text content in args)\n");
script.push_str(" local should_scan=0\n");
script.push_str(" local temp_file=\"\"\n");
script.push_str(" \n");
script.push_str(" # Simple heuristic: scan if args contain text that looks like a prompt\n");
script.push_str(" for arg in \"$@\"; do\n");
script.push_str(" # Skip flags\n");
script.push_str(" if [[ \"$arg\" =~ ^- ]]; then\n");
script.push_str(" continue\n");
script.push_str(" fi\n");
script.push_str(" # If arg has more than 10 characters, it might be a prompt\n");
script.push_str(" if [[ ${#arg} -gt 10 ]]; then\n");
script.push_str(" should_scan=1\n");
script.push_str(" break\n");
script.push_str(" fi\n");
script.push_str(" done\n");
script.push_str(" \n");
script.push_str(" if [[ $should_scan -eq 1 ]]; then\n");
script.push_str(" # Create temp file with all arguments\n");
script.push_str(" temp_file=$(mktemp /tmp/kindly-shield-XXXXXX.txt)\n");
script.push_str(" printf '%s\\n' \"$@\" > \"$temp_file\"\n");
script.push_str(" \n");
script.push_str(" # Scan for threats\n");
script.push_str(" if kindly scan \"$temp_file\" --quiet 2>/dev/null; then\n");
script.push_str(" # No threats found, proceed\n");
script.push_str(" rm -f \"$temp_file\"\n");
script.push_str(" command \"$cmd\" \"$@\"\n");
script.push_str(" else\n");
script.push_str(" # Threats detected\n");
script.push_str(
" echo \"⚠️ KindlyGuard Shield: Potential security threats detected!\" >&2\n",
);
script.push_str(" echo \"Run 'kindly scan $temp_file' for details\" >&2\n");
script.push_str(" echo \"To bypass (AT YOUR OWN RISK): KINDLY_SHIELD_DISABLED=1 $cmd ...\" >&2\n");
script.push_str(" rm -f \"$temp_file\"\n");
script.push_str(" return 1\n");
script.push_str(" fi\n");
script.push_str(" else\n");
script.push_str(" # No text content to scan, just run the command\n");
script.push_str(" command \"$cmd\" \"$@\"\n");
script.push_str(" fi\n");
script.push_str("}\n\n");
script.push_str("# AI command wrappers\n");
for cmd in commands {
let safe_cmd = cmd.replace('-', "_");
script.push_str(&format!("# Wrapper for {}\n", cmd));
script.push_str(&format!("{safe_cmd}() {{\n"));
script.push_str(&format!(" __kindly_shield_wrap \"{}\" \"$@\"\n", cmd));
script.push_str("}\n\n");
}
script.push_str("# Convenience functions\n");
script.push_str("kindly-shield-status() {\n");
script.push_str(" if [[ -n \"$KINDLY_SHIELD_DISABLED\" ]]; then\n");
script.push_str(" echo \"🛡️ KindlyGuard Shield: DISABLED\"\n");
script.push_str(" else\n");
script.push_str(" echo \"🛡️ KindlyGuard Shield: ACTIVE\"\n");
script.push_str(" echo \"Protected commands: ");
script.push_str(&commands.join(", "));
script.push_str("\"\n");
script.push_str(" fi\n");
script.push_str("}\n\n");
script.push_str("kindly-shield-disable() {\n");
script.push_str(" export KINDLY_SHIELD_DISABLED=1\n");
script.push_str(
" echo \"⚠️ KindlyGuard Shield: DISABLED - AI commands are now unprotected!\"\n",
);
script.push_str("}\n\n");
script.push_str("kindly-shield-enable() {\n");
script.push_str(" unset KINDLY_SHIELD_DISABLED\n");
script.push_str(" echo \"✓ KindlyGuard Shield: ENABLED - AI commands are now protected\"\n");
script.push_str("}\n\n");
script.push_str("# Show status when sourced\n");
script.push_str("kindly-shield-status\n");
script
}