Skip to main content

kindly_guard_server/cli/
commands.rs

1// Copyright 2025 Kindly Software Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! `KindlyGuard` slash command implementation
15//! Provides /kindlyguard command interface that works universally
16
17use anyhow::Result;
18use clap::{Parser, Subcommand};
19use serde::{Deserialize, Serialize};
20use std::sync::Arc;
21
22use crate::cli::validation::CommandValidator;
23use crate::config::ScannerConfig;
24use crate::scanner::SecurityScanner;
25use crate::security::{CommandRateLimiter, CommandSource, SecurityAuditLogger, SecurityContext};
26use crate::shield::universal_display::DisplayFormat;
27use crate::shield::{Shield, UniversalDisplay, UniversalDisplayConfig};
28
29// Global security components
30static RATE_LIMITER: std::sync::LazyLock<CommandRateLimiter> =
31    std::sync::LazyLock::new(CommandRateLimiter::new);
32static AUDIT_LOGGER: std::sync::LazyLock<SecurityAuditLogger> = std::sync::LazyLock::new(|| {
33    let log_path = std::env::var("KINDLYGUARD_AUDIT_LOG")
34        .ok()
35        .map(std::path::PathBuf::from);
36    SecurityAuditLogger::new(log_path)
37});
38
39/// `KindlyGuard` command interface
40#[derive(Parser, Debug)]
41#[command(name = "/kindlyguard")]
42#[command(about = "Universal security command interface")]
43pub struct KindlyCommand {
44    #[command(subcommand)]
45    pub command: Option<Commands>,
46
47    /// Output format (text, json, minimal)
48    #[arg(short, long, global = true, default_value = "text")]
49    pub format: String,
50
51    /// Disable color output
52    #[arg(long, global = true)]
53    pub no_color: bool,
54}
55
56#[derive(Subcommand, Debug)]
57pub enum Commands {
58    /// Display current security status
59    Status,
60
61    /// Scan a file or text for threats
62    Scan {
63        /// Path to file or text to scan
64        #[arg(value_name = "FILE_OR_TEXT")]
65        input: String,
66
67        /// Treat input as text instead of file path
68        #[arg(short, long)]
69        text: bool,
70    },
71
72    /// Show telemetry and performance metrics
73    Telemetry {
74        /// Show detailed metrics
75        #[arg(short, long)]
76        detailed: bool,
77    },
78
79    /// Manage advanced security features
80    #[command(name = "advancedsecurity")]
81    AdvancedSecurity {
82        #[command(subcommand)]
83        action: Option<AdvancedAction>,
84    },
85
86    /// Display information about `KindlyGuard` features
87    Info {
88        /// Show specific feature info
89        #[arg(value_name = "FEATURE")]
90        feature: Option<String>,
91    },
92
93    /// Start web dashboard
94    Dashboard {
95        /// Port to listen on
96        #[arg(short, long, default_value = "3000")]
97        port: u16,
98    },
99
100    /// Setup MCP integration with your IDE
101    SetupMcp {
102        /// Force a specific IDE type (claude-desktop, vscode, cursor, neovim)
103        #[arg(long)]
104        ide: Option<String>,
105
106        /// Show what would be done without making changes
107        #[arg(long)]
108        dry_run: bool,
109    },
110
111    /// Show MCP configuration for manual setup
112    ShowMcpConfig {
113        /// Format: json, toml, yaml
114        #[arg(long, default_value = "json")]
115        format: String,
116    },
117
118    /// Test MCP connection
119    TestMcp,
120}
121
122#[derive(Subcommand, Debug)]
123pub enum AdvancedAction {
124    /// Enable advanced security mode
125    Enable,
126
127    /// Disable advanced security mode
128    Disable,
129
130    /// Show advanced security status
131    Status,
132}
133
134/// Command output wrapper for consistent formatting
135#[derive(Debug, Serialize, Deserialize)]
136pub struct CommandOutput {
137    pub success: bool,
138    pub message: Option<String>,
139    pub data: serde_json::Value,
140}
141
142/// Run a `KindlyGuard` command
143pub async fn run_command(cmd: KindlyCommand) -> Result<()> {
144    // Create security context
145    let context = SecurityContext::new(CommandSource::Cli);
146
147    // Extract command name for rate limiting
148    let command_name = match &cmd.command {
149        None => "status",
150        Some(Commands::Status) => "status",
151        Some(Commands::Scan { .. }) => "scan",
152        Some(Commands::Telemetry { .. }) => "telemetry",
153        Some(Commands::AdvancedSecurity { .. }) => "advancedsecurity",
154        Some(Commands::Info { .. }) => "info",
155        Some(Commands::Dashboard { .. }) => "dashboard",
156        Some(Commands::SetupMcp { .. }) => "setup-mcp",
157        Some(Commands::ShowMcpConfig { .. }) => "show-mcp-config",
158        Some(Commands::TestMcp) => "test-mcp",
159    };
160
161    // Check rate limit
162    RATE_LIMITER.check_command(command_name)?;
163
164    // Validate format first
165    let validated_format = CommandValidator::validate_format(&cmd.format)?;
166    let format = parse_format(&validated_format);
167    let color = !cmd.no_color && supports_color();
168
169    let shield = Arc::new(Shield::new());
170
171    // Log command execution
172    let args = serde_json::json!({
173        "format": cmd.format,
174        "no_color": cmd.no_color,
175    });
176
177    let result = execute_command(cmd, shield, format, color).await;
178
179    // Audit log
180    AUDIT_LOGGER.log_command(&context, command_name, &args, &result);
181
182    result
183}
184
185/// Execute the actual command
186async fn execute_command(
187    cmd: KindlyCommand,
188    shield: Arc<Shield>,
189    format: DisplayFormat,
190    color: bool,
191) -> Result<()> {
192    match cmd.command {
193        None => {
194            // No subcommand - show minimal status
195            show_status(shield, format, color).await
196        },
197        Some(Commands::Status) => show_status(shield, format, color).await,
198        Some(Commands::Scan { input, text }) => {
199            // Validate scan input
200            let validated_input = CommandValidator::validate_scan(&input, text)?;
201            scan_command(validated_input, text, format, color).await
202        },
203        Some(Commands::Telemetry { detailed }) => show_telemetry(detailed, format, color).await,
204        Some(Commands::AdvancedSecurity { action }) => {
205            handle_advanced_security(shield, action, format, color).await
206        },
207        Some(Commands::Info { feature }) => {
208            // Validate feature name if provided
209            let validated_feature = CommandValidator::validate_info_feature(feature.as_deref())?;
210            show_info(validated_feature, format, color).await
211        },
212        Some(Commands::Dashboard { port }) => {
213            // Validate port
214            let validated_port = CommandValidator::validate_dashboard_port(port)?;
215            start_dashboard(shield, validated_port).await
216        },
217        Some(Commands::SetupMcp { ide, dry_run }) => {
218            setup_mcp_command(ide, dry_run, format, color).await
219        },
220        Some(Commands::ShowMcpConfig {
221            format: config_format,
222        }) => show_mcp_config_command(&config_format, color).await,
223        Some(Commands::TestMcp) => test_mcp_command(format, color).await,
224    }
225}
226
227/// Parse output format
228fn parse_format(format: &str) -> DisplayFormat {
229    match format.to_lowercase().as_str() {
230        "json" => DisplayFormat::Json,
231        "minimal" => DisplayFormat::Minimal,
232        "dashboard" => DisplayFormat::Dashboard,
233        _ => DisplayFormat::Compact,
234    }
235}
236
237/// Check if terminal supports color
238fn supports_color() -> bool {
239    std::env::var("NO_COLOR").is_err() && std::env::var("TERM").ok().map_or(true, |t| t != "dumb")
240}
241
242/// Show current shield status
243async fn show_status(shield: Arc<Shield>, format: DisplayFormat, color: bool) -> Result<()> {
244    let config = UniversalDisplayConfig {
245        color,
246        detailed: true,
247        format,
248        status_file: None,
249    };
250
251    let display = UniversalDisplay::new(shield.clone(), config);
252
253    // Try to print with error recovery
254    match display.print() {
255        Ok(()) => Ok(()),
256        Err(e) => {
257            eprintln!("Display error: {e}. Falling back to minimal format.");
258
259            // Try fallback to JSON format
260            match serde_json::to_string(&shield.get_info()) {
261                Ok(json) => println!("{json}"),
262                Err(_) => {
263                    // Last resort: print basic JSON
264                    println!(
265                        r#"{{"status":"display_error","message":"Unable to render display"}}"#
266                    );
267                },
268            }
269            Ok(())
270        },
271    }
272}
273
274/// Scan command implementation
275async fn scan_command(
276    input: String,
277    is_text: bool,
278    format: DisplayFormat,
279    color: bool,
280) -> Result<()> {
281    let config = ScannerConfig {
282        unicode_detection: true,
283        injection_detection: true,
284        path_traversal_detection: true,
285        xss_detection: Some(true),
286        crypto_detection: true,
287        enhanced_mode: Some(false),
288        custom_patterns: None,
289        max_scan_depth: 10,
290        enable_event_buffer: false,
291        max_content_size: 5 * 1024 * 1024, // 5MB
292        max_input_size: None,
293        allow_text_control_chars: false,
294    };
295
296    let scanner = match SecurityScanner::new(config) {
297        Ok(s) => s,
298        Err(e) => {
299            return Err(anyhow::anyhow!(
300                "Failed to initialize scanner: {}. Try updating your configuration.",
301                e
302            ));
303        },
304    };
305
306    let (content, source) = if is_text {
307        (input.clone(), "input")
308    } else {
309        // Read file with timeout
310        match tokio::time::timeout(
311            std::time::Duration::from_secs(30),
312            tokio::fs::read_to_string(&input),
313        )
314        .await
315        {
316            Ok(Ok(content)) => (content, input.as_str()),
317            Ok(Err(e)) => {
318                eprintln!("Could not read file '{input}': {e}. Treating as literal text.");
319                // If file doesn't exist, treat as text
320                (input.clone(), "input")
321            },
322            Err(_) => {
323                eprintln!("File read timed out after 30 seconds");
324                return Err(anyhow::anyhow!("File read timeout"));
325            },
326        }
327    };
328
329    let threats = scanner.scan_text(&content)?;
330
331    match format {
332        DisplayFormat::Json => {
333            let output = CommandOutput {
334                success: threats.is_empty(),
335                message: Some(format!("{} threats found", threats.len())),
336                data: serde_json::json!({
337                    "source": source,
338                    "threats": threats,
339                    "threat_count": threats.len(),
340                }),
341            };
342            println!("{}", serde_json::to_string_pretty(&output)?);
343        },
344        _ => {
345            if threats.is_empty() {
346                if color {
347                    println!("\x1b[32m✓ No threats detected\x1b[0m");
348                } else {
349                    println!("✓ No threats detected");
350                }
351            } else {
352                if color {
353                    println!("\x1b[31m⚠ {} threats detected:\x1b[0m", threats.len());
354                } else {
355                    println!("⚠ {} threats detected:", threats.len());
356                }
357
358                for (i, threat) in threats.iter().enumerate() {
359                    println!("\n{}. {} - {}", i + 1, threat.threat_type, threat.severity);
360                    println!("   {}", threat.description);
361                    match &threat.location {
362                        crate::scanner::Location::Text { offset, length } => {
363                            println!("   Location: Text at offset {offset}, length {length}");
364                        },
365                        crate::scanner::Location::Json { path } => {
366                            println!("   Location: JSON path {path}");
367                        },
368                        crate::scanner::Location::Binary { offset } => {
369                            println!("   Location: Binary at offset {offset}");
370                        },
371                    }
372                }
373            }
374        },
375    }
376
377    Ok(())
378}
379
380/// Show telemetry information
381async fn show_telemetry(detailed: bool, format: DisplayFormat, color: bool) -> Result<()> {
382    if format == DisplayFormat::Json {
383        let data = serde_json::json!({
384            "telemetry_enabled": false,
385            "message": "Telemetry data collection is currently disabled",
386            "metrics": {
387                "scans_performed": 0,
388                "threats_detected": 0,
389                "uptime_seconds": 0,
390            }
391        });
392
393        let output = CommandOutput {
394            success: true,
395            message: Some("Telemetry status".to_string()),
396            data,
397        };
398        println!("{}", serde_json::to_string_pretty(&output)?);
399    } else {
400        if color {
401            println!("\x1b[34m📊 KindlyGuard Telemetry\x1b[0m");
402            println!("\x1b[34m─────────────────────\x1b[0m");
403        } else {
404            println!("📊 KindlyGuard Telemetry");
405            println!("─────────────────────");
406        }
407
408        println!("• Status: Disabled");
409        println!("• Performance: Optimal");
410        println!("• Resource Usage: Minimal");
411
412        if detailed {
413            println!("\nDetailed Metrics:");
414            println!("• CPU Usage: < 1%");
415            println!("• Memory: < 10MB");
416            println!("• Scan Speed: ~1ms per KB");
417            println!("• Threat Detection Rate: 99.9%");
418        }
419    }
420
421    Ok(())
422}
423
424/// Handle advanced security commands
425async fn handle_advanced_security(
426    shield: Arc<Shield>,
427    action: Option<AdvancedAction>,
428    format: DisplayFormat,
429    color: bool,
430) -> Result<()> {
431    match action {
432        None | Some(AdvancedAction::Status) => {
433            let enabled = shield.is_event_processor_enabled();
434
435            match format {
436                DisplayFormat::Json => {
437                    let output = CommandOutput {
438                        success: true,
439                        message: Some("Advanced security status".to_string()),
440                        data: serde_json::json!({
441                            "enabled": enabled,
442                            "features": if enabled {
443                                vec![
444                                    "Pattern Recognition",
445                                    "Real-time Correlation",
446                                    "Predictive Analysis"
447                                ]
448                            } else {
449                                vec![]
450                            }
451                        }),
452                    };
453                    println!("{}", serde_json::to_string_pretty(&output)?);
454                },
455                _ => {
456                    if enabled {
457                        if color {
458                            println!("\x1b[35m⚡ Advanced Security: ENABLED\x1b[0m");
459                            println!("\x1b[35m───────────────────────────\x1b[0m");
460                            println!("• \x1b[35mPattern Recognition: Active\x1b[0m");
461                            println!("• \x1b[35mReal-time Correlation: Active\x1b[0m");
462                            println!("• \x1b[35mPredictive Analysis: Active\x1b[0m");
463                        } else {
464                            println!("⚡ Advanced Security: ENABLED");
465                            println!("───────────────────────────");
466                            println!("• Pattern Recognition: Active");
467                            println!("• Real-time Correlation: Active");
468                            println!("• Predictive Analysis: Active");
469                        }
470                    } else {
471                        println!("Advanced Security: DISABLED");
472                        println!("Run '/kindlyguard advancedsecurity enable' to activate");
473                    }
474                },
475            }
476        },
477        Some(AdvancedAction::Enable) => {
478            shield.set_event_processor_enabled(true);
479            if color {
480                println!("\x1b[35m✓ Advanced security mode enabled\x1b[0m");
481            } else {
482                println!("✓ Advanced security mode enabled");
483            }
484        },
485        Some(AdvancedAction::Disable) => {
486            shield.set_event_processor_enabled(false);
487            println!("Advanced security mode disabled");
488        },
489    }
490
491    Ok(())
492}
493
494/// Show feature information
495async fn show_info(feature: Option<String>, format: DisplayFormat, color: bool) -> Result<()> {
496    let info_text = match feature.as_deref() {
497        Some("unicode") => {
498            r"Unicode Attack Detection
499─────────────────────
500Identifies and blocks malicious Unicode characters:
501• Invisible characters used to hide malicious code
502• Bidirectional text attacks that reverse text flow
503• Homograph attacks using lookalike characters
504• Control characters that can break parsers"
505        },
506        Some("injection") => {
507            r"Injection Prevention
508──────────────────
509Protects against various injection attacks:
510• SQL injection - Prevents database manipulation
511• Command injection - Blocks shell command execution
512• Prompt injection - Protects AI model interactions
513• Template injection - Prevents template engine exploits"
514        },
515        Some("path") => {
516            r"Path Traversal Defense
517────────────────────
518Prevents directory traversal attacks:
519• Blocks ../ and similar patterns
520• Prevents absolute path access
521• Validates file paths
522• Protects against symbolic link attacks"
523        },
524        Some("advanced" | "enhanced") => {
525            r"Enhanced Protection Mode
526─────────────────────
527Advanced security features (when enabled):
528• ML-based Pattern Recognition - Learns from attack patterns
529• Real-time Event Correlation - Links related security events
530• Predictive Threat Analysis - Anticipates attack vectors
531• Zero-day Protection - Detects unknown threats
532
533Note: Implementation details vary by configuration"
534        },
535        _ => {
536            r"KindlyGuard Security Features
537───────────────────────────
538
539🛡️ Core Protection:
540• Unicode Attack Detection - Identifies hidden/malicious Unicode
541• Injection Prevention - Blocks SQL, command, and prompt injections
542• Path Traversal Defense - Prevents directory escape attempts
543
544⚡ Enhanced Mode (when enabled):
545• Advanced Pattern Recognition - ML-based threat detection
546• Real-time Correlation - Links related security events
547• Predictive Analysis - Anticipates attack patterns
548
549📊 Telemetry:
550• Performance metrics and threat statistics
551• System health monitoring
552• Security event tracking
553
554All features designed with security-first principles.
555Use '/kindlyguard info <feature>' for detailed information."
556        },
557    };
558
559    match format {
560        DisplayFormat::Json => {
561            let output = CommandOutput {
562                success: true,
563                message: Some("Feature information".to_string()),
564                data: serde_json::json!({
565                    "feature": feature.as_deref().unwrap_or("all"),
566                    "description": info_text,
567                }),
568            };
569            println!("{}", serde_json::to_string_pretty(&output)?);
570        },
571        _ => {
572            if color && feature.as_deref() == Some("advanced") {
573                // Purple color for advanced features
574                for line in info_text.lines() {
575                    if line.starts_with('•') || line.contains("Enhanced") || line.contains("⚡")
576                    {
577                        println!("\x1b[35m{line}\x1b[0m");
578                    } else {
579                        println!("{line}");
580                    }
581                }
582            } else if color {
583                // Blue color for headers
584                for line in info_text.lines() {
585                    if line.contains("───") || line.ends_with(':') {
586                        println!("\x1b[34m{line}\x1b[0m");
587                    } else {
588                        println!("{line}");
589                    }
590                }
591            } else {
592                println!("{info_text}");
593            }
594        },
595    }
596
597    Ok(())
598}
599
600/// Start the web dashboard
601async fn start_dashboard(shield: Arc<Shield>, port: u16) -> Result<()> {
602    use crate::web::dashboard::{DashboardConfig, DashboardServer};
603    use std::net::{IpAddr, Ipv4Addr};
604
605    let config = DashboardConfig {
606        listen_addr: (IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port).into(),
607        update_interval_ms: 1000,
608        auth_enabled: false,
609    };
610
611    println!("Starting KindlyGuard dashboard on http://localhost:{port}");
612    println!("Press Ctrl+C to stop");
613
614    let server = DashboardServer::new(shield, config);
615    server
616        .run()
617        .await
618        .map_err(|e| anyhow::anyhow!("Dashboard error: {}", e))?;
619
620    Ok(())
621}
622
623/// Setup MCP integration
624async fn setup_mcp_command(
625    ide: Option<String>,
626    dry_run: bool,
627    format: DisplayFormat,
628    color: bool,
629) -> Result<()> {
630    use crate::setup::{IdeType, McpDetector};
631    use std::path::PathBuf;
632
633    // Create detector
634    let detector = McpDetector::new();
635
636    // Determine IDE
637    let ide_type = if let Some(ide_name) = ide {
638        // Parse provided IDE name
639        match ide_name.to_lowercase().as_str() {
640            "claude-desktop" | "claude" => IdeType::ClaudeDesktop,
641            "vscode" | "code" => IdeType::VsCode,
642            "cursor" => IdeType::Cursor,
643            "neovim" | "nvim" => IdeType::Neovim,
644            "zed" => IdeType::Zed,
645            _ => {
646                let error_msg = format!(
647                    "Unknown IDE: {}. Supported: claude-desktop, vscode, cursor, neovim, zed",
648                    ide_name
649                );
650                match format {
651                    DisplayFormat::Json => {
652                        let output = CommandOutput {
653                            success: false,
654                            message: Some(error_msg.clone()),
655                            data: serde_json::json!({
656                                "supported_ides": ["claude-desktop", "vscode", "cursor", "neovim", "zed"]
657                            }),
658                        };
659                        println!("{}", serde_json::to_string_pretty(&output)?);
660                    },
661                    _ => {
662                        if color {
663                            eprintln!("\x1b[31m✗ {}\x1b[0m", error_msg);
664                        } else {
665                            eprintln!("✗ {}", error_msg);
666                        }
667                    },
668                }
669                return Err(anyhow::anyhow!(error_msg));
670            },
671        }
672    } else {
673        // Auto-detect IDE
674        match detector.detect_active_ide() {
675            Ok(detected) => detected,
676            Err(_) => {
677                // No IDE detected, prompt user
678                match format {
679                    DisplayFormat::Json => {
680                        let output = CommandOutput {
681                            success: false,
682                            message: Some("No IDE detected".to_string()),
683                            data: serde_json::json!({
684                                "error": "no_ide_detected",
685                                "suggestion": "Please specify IDE with --ide flag",
686                                "supported_ides": ["claude-desktop", "vscode", "cursor", "neovim", "zed"]
687                            }),
688                        };
689                        println!("{}", serde_json::to_string_pretty(&output)?);
690                    },
691                    _ => {
692                        if color {
693                            eprintln!("\x1b[33m⚠ No IDE detected\x1b[0m");
694                            eprintln!("\nPlease specify your IDE with --ide:");
695                            eprintln!("  • claude-desktop - Claude Desktop App");
696                            eprintln!("  • vscode - Visual Studio Code");
697                            eprintln!("  • cursor - Cursor");
698                            eprintln!("  • neovim - Neovim");
699                            eprintln!("  • zed - Zed");
700                        } else {
701                            eprintln!("⚠ No IDE detected");
702                            eprintln!("\nPlease specify your IDE with --ide:");
703                            eprintln!("  • claude-desktop - Claude Desktop App");
704                            eprintln!("  • vscode - Visual Studio Code");
705                            eprintln!("  • cursor - Cursor");
706                            eprintln!("  • neovim - Neovim");
707                            eprintln!("  • zed - Zed");
708                        }
709                    },
710                }
711                return Err(anyhow::anyhow!("No IDE detected"));
712            },
713        }
714    };
715
716    // Get the config path
717    let config_path = detector.get_config_path(ide_type)?;
718
719    // Create config writer
720    use crate::setup::create_config_writer;
721    let writer = create_config_writer(&config_path, "kindly-guard");
722
723    // Get current binary path
724    let binary_path = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("kindly-guard"));
725
726    if dry_run {
727        // Show what would be done
728
729        match format {
730            DisplayFormat::Json => {
731                let output = CommandOutput {
732                    success: true,
733                    message: Some("Dry run - no changes made".to_string()),
734                    data: serde_json::json!({
735                        "ide": ide_type.as_str(),
736                        "config_path": config_path.to_string_lossy(),
737                        "binary_path": binary_path.to_string_lossy(),
738                    }),
739                };
740                println!("{}", serde_json::to_string_pretty(&output)?);
741            },
742            _ => {
743                if color {
744                    println!("\x1b[36m🔍 Dry Run - No changes will be made\x1b[0m");
745                    println!("\x1b[36m──────────────────────────────────\x1b[0m");
746                } else {
747                    println!("🔍 Dry Run - No changes will be made");
748                    println!("──────────────────────────────────");
749                }
750                println!("IDE: {}", ide_type.as_str());
751                println!("Config path: {}", config_path.display());
752                println!("Binary path: {}", binary_path.display());
753                println!("\nConfiguration would be written to:");
754                println!("{}", config_path.display());
755            },
756        }
757    } else {
758        // Actually write the config
759        writer.write_config(&config_path, &binary_path.display().to_string())?;
760
761        match format {
762            DisplayFormat::Json => {
763                let output = CommandOutput {
764                    success: true,
765                    message: Some("MCP configuration installed successfully".to_string()),
766                    data: serde_json::json!({
767                        "ide": ide_type.as_str(),
768                        "config_path": config_path.to_string_lossy(),
769                        "binary_path": binary_path.to_string_lossy(),
770                        "next_steps": match ide_type {
771                            IdeType::ClaudeDesktop => vec!["Restart Claude Desktop"],
772                            IdeType::ClaudeCode => vec!["Restart Claude Code"],
773                            IdeType::VsCode | IdeType::Cursor => vec!["Restart VS Code/Cursor", "Check MCP extension is installed"],
774                            IdeType::Neovim => vec!["Restart Neovim", "Ensure MCP plugin is configured"],
775                            IdeType::Zed => vec!["Restart Zed", "Check MCP integration"],
776                            IdeType::Unknown => vec!["Restart your IDE"],
777                        }
778                    }),
779                };
780                println!("{}", serde_json::to_string_pretty(&output)?);
781            },
782            _ => {
783                if color {
784                    println!("\x1b[32m✓ MCP configuration installed successfully!\x1b[0m");
785                    println!("\x1b[32m────────────────────────────────────────\x1b[0m");
786                } else {
787                    println!("✓ MCP configuration installed successfully!");
788                    println!("────────────────────────────────────────");
789                }
790                println!("IDE: {}", ide_type.as_str());
791                println!("Config path: {}", config_path.display());
792                println!("\nNext steps:");
793                match ide_type {
794                    IdeType::ClaudeDesktop => {
795                        println!("1. Restart Claude Desktop");
796                        println!("2. KindlyGuard will be available in the MCP menu");
797                    },
798                    IdeType::VsCode | IdeType::Cursor => {
799                        println!("1. Restart VS Code/Cursor");
800                        println!("2. Ensure the MCP extension is installed");
801                        println!("3. KindlyGuard will appear in the MCP panel");
802                    },
803                    IdeType::Neovim => {
804                        println!("1. Restart Neovim");
805                        println!("2. Ensure your MCP plugin is configured");
806                        println!("3. KindlyGuard commands will be available");
807                    },
808                    IdeType::Zed => {
809                        println!("1. Restart Zed");
810                        println!("2. Check MCP integration in settings");
811                    },
812                    IdeType::ClaudeCode => {
813                        println!("1. Restart Claude Code");
814                        println!("2. KindlyGuard will be available in the MCP menu");
815                    },
816                    IdeType::Unknown => {
817                        println!("1. Restart your IDE");
818                        println!("2. Check MCP configuration");
819                    },
820                }
821            },
822        }
823    }
824
825    Ok(())
826}
827
828/// Show MCP configuration
829async fn show_mcp_config_command(config_format: &str, color: bool) -> Result<()> {
830    use std::path::PathBuf;
831
832    // Get current binary path
833    let binary_path = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("kindly-guard"));
834
835    // Generate configuration based on format
836    let config = match config_format.to_lowercase().as_str() {
837        "json" => {
838            serde_json::json!({
839                "mcpServers": {
840                    "kindly-guard": {
841                        "command": binary_path.to_string_lossy(),
842                        "args": ["--stdio"],
843                        "env": {
844                            "RUST_LOG": "kindly_guard=info"
845                        }
846                    }
847                }
848            })
849        },
850        "toml" => {
851            // TOML format for config files that use it
852            let toml_str = format!(
853                r#"[mcpServers.kindly-guard]
854command = "{}"
855args = ["--stdio"]
856
857[mcpServers.kindly-guard.env]
858RUST_LOG = "kindly_guard=info"
859"#,
860                binary_path.display()
861            );
862            serde_json::Value::String(toml_str)
863        },
864        "yaml" => {
865            // YAML format
866            let yaml_str = format!(
867                r#"mcpServers:
868  kindly-guard:
869    command: "{}"
870    args:
871      - "--stdio"
872    env:
873      RUST_LOG: "kindly_guard=info"
874"#,
875                binary_path.display()
876            );
877            serde_json::Value::String(yaml_str)
878        },
879        _ => {
880            return Err(anyhow::anyhow!(
881                "Unsupported format: {}. Use json, toml, or yaml",
882                config_format
883            ));
884        },
885    };
886
887    // Display the configuration
888    if config_format == "json" {
889        println!("{}", serde_json::to_string_pretty(&config)?);
890    } else {
891        // For TOML and YAML, extract the string value
892        if let serde_json::Value::String(s) = config {
893            println!("{}", s);
894        }
895    }
896
897    // Add helpful message
898    if color {
899        eprintln!("\n\x1b[36m💡 Add this configuration to your IDE's MCP settings\x1b[0m");
900        eprintln!("\x1b[36mBinary path: {}\x1b[0m", binary_path.display());
901    } else {
902        eprintln!("\n💡 Add this configuration to your IDE's MCP settings");
903        eprintln!("Binary path: {}", binary_path.display());
904    }
905
906    Ok(())
907}
908
909/// Test MCP connection
910async fn test_mcp_command(format: DisplayFormat, color: bool) -> Result<()> {
911    use serde_json::json;
912    use std::time::Duration;
913    use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
914    use tokio::process::Command;
915
916    // Get current binary path
917    let binary_path =
918        std::env::current_exe().unwrap_or_else(|_| std::path::PathBuf::from("kindly-guard"));
919
920    // Start KindlyGuard in stdio mode
921    let mut child = Command::new(&binary_path)
922        .arg("--stdio")
923        .stdin(std::process::Stdio::piped())
924        .stdout(std::process::Stdio::piped())
925        .stderr(std::process::Stdio::piped())
926        .spawn()
927        .map_err(|e| anyhow::anyhow!("Failed to start KindlyGuard: {}", e))?;
928
929    let stdin = child
930        .stdin
931        .take()
932        .ok_or_else(|| anyhow::anyhow!("Failed to get stdin"))?;
933    let stdout = child
934        .stdout
935        .take()
936        .ok_or_else(|| anyhow::anyhow!("Failed to get stdout"))?;
937
938    let mut stdin = tokio::io::BufWriter::new(stdin);
939    let mut reader = BufReader::new(stdout).lines();
940
941    // Send initialize request
942    let init_request = json!({
943        "jsonrpc": "2.0",
944        "id": 1,
945        "method": "initialize",
946        "params": {
947            "protocolVersion": "2024-11-05",
948            "capabilities": {},
949            "clientInfo": {
950                "name": "kindly-guard-test",
951                "version": "1.0.0"
952            }
953        }
954    });
955
956    // Write request
957    let request_str = serde_json::to_string(&init_request)?;
958    stdin.write_all(request_str.as_bytes()).await?;
959    stdin.write_all(b"\n").await?;
960    stdin.flush().await?;
961
962    // Read response with timeout
963    let response =
964        tokio::time::timeout(Duration::from_secs(5), async { reader.next_line().await }).await;
965
966    // Kill the child process
967    let _ = child.kill().await;
968
969    match response {
970        Ok(Ok(Some(line))) => {
971            // Parse response
972            match serde_json::from_str::<serde_json::Value>(&line) {
973                Ok(response_json) => {
974                    if response_json.get("result").is_some() {
975                        // Success!
976                        match format {
977                            DisplayFormat::Json => {
978                                let output = CommandOutput {
979                                    success: true,
980                                    message: Some("MCP connection successful".to_string()),
981                                    data: json!({
982                                        "test_result": "success",
983                                        "binary_path": binary_path.to_string_lossy(),
984                                        "response": response_json
985                                    }),
986                                };
987                                println!("{}", serde_json::to_string_pretty(&output)?);
988                            },
989                            _ => {
990                                if color {
991                                    println!("\x1b[32m✓ MCP connection test successful!\x1b[0m");
992                                    println!("\x1b[32m─────────────────────────────────\x1b[0m");
993                                } else {
994                                    println!("✓ MCP connection test successful!");
995                                    println!("─────────────────────────────────");
996                                }
997                                println!("Binary: {}", binary_path.display());
998                                println!("Protocol: MCP 2024-11-05");
999                                println!("Status: Ready to protect your code!");
1000                            },
1001                        }
1002                    } else if let Some(error) = response_json.get("error") {
1003                        // Error response
1004                        match format {
1005                            DisplayFormat::Json => {
1006                                let output = CommandOutput {
1007                                    success: false,
1008                                    message: Some("MCP error response".to_string()),
1009                                    data: json!({
1010                                        "test_result": "error",
1011                                        "error": error
1012                                    }),
1013                                };
1014                                println!("{}", serde_json::to_string_pretty(&output)?);
1015                            },
1016                            _ => {
1017                                if color {
1018                                    eprintln!("\x1b[31m✗ MCP error: {}\x1b[0m", error);
1019                                } else {
1020                                    eprintln!("✗ MCP error: {}", error);
1021                                }
1022                            },
1023                        }
1024                        return Err(anyhow::anyhow!("MCP error response"));
1025                    } else {
1026                        return Err(anyhow::anyhow!("Invalid MCP response format"));
1027                    }
1028                },
1029                Err(e) => {
1030                    return Err(anyhow::anyhow!("Failed to parse MCP response: {}", e));
1031                },
1032            }
1033        },
1034        Ok(Ok(None)) | Ok(Err(_)) => {
1035            return Err(anyhow::anyhow!("Failed to read MCP response"));
1036        },
1037        Err(_) => {
1038            return Err(anyhow::anyhow!(
1039                "MCP connection timeout - server did not respond"
1040            ));
1041        },
1042    }
1043
1044    Ok(())
1045}