syncable_cli/
handlers.rs

1use crate::{
2    analyzer::{
3        self, vulnerability_checker::VulnerabilitySeverity, DetectedTechnology, TechnologyCategory, LibraryType,
4        analyze_monorepo, ProjectCategory,
5        security::{TurboSecurityAnalyzer, TurboConfig, ScanMode},
6    },
7    cli::{ToolsCommand, OutputFormat, SeverityThreshold, DisplayFormat, SecurityScanMode},
8    generator,
9};
10use crate::analyzer::security::SecuritySeverity as TurboSecuritySeverity;
11use crate::analyzer::display::{display_analysis_with_return, DisplayMode};
12use std::process;
13use std::collections::HashMap;
14
15pub fn handle_analyze(
16    path: std::path::PathBuf,
17    json: bool,
18    detailed: bool,
19    display: Option<DisplayFormat>,
20    _only: Option<Vec<String>>,
21) -> crate::Result<String> {
22    println!("šŸ” Analyzing project: {}", path.display());
23    
24    let monorepo_analysis = analyze_monorepo(&path)?;
25    
26    let output = if json {
27        display_analysis_with_return(&monorepo_analysis, DisplayMode::Json)
28    } else {
29        // Determine display mode
30        let mode = if detailed {
31            // Legacy flag for backward compatibility
32            DisplayMode::Detailed
33        } else {
34            match display {
35                Some(DisplayFormat::Matrix) | None => DisplayMode::Matrix,
36                Some(DisplayFormat::Detailed) => DisplayMode::Detailed,
37                Some(DisplayFormat::Summary) => DisplayMode::Summary,
38            }
39        };
40        
41        display_analysis_with_return(&monorepo_analysis, mode)
42    };
43    
44    Ok(output)
45}
46
47pub fn handle_generate(
48    path: std::path::PathBuf,
49    _output: Option<std::path::PathBuf>,
50    dockerfile: bool,
51    compose: bool,
52    terraform: bool,
53    all: bool,
54    dry_run: bool,
55    _force: bool,
56) -> crate::Result<()> {
57    println!("šŸ” Analyzing project for generation: {}", path.display());
58    
59    let monorepo_analysis = analyze_monorepo(&path)?;
60    
61    println!("āœ… Analysis complete. Generating IaC files...");
62    
63    if monorepo_analysis.is_monorepo {
64        println!("šŸ“¦ Detected monorepo with {} projects", monorepo_analysis.projects.len());
65        println!("🚧 Monorepo IaC generation is coming soon! For now, generating for the overall structure.");
66        println!("šŸ’” Tip: You can run generate commands on individual project directories for now.");
67    }
68    
69    // For now, use the first/main project for generation
70    // TODO: Implement proper monorepo IaC generation
71    let main_project = &monorepo_analysis.projects[0];
72    
73    let generate_all = all || (!dockerfile && !compose && !terraform);
74    
75    if generate_all || dockerfile {
76        println!("\n🐳 Generating Dockerfile...");
77        let dockerfile_content = generator::generate_dockerfile(&main_project.analysis)?;
78        
79        if dry_run {
80            println!("--- Dockerfile (dry run) ---");
81            println!("{}", dockerfile_content);
82        } else {
83            std::fs::write("Dockerfile", dockerfile_content)?;
84            println!("āœ… Dockerfile generated successfully!");
85        }
86    }
87    
88    if generate_all || compose {
89        println!("\nšŸ™ Generating Docker Compose file...");
90        let compose_content = generator::generate_compose(&main_project.analysis)?;
91        
92        if dry_run {
93            println!("--- docker-compose.yml (dry run) ---");
94            println!("{}", compose_content);
95        } else {
96            std::fs::write("docker-compose.yml", compose_content)?;
97            println!("āœ… Docker Compose file generated successfully!");
98        }
99    }
100    
101    if generate_all || terraform {
102        println!("\nšŸ—ļø  Generating Terraform configuration...");
103        let terraform_content = generator::generate_terraform(&main_project.analysis)?;
104        
105        if dry_run {
106            println!("--- main.tf (dry run) ---");
107            println!("{}", terraform_content);
108        } else {
109            std::fs::write("main.tf", terraform_content)?;
110            println!("āœ… Terraform configuration generated successfully!");
111        }
112    }
113    
114    if !dry_run {
115        println!("\nšŸŽ‰ Generation complete! IaC files have been created in the current directory.");
116        
117        if monorepo_analysis.is_monorepo {
118            println!("šŸ”§ Note: Generated files are based on the main project structure.");
119            println!("   Advanced monorepo support with per-project generation is coming soon!");
120        }
121    }
122    
123    Ok(())
124}
125
126pub fn handle_validate(
127    _path: std::path::PathBuf,
128    _types: Option<Vec<String>>,
129    _fix: bool,
130) -> crate::Result<()> {
131    println!("šŸ” Validating IaC files...");
132    println!("āš ļø  Validation feature is not yet implemented.");
133    Ok(())
134}
135
136pub fn handle_support(
137    languages: bool,
138    frameworks: bool,
139    _detailed: bool,
140) -> crate::Result<()> {
141    if languages || (!languages && !frameworks) {
142        println!("🌐 Supported Languages:");
143        println!("ā”œā”€ā”€ Rust");
144        println!("ā”œā”€ā”€ JavaScript/TypeScript");
145        println!("ā”œā”€ā”€ Python");
146        println!("ā”œā”€ā”€ Go");
147        println!("ā”œā”€ā”€ Java");
148        println!("└── (More coming soon...)");
149    }
150    
151    if frameworks || (!languages && !frameworks) {
152        println!("\nšŸš€ Supported Frameworks:");
153        println!("ā”œā”€ā”€ Web: Express.js, Next.js, React, Vue.js, Actix Web");
154        println!("ā”œā”€ā”€ Database: PostgreSQL, MySQL, MongoDB, Redis");
155        println!("ā”œā”€ā”€ Build Tools: npm, yarn, cargo, maven, gradle");
156        println!("└── (More coming soon...)");
157    }
158    
159    Ok(())
160}
161
162pub async fn handle_dependencies(
163    path: std::path::PathBuf,
164    licenses: bool,
165    vulnerabilities: bool,
166    _prod_only: bool,
167    _dev_only: bool,
168    format: OutputFormat,
169) -> crate::Result<()> {
170    let project_path = path.canonicalize()
171        .unwrap_or_else(|_| path.clone());
172    
173    println!("šŸ” Analyzing dependencies: {}", project_path.display());
174    
175    // First, analyze the project using monorepo analysis
176    let monorepo_analysis = analyze_monorepo(&project_path)?;
177    
178    // Collect all languages from all projects
179    let mut all_languages = Vec::new();
180    for project in &monorepo_analysis.projects {
181        all_languages.extend(project.analysis.languages.clone());
182    }
183    
184    // Then perform detailed dependency analysis using the collected languages
185    let dep_analysis = analyzer::dependency_parser::parse_detailed_dependencies(
186        &project_path,
187        &all_languages,
188        &analyzer::AnalysisConfig::default(),
189    ).await?;
190    
191    if format == OutputFormat::Table {
192        // Table output
193        use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
194        
195        let mut stdout = StandardStream::stdout(ColorChoice::Always);
196        
197        // Print summary
198        println!("\nšŸ“¦ Dependency Analysis Report");
199        println!("{}", "=".repeat(80));
200        
201        let total_deps: usize = dep_analysis.dependencies.len();
202        println!("Total dependencies: {}", total_deps);
203        
204        if monorepo_analysis.is_monorepo {
205            println!("Projects analyzed: {}", monorepo_analysis.projects.len());
206            for project in &monorepo_analysis.projects {
207                println!("  • {} ({})", project.name, format_project_category(&project.project_category));
208            }
209        }
210        
211        for (name, info) in &dep_analysis.dependencies {
212            print!("  {} v{}", name, info.version);
213            
214            // Color code by type
215            stdout.set_color(ColorSpec::new().set_fg(Some(
216                if info.is_dev { Color::Yellow } else { Color::Green }
217            )))?;
218            
219            print!(" [{}]", if info.is_dev { "dev" } else { "prod" });
220            
221            stdout.reset()?;
222            
223            if licenses && info.license.is_some() {
224                print!(" - License: {}", info.license.as_ref().unwrap_or(&"Unknown".to_string()));
225            }
226            
227            println!();
228        }
229        
230        if licenses {
231            // License summary
232            println!("\nšŸ“‹ License Summary");
233            println!("{}", "-".repeat(80));
234            
235            use std::collections::HashMap;
236            let mut license_counts: HashMap<String, usize> = HashMap::new();
237            
238            for (_name, info) in &dep_analysis.dependencies {
239                if let Some(license) = &info.license {
240                    *license_counts.entry(license.clone()).or_insert(0) += 1;
241                }
242            }
243            
244            let mut licenses: Vec<_> = license_counts.into_iter().collect();
245            licenses.sort_by(|a, b| b.1.cmp(&a.1));
246            
247            for (license, count) in licenses {
248                println!("  {}: {} packages", license, count);
249            }
250        }
251        
252        if vulnerabilities {
253            println!("\nšŸ” Checking for vulnerabilities...");
254            
255            // Convert DetailedDependencyMap to the format expected by VulnerabilityChecker
256            let mut deps_by_language: HashMap<analyzer::dependency_parser::Language, Vec<analyzer::dependency_parser::DependencyInfo>> = HashMap::new();
257            
258            // Group dependencies by detected languages
259            for language in &all_languages {
260                let mut lang_deps = Vec::new();
261                
262                // Filter dependencies that belong to this language
263                for (name, info) in &dep_analysis.dependencies {
264                    // Simple heuristic to determine language based on source
265                    let matches_language = match language.name.as_str() {
266                        "Rust" => info.source == "crates.io",
267                        "JavaScript" | "TypeScript" => info.source == "npm",
268                        "Python" => info.source == "pypi",
269                        "Go" => info.source == "go modules",
270                        "Java" | "Kotlin" => info.source == "maven" || info.source == "gradle",
271                        _ => false,
272                    };
273                    
274                    if matches_language {
275                        // Convert to new DependencyInfo format expected by vulnerability checker
276                        lang_deps.push(analyzer::dependency_parser::DependencyInfo {
277                            name: name.clone(),
278                            version: info.version.clone(),
279                            dep_type: if info.is_dev { 
280                                analyzer::dependency_parser::DependencyType::Dev 
281                            } else { 
282                                analyzer::dependency_parser::DependencyType::Production 
283                            },
284                            license: info.license.clone().unwrap_or_default(),
285                            source: Some(info.source.clone()),
286                            language: match language.name.as_str() {
287                                "Rust" => analyzer::dependency_parser::Language::Rust,
288                                "JavaScript" => analyzer::dependency_parser::Language::JavaScript,
289                                "TypeScript" => analyzer::dependency_parser::Language::TypeScript,
290                                "Python" => analyzer::dependency_parser::Language::Python,
291                                "Go" => analyzer::dependency_parser::Language::Go,
292                                "Java" => analyzer::dependency_parser::Language::Java,
293                                "Kotlin" => analyzer::dependency_parser::Language::Kotlin,
294                                _ => analyzer::dependency_parser::Language::Unknown,
295                            },
296                        });
297                    }
298                }
299                
300                if !lang_deps.is_empty() {
301                    let lang_enum = match language.name.as_str() {
302                        "Rust" => analyzer::dependency_parser::Language::Rust,
303                        "JavaScript" => analyzer::dependency_parser::Language::JavaScript,
304                        "TypeScript" => analyzer::dependency_parser::Language::TypeScript,
305                        "Python" => analyzer::dependency_parser::Language::Python,
306                        "Go" => analyzer::dependency_parser::Language::Go,
307                        "Java" => analyzer::dependency_parser::Language::Java,
308                        "Kotlin" => analyzer::dependency_parser::Language::Kotlin,
309                        _ => analyzer::dependency_parser::Language::Unknown,
310                    };
311                    deps_by_language.insert(lang_enum, lang_deps);
312                }
313            }
314            
315            let checker = analyzer::vulnerability_checker::VulnerabilityChecker::new();
316            match checker.check_all_dependencies(&deps_by_language, &project_path).await {
317                Ok(report) => {
318                    println!("\nšŸ›”ļø Vulnerability Report");
319                    println!("{}", "-".repeat(80));
320                    println!("Checked at: {}", report.checked_at.format("%Y-%m-%d %H:%M:%S UTC"));
321                    println!("Total vulnerabilities: {}", report.total_vulnerabilities);
322                    
323                    if report.total_vulnerabilities > 0 {
324                        println!("\nSeverity Breakdown:");
325                        if report.critical_count > 0 {
326                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
327                            println!("  CRITICAL: {}", report.critical_count);
328                            stdout.reset()?;
329                        }
330                        if report.high_count > 0 {
331                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
332                            println!("  HIGH: {}", report.high_count);
333                            stdout.reset()?;
334                        }
335                        if report.medium_count > 0 {
336                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
337                            println!("  MEDIUM: {}", report.medium_count);
338                            stdout.reset()?;
339                        }
340                        if report.low_count > 0 {
341                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?;
342                            println!("  LOW: {}", report.low_count);
343                            stdout.reset()?;
344                        }
345                        
346                        println!("\nVulnerable Dependencies:");
347                        for vuln_dep in &report.vulnerable_dependencies {
348                            println!("\n  šŸ“¦ {} v{} ({})", 
349                                vuln_dep.name, 
350                                vuln_dep.version,
351                                vuln_dep.language.as_str()
352                            );
353                            
354                            for vuln in &vuln_dep.vulnerabilities {
355                                print!("    āš ļø  {} ", vuln.id);
356                                
357                                // Color by severity
358                                stdout.set_color(ColorSpec::new().set_fg(Some(
359                                    match vuln.severity {
360                                        VulnerabilitySeverity::Critical => Color::Red,
361                                        VulnerabilitySeverity::High => Color::Red,
362                                        VulnerabilitySeverity::Medium => Color::Yellow,
363                                        VulnerabilitySeverity::Low => Color::Blue,
364                                        VulnerabilitySeverity::Info => Color::Cyan,
365                                    }
366                                )).set_bold(vuln.severity == VulnerabilitySeverity::Critical))?;
367                                
368                                print!("[{}]", match vuln.severity {
369                                    VulnerabilitySeverity::Critical => "CRITICAL",
370                                    VulnerabilitySeverity::High => "HIGH",
371                                    VulnerabilitySeverity::Medium => "MEDIUM",
372                                    VulnerabilitySeverity::Low => "LOW",
373                                    VulnerabilitySeverity::Info => "INFO",
374                                });
375                                
376                                stdout.reset()?;
377                                
378                                println!(" - {}", vuln.title);
379                                
380                                if let Some(ref cve) = vuln.cve {
381                                    println!("       CVE: {}", cve);
382                                }
383                                if let Some(ref patched) = vuln.patched_versions {
384                                    stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
385                                    println!("       Fix: Upgrade to {}", patched);
386                                    stdout.reset()?;
387                                }
388                            }
389                        }
390                    } else {
391                        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
392                        println!("\nāœ… No known vulnerabilities found!");
393                        stdout.reset()?;
394                    }
395                }
396                Err(e) => {
397                    eprintln!("Error checking vulnerabilities: {}", e);
398                    process::exit(1);
399                }
400            }
401        }
402    } else if format == OutputFormat::Json {
403        // JSON output
404        let output = serde_json::json!({
405            "dependencies": dep_analysis.dependencies,
406            "total": dep_analysis.dependencies.len(),
407        });
408        println!("{}", serde_json::to_string_pretty(&output)?);
409    }
410    
411    Ok(())
412}
413
414pub async fn handle_vulnerabilities(
415    path: std::path::PathBuf,
416    severity: Option<SeverityThreshold>,
417    format: OutputFormat,
418    output: Option<std::path::PathBuf>,
419) -> crate::Result<()> {
420    let project_path = path.canonicalize()
421        .unwrap_or_else(|_| path.clone());
422    
423    println!("šŸ” Scanning for vulnerabilities in: {}", project_path.display());
424    
425    // Parse dependencies
426    let dependencies = analyzer::dependency_parser::DependencyParser::new().parse_all_dependencies(&project_path)?;
427    
428    if dependencies.is_empty() {
429        println!("No dependencies found to check.");
430        return Ok(());
431    }
432    
433    // Check vulnerabilities
434    let checker = analyzer::vulnerability_checker::VulnerabilityChecker::new();
435    let report = checker.check_all_dependencies(&dependencies, &project_path).await
436        .map_err(|e| crate::error::IaCGeneratorError::Analysis(
437            crate::error::AnalysisError::DependencyParsing {
438                file: "vulnerability check".to_string(),
439                reason: e.to_string(),
440            }
441        ))?;
442    
443    // Filter by severity if requested
444    let filtered_report = if let Some(threshold) = severity {
445        let min_severity = match threshold {
446            SeverityThreshold::Low => VulnerabilitySeverity::Low,
447            SeverityThreshold::Medium => VulnerabilitySeverity::Medium,
448            SeverityThreshold::High => VulnerabilitySeverity::High,
449            SeverityThreshold::Critical => VulnerabilitySeverity::Critical,
450        };
451        
452        let filtered_deps: Vec<_> = report.vulnerable_dependencies
453            .into_iter()
454            .filter_map(|mut dep| {
455                dep.vulnerabilities.retain(|v| v.severity >= min_severity);
456                if dep.vulnerabilities.is_empty() {
457                    None
458                } else {
459                    Some(dep)
460                }
461            })
462            .collect();
463        
464        use analyzer::vulnerability_checker::VulnerabilityReport;
465        let mut filtered = VulnerabilityReport {
466            checked_at: report.checked_at,
467            total_vulnerabilities: 0,
468            critical_count: 0,
469            high_count: 0,
470            medium_count: 0,
471            low_count: 0,
472            vulnerable_dependencies: filtered_deps,
473        };
474        
475        // Recalculate counts
476        for dep in &filtered.vulnerable_dependencies {
477            for vuln in &dep.vulnerabilities {
478                                 filtered.total_vulnerabilities += 1;
479                 match vuln.severity {
480                     VulnerabilitySeverity::Critical => filtered.critical_count += 1,
481                     VulnerabilitySeverity::High => filtered.high_count += 1,
482                     VulnerabilitySeverity::Medium => filtered.medium_count += 1,
483                     VulnerabilitySeverity::Low => filtered.low_count += 1,
484                     VulnerabilitySeverity::Info => {},
485                 }
486            }
487        }
488        
489        filtered
490    } else {
491        report
492    };
493    
494    // Format output
495    let output_string = match format {
496        OutputFormat::Table => {
497            // Color formatting for output
498
499            
500            let mut output = String::new();
501            
502            output.push_str(&format!("\nšŸ›”ļø  Vulnerability Scan Report\n"));
503            output.push_str(&format!("{}\n", "=".repeat(80)));
504            output.push_str(&format!("Scanned at: {}\n", filtered_report.checked_at.format("%Y-%m-%d %H:%M:%S UTC")));
505            output.push_str(&format!("Path: {}\n", project_path.display()));
506            
507            if let Some(threshold) = severity {
508                output.push_str(&format!("Severity filter: >= {:?}\n", threshold));
509            }
510            
511            output.push_str(&format!("\nSummary:\n"));
512            output.push_str(&format!("Total vulnerabilities: {}\n", filtered_report.total_vulnerabilities));
513            
514            if filtered_report.total_vulnerabilities > 0 {
515                output.push_str("\nBy Severity:\n");
516                if filtered_report.critical_count > 0 {
517                    output.push_str(&format!("  šŸ”“ CRITICAL: {}\n", filtered_report.critical_count));
518                }
519                if filtered_report.high_count > 0 {
520                    output.push_str(&format!("  šŸ”“ HIGH: {}\n", filtered_report.high_count));
521                }
522                if filtered_report.medium_count > 0 {
523                    output.push_str(&format!("  🟔 MEDIUM: {}\n", filtered_report.medium_count));
524                }
525                if filtered_report.low_count > 0 {
526                    output.push_str(&format!("  šŸ”µ LOW: {}\n", filtered_report.low_count));
527                }
528                
529                output.push_str(&format!("\n{}\n", "-".repeat(80)));
530                output.push_str("Vulnerable Dependencies:\n\n");
531                
532                for vuln_dep in &filtered_report.vulnerable_dependencies {
533                    output.push_str(&format!("šŸ“¦ {} v{} ({})\n", 
534                        vuln_dep.name, 
535                        vuln_dep.version,
536                        vuln_dep.language.as_str()
537                    ));
538                    
539                    for vuln in &vuln_dep.vulnerabilities {
540                        let severity_str = match vuln.severity {
541                            VulnerabilitySeverity::Critical => "CRITICAL",
542                            VulnerabilitySeverity::High => "HIGH",
543                            VulnerabilitySeverity::Medium => "MEDIUM",
544                            VulnerabilitySeverity::Low => "LOW",
545                            VulnerabilitySeverity::Info => "INFO",
546                        };
547                        
548                        output.push_str(&format!("\n  āš ļø  {} [{}]\n", vuln.id, severity_str));
549                        output.push_str(&format!("     {}\n", vuln.title));
550                        
551                        if !vuln.description.is_empty() && vuln.description != vuln.title {
552                            // Wrap description
553                            let wrapped = textwrap::fill(&vuln.description, 70);
554                            for line in wrapped.lines() {
555                                output.push_str(&format!("     {}\n", line));
556                            }
557                        }
558                        
559                        if let Some(ref cve) = vuln.cve {
560                            output.push_str(&format!("     CVE: {}\n", cve));
561                        }
562                        
563                        if let Some(ref ghsa) = vuln.ghsa {
564                            output.push_str(&format!("     GHSA: {}\n", ghsa));
565                        }
566                        
567                        output.push_str(&format!("     Affected: {}\n", vuln.affected_versions));
568                        
569                        if let Some(ref patched) = vuln.patched_versions {
570                            output.push_str(&format!("     āœ… Fix: Upgrade to {}\n", patched));
571                        }
572                    }
573                    output.push_str("\n");
574                }
575            } else {
576                output.push_str("\nāœ… No vulnerabilities found!\n");
577            }
578            
579            output
580        }
581        OutputFormat::Json => {
582            serde_json::to_string_pretty(&filtered_report)?
583        }
584    };
585    
586    // Output results
587    if let Some(output_path) = output {
588        std::fs::write(&output_path, output_string)?;
589        println!("Report saved to: {}", output_path.display());
590    } else {
591        println!("{}", output_string);
592    }
593    
594    // Exit with non-zero code if critical/high vulnerabilities found
595    if filtered_report.critical_count > 0 || filtered_report.high_count > 0 {
596        std::process::exit(1);
597    }
598    
599    Ok(())
600}
601
602/// Display technologies in detailed format with proper categorization
603fn display_technologies_detailed(technologies: &[DetectedTechnology]) {
604    if technologies.is_empty() {
605        println!("\nšŸ› ļø  Technologies Detected: None");
606        return;
607    }
608
609    // Group technologies by IaC-relevant categories
610    let mut meta_frameworks = Vec::new();
611    let mut backend_frameworks = Vec::new();
612    let mut frontend_frameworks = Vec::new();
613    let mut ui_libraries = Vec::new();
614    let mut build_tools = Vec::new();
615    let mut databases = Vec::new();
616    let mut testing = Vec::new();
617    let mut runtimes = Vec::new();
618    let mut other_libraries = Vec::new();
619
620    for tech in technologies {
621        match &tech.category {
622            TechnologyCategory::MetaFramework => meta_frameworks.push(tech),
623            TechnologyCategory::BackendFramework => backend_frameworks.push(tech),
624            TechnologyCategory::FrontendFramework => frontend_frameworks.push(tech),
625            TechnologyCategory::Library(lib_type) => match lib_type {
626                LibraryType::UI => ui_libraries.push(tech),
627                _ => other_libraries.push(tech),
628            },
629            TechnologyCategory::BuildTool => build_tools.push(tech),
630            TechnologyCategory::Database => databases.push(tech),
631            TechnologyCategory::Testing => testing.push(tech),
632            TechnologyCategory::Runtime => runtimes.push(tech),
633            _ => other_libraries.push(tech),
634        }
635    }
636
637    println!("\nšŸ› ļø  Technology Stack:");
638    
639    // Primary Framework (highlighted)
640    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
641        println!("   šŸŽÆ PRIMARY: {} (confidence: {:.1}%)", primary.name, primary.confidence * 100.0);
642        println!("      Architecture driver for this project");
643    }
644
645    // Meta-frameworks
646    if !meta_frameworks.is_empty() {
647        println!("\n   šŸ—ļø  Meta-Frameworks:");
648        for tech in meta_frameworks {
649            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
650        }
651    }
652
653    // Backend frameworks
654    if !backend_frameworks.is_empty() {
655        println!("\n   šŸ–„ļø  Backend Frameworks:");
656        for tech in backend_frameworks {
657            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
658        }
659    }
660
661    // Frontend frameworks
662    if !frontend_frameworks.is_empty() {
663        println!("\n   🌐 Frontend Frameworks:");
664        for tech in frontend_frameworks {
665            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
666        }
667    }
668
669    // UI Libraries
670    if !ui_libraries.is_empty() {
671        println!("\n   šŸŽØ UI Libraries:");
672        for tech in ui_libraries {
673            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
674        }
675    }
676
677    // Note: Removed utility library categories (Data Fetching, Routing, State Management)
678    // as they don't provide value for IaC generation
679
680    // Build Tools
681    if !build_tools.is_empty() {
682        println!("\n   šŸ”Ø Build Tools:");
683        for tech in build_tools {
684            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
685        }
686    }
687
688    // Databases
689    if !databases.is_empty() {
690        println!("\n   šŸ—ƒļø  Database & ORM:");
691        for tech in databases {
692            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
693        }
694    }
695
696    // Testing
697    if !testing.is_empty() {
698        println!("\n   🧪 Testing:");
699        for tech in testing {
700            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
701        }
702    }
703
704    // Runtimes
705    if !runtimes.is_empty() {
706        println!("\n   ⚔ Runtimes:");
707        for tech in runtimes {
708            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
709        }
710    }
711
712    // Other Libraries
713    if !other_libraries.is_empty() {
714        println!("\n   šŸ“š Other Libraries:");
715        for tech in other_libraries {
716            println!("      • {} (confidence: {:.1}%)", tech.name, tech.confidence * 100.0);
717        }
718    }
719}
720
721/// Display technologies in summary format for simple view
722fn display_technologies_summary(technologies: &[DetectedTechnology]) {
723    println!("ā”œā”€ā”€ Technologies detected: {}", technologies.len());
724    
725    // Show primary technology first
726    if let Some(primary) = technologies.iter().find(|t| t.is_primary) {
727        println!("│   ā”œā”€ā”€ šŸŽÆ {} (PRIMARY, {:.1}%)", primary.name, primary.confidence * 100.0);
728    }
729    
730    // Show other technologies
731    for tech in technologies.iter().filter(|t| !t.is_primary) {
732        let icon = match &tech.category {
733            TechnologyCategory::MetaFramework => "šŸ—ļø",
734            TechnologyCategory::BackendFramework => "šŸ–„ļø",
735            TechnologyCategory::FrontendFramework => "🌐",
736            TechnologyCategory::Library(LibraryType::UI) => "šŸŽØ",
737            TechnologyCategory::BuildTool => "šŸ”Ø",
738            TechnologyCategory::Database => "šŸ—ƒļø",
739            TechnologyCategory::Testing => "🧪",
740            TechnologyCategory::Runtime => "⚔",
741            _ => "šŸ“š",
742        };
743        println!("│   ā”œā”€ā”€ {} {} (confidence: {:.1}%)", icon, tech.name, tech.confidence * 100.0);
744    }
745}
746
747pub fn handle_security(
748    path: std::path::PathBuf,
749    mode: SecurityScanMode,
750    include_low: bool,
751    no_secrets: bool,
752    no_code_patterns: bool,
753    _no_infrastructure: bool,
754    _no_compliance: bool,
755    _frameworks: Vec<String>,
756    format: OutputFormat,
757    output: Option<std::path::PathBuf>,
758    fail_on_findings: bool,
759) -> crate::Result<()> {
760    let project_path = path.canonicalize()
761        .unwrap_or_else(|_| path.clone());
762    
763    println!("šŸ›”ļø  Running security analysis on: {}", project_path.display());
764    
765    // Convert CLI mode to internal ScanMode, with flag overrides
766    let scan_mode = if no_secrets && no_code_patterns {
767        // Override: if both secrets and code patterns are disabled, use lightning
768        ScanMode::Lightning
769    } else if include_low {
770        // Override: if including low findings, force paranoid mode
771        ScanMode::Paranoid
772    } else {
773        // Use the requested mode from CLI
774        match mode {
775            SecurityScanMode::Lightning => ScanMode::Lightning,
776            SecurityScanMode::Fast => ScanMode::Fast,
777            SecurityScanMode::Balanced => ScanMode::Balanced,
778            SecurityScanMode::Thorough => ScanMode::Thorough,
779            SecurityScanMode::Paranoid => ScanMode::Paranoid,
780        }
781    };
782    
783    // Configure turbo analyzer
784    let config = TurboConfig {
785        scan_mode,
786        max_file_size: 10 * 1024 * 1024, // 10MB
787        worker_threads: 0, // Auto-detect
788        use_mmap: true,
789        enable_cache: true,
790        cache_size_mb: 100,
791        max_critical_findings: if fail_on_findings { Some(1) } else { None },
792        timeout_seconds: Some(60),
793        skip_gitignored: true,
794        priority_extensions: vec![
795            "env".to_string(), "key".to_string(), "pem".to_string(),
796            "json".to_string(), "yml".to_string(), "yaml".to_string(),
797            "toml".to_string(), "ini".to_string(), "conf".to_string(),
798            "config".to_string(), "js".to_string(), "ts".to_string(),
799            "py".to_string(), "rs".to_string(), "go".to_string(),
800        ],
801        pattern_sets: if no_secrets {
802            vec![]
803        } else {
804            vec!["default".to_string(), "aws".to_string(), "gcp".to_string()]
805        },
806    };
807    
808    // Initialize and run analyzer
809    let analyzer = TurboSecurityAnalyzer::new(config)
810        .map_err(|e| crate::error::IaCGeneratorError::Analysis(
811            crate::error::AnalysisError::InvalidStructure(
812                format!("Failed to create turbo security analyzer: {}", e)
813            )
814        ))?;
815    
816    let start_time = std::time::Instant::now();
817    let security_report = analyzer.analyze_project(&project_path)
818        .map_err(|e| crate::error::IaCGeneratorError::Analysis(
819            crate::error::AnalysisError::InvalidStructure(
820                format!("Turbo security analysis failed: {}", e)
821            )
822        ))?;
823    let scan_duration = start_time.elapsed();
824    
825    println!("⚔ Scan completed in {:.2}s", scan_duration.as_secs_f64());
826    
827    // Format output in the beautiful style requested
828    let output_string = match format {
829        OutputFormat::Table => {
830            use crate::analyzer::display::BoxDrawer;
831            use colored::*;
832            
833            let mut output = String::new();
834            
835            // Header
836            output.push_str(&format!("\n{}\n", "šŸ›”ļø  Security Analysis Results".bright_white().bold()));
837            output.push_str(&format!("{}\n", "═".repeat(80).bright_blue()));
838            
839            // Security Score Box
840            let mut score_box = BoxDrawer::new("Security Summary");
841            score_box.add_line("Overall Score:", &format!("{:.0}/100", security_report.overall_score).bright_yellow(), true);
842            score_box.add_line("Risk Level:", &format!("{:?}", security_report.risk_level).color(match security_report.risk_level {
843                TurboSecuritySeverity::Critical => "bright_red",
844                TurboSecuritySeverity::High => "red", 
845                TurboSecuritySeverity::Medium => "yellow",
846                TurboSecuritySeverity::Low => "green",
847                TurboSecuritySeverity::Info => "blue",
848            }), true);
849            score_box.add_line("Total Findings:", &security_report.total_findings.to_string().cyan(), true);
850            
851            // Analysis scope
852            let config_files = security_report.findings.iter()
853                .filter_map(|f| f.file_path.as_ref())
854                .collect::<std::collections::HashSet<_>>()
855                .len();
856            score_box.add_line("Files Analyzed:", &config_files.max(1).to_string().green(), true);
857            score_box.add_line("Scan Mode:", &format!("{:?}", scan_mode).green(), true);
858            
859            output.push_str(&format!("\n{}\n", score_box.draw()));
860            
861            // Findings in Card Format  
862            if !security_report.findings.is_empty() {
863                // Get terminal width to determine optimal display width
864                let terminal_width = if let Some((width, _)) = term_size::dimensions() {
865                    width.saturating_sub(10) // Leave some margin
866                } else {
867                    120 // Fallback width
868                };
869                
870                let mut findings_box = BoxDrawer::new("Security Findings");
871                
872                for (i, finding) in security_report.findings.iter().enumerate() {
873                    let severity_color = match finding.severity {
874                        TurboSecuritySeverity::Critical => "bright_red",
875                        TurboSecuritySeverity::High => "red",
876                        TurboSecuritySeverity::Medium => "yellow", 
877                        TurboSecuritySeverity::Low => "blue",
878                        TurboSecuritySeverity::Info => "green",
879                    };
880                    
881                    // Extract relative file path from project root
882                    let file_display = if let Some(file_path) = &finding.file_path {
883                        // Cross-platform path normalization
884                        let canonical_file = file_path.canonicalize().unwrap_or_else(|_| file_path.clone());
885                        let canonical_project = path.canonicalize().unwrap_or_else(|_| path.clone());
886                        
887                        // Try to calculate relative path from project root
888                        if let Ok(relative_path) = canonical_file.strip_prefix(&canonical_project) {
889                            // Use forward slashes for consistency across platforms
890                            let relative_str = relative_path.to_string_lossy().replace('\\', "/");
891                            format!("./{}", relative_str)
892                        } else {
893                            // Fallback: try to find any common ancestor or use absolute path
894                            let path_str = file_path.to_string_lossy();
895                            if path_str.starts_with('/') {
896                                // For absolute paths, try to extract meaningful relative portion
897                                if let Some(project_name) = path.file_name().and_then(|n| n.to_str()) {
898                                    if let Some(project_idx) = path_str.rfind(project_name) {
899                                        let relative_part = &path_str[project_idx + project_name.len()..];
900                                        if relative_part.starts_with('/') {
901                                            format!(".{}", relative_part)
902                                        } else if !relative_part.is_empty() {
903                                            format!("./{}", relative_part)
904                                        } else {
905                                            format!("./{}", file_path.file_name().unwrap_or_default().to_string_lossy())
906                                        }
907                                    } else {
908                                        // Last resort: show the full path
909                                        path_str.to_string()
910                                    }
911                                } else {
912                                    // Show full path if we can't determine project context
913                                    path_str.to_string()
914                                }
915                            } else {
916                                // For relative paths that don't strip properly, use as-is
917                                if path_str.starts_with("./") {
918                                    path_str.to_string()
919                                } else {
920                                    format!("./{}", path_str)
921                                }
922                            }
923                        }
924                    } else {
925                        "N/A".to_string()
926                    };
927                    
928                    // Parse gitignore status from description (clean colored text)
929                    let gitignore_status = if finding.description.contains("is tracked by git") {
930                        "TRACKED".bright_red().bold()
931                    } else if finding.description.contains("is NOT in .gitignore") {
932                        "EXPOSED".yellow().bold()
933                    } else if finding.description.contains("is protected") || finding.description.contains("properly ignored") {
934                        "SAFE".bright_green().bold()
935                    } else if finding.description.contains("appears safe") {
936                        "OK".bright_blue().bold()
937                    } else {
938                        "UNKNOWN".dimmed()
939                    };
940                    
941                    // Determine finding type
942                    let finding_type = if finding.title.contains("Environment Variable") {
943                        "ENV VAR"
944                    } else if finding.title.contains("Secret File") {
945                        "SECRET FILE"
946                    } else if finding.title.contains("API Key") || finding.title.contains("Stripe") || finding.title.contains("Firebase") {
947                        "API KEY"
948                    } else if finding.title.contains("Configuration") {
949                        "CONFIG"
950                    } else {
951                        "OTHER"
952                    };
953                    
954                    // Format position as "line:column" or just "line" if no column info
955                    let position_display = match (finding.line_number, finding.column_number) {
956                        (Some(line), Some(col)) => format!("{}:{}", line, col),
957                        (Some(line), None) => format!("{}", line),
958                        _ => "—".to_string(),
959                    };
960                    
961                    // Card format: File path with intelligent display based on terminal width
962                    let box_margin = 6; // Account for box borders and padding
963                    let available_width = terminal_width.saturating_sub(box_margin);
964                    let max_path_width = available_width.saturating_sub(20); // Leave space for numbering and spacing
965                    
966                    if file_display.len() + 3 <= max_path_width {
967                        // Path fits on one line with numbering
968                        findings_box.add_value_only(&format!("{}. {}", 
969                            format!("{}", i + 1).bright_white().bold(),
970                            file_display.cyan().bold()
971                        ));
972                    } else if file_display.len() <= available_width.saturating_sub(4) {
973                        // Path fits on its own line with indentation
974                        findings_box.add_value_only(&format!("{}.", 
975                            format!("{}", i + 1).bright_white().bold()
976                        ));
977                        findings_box.add_value_only(&format!("   {}", 
978                            file_display.cyan().bold()
979                        ));
980                    } else {
981                        // Path is extremely long - use smart wrapping
982                        findings_box.add_value_only(&format!("{}.", 
983                            format!("{}", i + 1).bright_white().bold()
984                        ));
985                        
986                        // Smart path wrapping - prefer breaking at directory separators
987                        let wrap_width = available_width.saturating_sub(4);
988                        let mut remaining = file_display.as_str();
989                        let mut first_line = true;
990                        
991                        while !remaining.is_empty() {
992                            let prefix = if first_line { "   " } else { "     " };
993                            let line_width = wrap_width.saturating_sub(prefix.len());
994                            
995                            if remaining.len() <= line_width {
996                                // Last chunk fits entirely
997                                findings_box.add_value_only(&format!("{}{}", 
998                                    prefix, remaining.cyan().bold()
999                                ));
1000                                break;
1001                            } else {
1002                                // Find a good break point (prefer directory separator)
1003                                let chunk = &remaining[..line_width];
1004                                let break_point = chunk.rfind('/').unwrap_or(line_width.saturating_sub(1));
1005                                
1006                                findings_box.add_value_only(&format!("{}{}", 
1007                                    prefix, chunk[..break_point].cyan().bold()
1008                                ));
1009                                remaining = &remaining[break_point..];
1010                                if remaining.starts_with('/') {
1011                                    remaining = &remaining[1..]; // Skip the separator
1012                                }
1013                            }
1014                            first_line = false;
1015                        }
1016                    }
1017                    
1018                    findings_box.add_value_only(&format!("   {} {} | {} {} | {} {} | {} {}", 
1019                        "Type:".dimmed(),
1020                        finding_type.yellow(),
1021                        "Severity:".dimmed(),
1022                        format!("{:?}", finding.severity).color(severity_color).bold(),
1023                        "Position:".dimmed(),
1024                        position_display.bright_cyan(),
1025                        "Status:".dimmed(),
1026                        gitignore_status
1027                    ));
1028                    
1029                    // Add spacing between findings (except for the last one)
1030                    if i < security_report.findings.len() - 1 {
1031                        findings_box.add_value_only("");
1032                    }
1033                }
1034                
1035                output.push_str(&format!("\n{}\n", findings_box.draw()));
1036                
1037                // GitIgnore Status Legend  
1038                let mut legend_box = BoxDrawer::new("Git Status Legend");
1039                legend_box.add_line(&"TRACKED:".bright_red().bold().to_string(), "File is tracked by git - CRITICAL RISK", false);
1040                legend_box.add_line(&"EXPOSED:".yellow().bold().to_string(), "File contains secrets but not in .gitignore", false);
1041                legend_box.add_line(&"SAFE:".bright_green().bold().to_string(), "File is properly ignored by .gitignore", false);
1042                legend_box.add_line(&"OK:".bright_blue().bold().to_string(), "File appears safe for version control", false);
1043                output.push_str(&format!("\n{}\n", legend_box.draw()));
1044            } else {
1045                let mut no_findings_box = BoxDrawer::new("Security Status");
1046                no_findings_box.add_value_only(&"āœ… No security issues detected".green());
1047                no_findings_box.add_value_only("šŸ’” Regular security scanning recommended");
1048                output.push_str(&format!("\n{}\n", no_findings_box.draw()));
1049            }
1050            
1051            // Recommendations Box
1052            let mut rec_box = BoxDrawer::new("Key Recommendations");
1053            if !security_report.recommendations.is_empty() {
1054                for (i, rec) in security_report.recommendations.iter().take(5).enumerate() {
1055                    // Clean up recommendation text
1056                    let clean_rec = rec.replace("Add these patterns to your .gitignore:", "Add to .gitignore:");
1057                    rec_box.add_value_only(&format!("{}. {}", i + 1, clean_rec));
1058                }
1059                if security_report.recommendations.len() > 5 {
1060                    rec_box.add_value_only(&format!("... and {} more recommendations", 
1061                        security_report.recommendations.len() - 5).dimmed());
1062                }
1063            } else {
1064                rec_box.add_value_only("āœ… No immediate security concerns detected");
1065                rec_box.add_value_only("šŸ’” Consider implementing dependency scanning");
1066                rec_box.add_value_only("šŸ’” Review environment variable security practices");
1067            }
1068            output.push_str(&format!("\n{}\n", rec_box.draw()));
1069            
1070            output
1071        }
1072        OutputFormat::Json => {
1073            serde_json::to_string_pretty(&security_report)?
1074        }
1075    };
1076    
1077    // Output results
1078    if let Some(output_path) = output {
1079        std::fs::write(&output_path, output_string)?;
1080        println!("Security report saved to: {}", output_path.display());
1081    } else {
1082        print!("{}", output_string);
1083    }
1084    
1085    // Exit with error code if requested and findings exist
1086    if fail_on_findings && security_report.total_findings > 0 {
1087        let critical_count = security_report.findings_by_severity
1088            .get(&TurboSecuritySeverity::Critical)
1089            .unwrap_or(&0);
1090        let high_count = security_report.findings_by_severity
1091            .get(&TurboSecuritySeverity::High)
1092            .unwrap_or(&0);
1093        
1094        if *critical_count > 0 {
1095            eprintln!("āŒ Critical security issues found. Please address immediately.");
1096            std::process::exit(1);
1097        } else if *high_count > 0 {
1098            eprintln!("āš ļø  High severity security issues found. Review recommended.");
1099            std::process::exit(2);
1100        } else {
1101            eprintln!("ā„¹ļø  Security issues found but none are critical or high severity.");
1102            std::process::exit(3);
1103        }
1104    }
1105    
1106    Ok(())
1107}
1108
1109pub async fn handle_tools(command: ToolsCommand) -> crate::Result<()> {
1110    use crate::analyzer::{tool_installer::ToolInstaller, dependency_parser::Language};
1111    use std::collections::HashMap;
1112    use termcolor::{ColorChoice, StandardStream, WriteColor, ColorSpec, Color};
1113    
1114    match command {
1115        ToolsCommand::Status { format, languages } => {
1116            let installer = ToolInstaller::new();
1117            
1118            // Determine which languages to check
1119            let langs_to_check = if let Some(lang_names) = languages {
1120                lang_names.iter()
1121                    .filter_map(|name| Language::from_string(name))
1122                    .collect()
1123            } else {
1124                vec![
1125                    Language::Rust,
1126                    Language::JavaScript,
1127                    Language::TypeScript,
1128                    Language::Python,
1129                    Language::Go,
1130                    Language::Java,
1131                    Language::Kotlin,
1132                ]
1133            };
1134            
1135            println!("šŸ”§ Checking vulnerability scanning tools status...\n");
1136            
1137            match format {
1138                OutputFormat::Table => {
1139                    let mut stdout = StandardStream::stdout(ColorChoice::Always);
1140                    
1141                    println!("šŸ“‹ Vulnerability Scanning Tools Status");
1142                    println!("{}", "=".repeat(50));
1143                    
1144                    for language in &langs_to_check {
1145                        let (tool_name, is_available) = match language {
1146                            Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
1147                            Language::JavaScript | Language::TypeScript => ("npm", installer.test_tool_availability("npm")),
1148                            Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
1149                            Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
1150                            Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
1151                            _ => continue,
1152                        };
1153                        
1154                        print!("  {} {:?}: ", 
1155                               if is_available { "āœ…" } else { "āŒ" }, 
1156                               language);
1157                        
1158                        if is_available {
1159                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
1160                            print!("{} installed", tool_name);
1161                        } else {
1162                            stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
1163                            print!("{} missing", tool_name);
1164                        }
1165                        
1166                        stdout.reset()?;
1167                        println!();
1168                    }
1169                    
1170                    // Check universal tools
1171                    println!("\nšŸ” Universal Scanners:");
1172                    let grype_available = installer.test_tool_availability("grype");
1173                    print!("  {} Grype: ", if grype_available { "āœ…" } else { "āŒ" });
1174                    if grype_available {
1175                        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
1176                        println!("installed");
1177                    } else {
1178                        stdout.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
1179                        println!("missing");
1180                    }
1181                    stdout.reset()?;
1182                }
1183                OutputFormat::Json => {
1184                    let mut status = HashMap::new();
1185                    
1186                    for language in &langs_to_check {
1187                        let (tool_name, is_available) = match language {
1188                            Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
1189                            Language::JavaScript | Language::TypeScript => ("npm", installer.test_tool_availability("npm")),
1190                            Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
1191                            Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
1192                            Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
1193                            _ => continue,
1194                        };
1195                        
1196                        status.insert(format!("{:?}", language), serde_json::json!({
1197                            "tool": tool_name,
1198                            "available": is_available
1199                        }));
1200                    }
1201                    
1202                    println!("{}", serde_json::to_string_pretty(&status)?);
1203                }
1204            }
1205        }
1206        
1207        ToolsCommand::Install { languages, include_owasp, dry_run, yes } => {
1208            let mut installer = ToolInstaller::new();
1209            
1210            // Determine which languages to install tools for
1211            let langs_to_install = if let Some(lang_names) = languages {
1212                lang_names.iter()
1213                    .filter_map(|name| Language::from_string(name))
1214                    .collect()
1215            } else {
1216                vec![
1217                    Language::Rust,
1218                    Language::JavaScript,
1219                    Language::TypeScript,
1220                    Language::Python,
1221                    Language::Go,
1222                    Language::Java,
1223                ]
1224            };
1225            
1226            if dry_run {
1227                println!("šŸ” Dry run: Tools that would be installed:");
1228                println!("{}", "=".repeat(50));
1229                
1230                for language in &langs_to_install {
1231                    let (tool_name, is_available) = match language {
1232                        Language::Rust => ("cargo-audit", installer.test_tool_availability("cargo-audit")),
1233                        Language::JavaScript | Language::TypeScript => ("npm", installer.test_tool_availability("npm")),
1234                        Language::Python => ("pip-audit", installer.test_tool_availability("pip-audit")),
1235                        Language::Go => ("govulncheck", installer.test_tool_availability("govulncheck")),
1236                        Language::Java | Language::Kotlin => ("grype", installer.test_tool_availability("grype")),
1237                        _ => continue,
1238                    };
1239                    
1240                    if !is_available {
1241                        println!("  šŸ“¦ Would install {} for {:?}", tool_name, language);
1242                    } else {
1243                        println!("  āœ… {} already installed for {:?}", tool_name, language);
1244                    }
1245                }
1246                
1247                if include_owasp && !installer.test_tool_availability("dependency-check") {
1248                    println!("  šŸ“¦ Would install OWASP Dependency Check (large download)");
1249                }
1250                
1251                return Ok(());
1252            }
1253            
1254            if !yes {
1255                use std::io::{self, Write};
1256                print!("šŸ”§ Install missing vulnerability scanning tools? [y/N]: ");
1257                io::stdout().flush()?;
1258                
1259                let mut input = String::new();
1260                io::stdin().read_line(&mut input)?;
1261                
1262                if !input.trim().to_lowercase().starts_with('y') {
1263                    println!("Installation cancelled.");
1264                    return Ok(());
1265                }
1266            }
1267            
1268            println!("šŸ› ļø  Installing vulnerability scanning tools...");
1269            
1270            match installer.ensure_tools_for_languages(&langs_to_install) {
1271                Ok(()) => {
1272                    println!("āœ… Tool installation completed!");
1273                    installer.print_tool_status(&langs_to_install);
1274                    
1275                    // Show PATH instructions if needed
1276                    println!("\nšŸ’” Setup Instructions:");
1277                    println!("  • Add ~/.local/bin to your PATH for manually installed tools");
1278                    println!("  • Add ~/go/bin to your PATH for Go tools");
1279                    println!("  • Add to your shell profile (~/.bashrc, ~/.zshrc, etc.):");
1280                    println!("    export PATH=\"$HOME/.local/bin:$HOME/go/bin:$PATH\"");
1281                }
1282                Err(e) => {
1283                    eprintln!("āŒ Tool installation failed: {}", e);
1284                    eprintln!("\nšŸ”§ Manual installation may be required for some tools.");
1285                    eprintln!("   Run 'sync-ctl tools guide' for manual installation instructions.");
1286                    return Err(e);
1287                }
1288            }
1289        }
1290        
1291        ToolsCommand::Verify { languages, verbose } => {
1292            let installer = ToolInstaller::new();
1293            
1294            // Determine which languages to verify
1295            let langs_to_verify = if let Some(lang_names) = languages {
1296                lang_names.iter()
1297                    .filter_map(|name| Language::from_string(name))
1298                    .collect()
1299            } else {
1300                vec![
1301                    Language::Rust,
1302                    Language::JavaScript,
1303                    Language::TypeScript,
1304                    Language::Python,
1305                    Language::Go,
1306                    Language::Java,
1307                ]
1308            };
1309            
1310            println!("šŸ” Verifying vulnerability scanning tools...\n");
1311            
1312            let mut all_working = true;
1313            
1314            for language in &langs_to_verify {
1315                let (tool_name, is_working) = match language {
1316                    Language::Rust => {
1317                        let working = installer.test_tool_availability("cargo-audit");
1318                        ("cargo-audit", working)
1319                    }
1320                    Language::JavaScript | Language::TypeScript => {
1321                        let working = installer.test_tool_availability("npm");
1322                        ("npm", working)
1323                    }
1324                    Language::Python => {
1325                        let working = installer.test_tool_availability("pip-audit");
1326                        ("pip-audit", working)
1327                    }
1328                    Language::Go => {
1329                        let working = installer.test_tool_availability("govulncheck");
1330                        ("govulncheck", working)
1331                    }
1332                    Language::Java | Language::Kotlin => {
1333                        let working = installer.test_tool_availability("grype");
1334                        ("grype", working)
1335                    }
1336                    _ => continue,
1337                };
1338                
1339                print!("  {} {:?}: {}", 
1340                       if is_working { "āœ…" } else { "āŒ" }, 
1341                       language,
1342                       tool_name);
1343                
1344                if is_working {
1345                    println!(" - working correctly");
1346                    
1347                    if verbose {
1348                        // Try to get version info
1349                        use std::process::Command;
1350                        let version_result = match tool_name {
1351                            "cargo-audit" => Command::new("cargo").args(&["audit", "--version"]).output(),
1352                            "npm" => Command::new("npm").arg("--version").output(),
1353                            "pip-audit" => Command::new("pip-audit").arg("--version").output(),
1354                            "govulncheck" => Command::new("govulncheck").arg("-version").output(),
1355                            "grype" => Command::new("grype").arg("version").output(),
1356                            _ => continue,
1357                        };
1358                        
1359                        if let Ok(output) = version_result {
1360                            if output.status.success() {
1361                                let version = String::from_utf8_lossy(&output.stdout);
1362                                println!("    Version: {}", version.trim());
1363                            }
1364                        }
1365                    }
1366                } else {
1367                    println!(" - not working or missing");
1368                    all_working = false;
1369                }
1370            }
1371            
1372            if all_working {
1373                println!("\nāœ… All tools are working correctly!");
1374            } else {
1375                println!("\nāŒ Some tools are missing or not working.");
1376                println!("   Run 'sync-ctl tools install' to install missing tools.");
1377            }
1378        }
1379        
1380        ToolsCommand::Guide { languages, platform } => {
1381            let target_platform = platform.unwrap_or_else(|| {
1382                match std::env::consts::OS {
1383                    "macos" => "macOS".to_string(),
1384                    "linux" => "Linux".to_string(),
1385                    "windows" => "Windows".to_string(),
1386                    other => other.to_string(),
1387                }
1388            });
1389            
1390            println!("šŸ“š Vulnerability Scanning Tools Installation Guide");
1391            println!("Platform: {}", target_platform);
1392            println!("{}", "=".repeat(60));
1393            
1394            let langs_to_show = if let Some(lang_names) = languages {
1395                lang_names.iter()
1396                    .filter_map(|name| Language::from_string(name))
1397                    .collect()
1398            } else {
1399                vec![
1400                    Language::Rust,
1401                    Language::JavaScript,
1402                    Language::TypeScript,
1403                    Language::Python,
1404                    Language::Go,
1405                    Language::Java,
1406                ]
1407            };
1408            
1409            for language in &langs_to_show {
1410                match language {
1411                    Language::Rust => {
1412                        println!("\nšŸ¦€ Rust - cargo-audit");
1413                        println!("  Install: cargo install cargo-audit");
1414                        println!("  Usage: cargo audit");
1415                    }
1416                    Language::JavaScript | Language::TypeScript => {
1417                        println!("\n🌐 JavaScript/TypeScript - npm audit");
1418                        println!("  Install: Download Node.js from https://nodejs.org/");
1419                        match target_platform.as_str() {
1420                            "macOS" => println!("  Package manager: brew install node"),
1421                            "Linux" => println!("  Package manager: sudo apt install nodejs npm (Ubuntu/Debian)"),
1422                            _ => {}
1423                        }
1424                        println!("  Usage: npm audit");
1425                    }
1426                    Language::Python => {
1427                        println!("\nšŸ Python - pip-audit");
1428                        println!("  Install: pipx install pip-audit (recommended)");
1429                        println!("  Alternative: pip3 install --user pip-audit");
1430                        println!("  Also available: safety (pip install safety)");
1431                        println!("  Usage: pip-audit");
1432                    }
1433                    Language::Go => {
1434                        println!("\n🐹 Go - govulncheck");
1435                        println!("  Install: go install golang.org/x/vuln/cmd/govulncheck@latest");
1436                        println!("  Note: Make sure ~/go/bin is in your PATH");
1437                        println!("  Usage: govulncheck ./...");
1438                    }
1439                    Language::Java => {
1440                        println!("\nā˜• Java - Multiple options");
1441                        println!("  Grype (recommended):");
1442                        match target_platform.as_str() {
1443                            "macOS" => println!("    Install: brew install anchore/grype/grype"),
1444                            "Linux" => println!("    Install: Download from https://github.com/anchore/grype/releases"),
1445                            _ => println!("    Install: Download from https://github.com/anchore/grype/releases"),
1446                        }
1447                        println!("    Usage: grype .");
1448                        println!("  OWASP Dependency Check:");
1449                        match target_platform.as_str() {
1450                            "macOS" => println!("    Install: brew install dependency-check"),
1451                            _ => println!("    Install: Download from https://github.com/jeremylong/DependencyCheck/releases"),
1452                        }
1453                        println!("    Usage: dependency-check --project myproject --scan .");
1454                    }
1455                    _ => {}
1456                }
1457            }
1458            
1459            println!("\nšŸ” Universal Scanners:");
1460            println!("  Grype: Works with multiple ecosystems");
1461            println!("  Trivy: Container and filesystem scanning");
1462            println!("  Snyk: Commercial solution with free tier");
1463            
1464            println!("\nšŸ’” Tips:");
1465            println!("  • Run 'sync-ctl tools status' to check current installation");
1466            println!("  • Run 'sync-ctl tools install' for automatic installation");
1467            println!("  • Add tool directories to your PATH for easier access");
1468        }
1469    }
1470    
1471    Ok(())
1472}
1473
1474/// Format project category for display
1475fn format_project_category(category: &ProjectCategory) -> &'static str {
1476    match category {
1477        ProjectCategory::Frontend => "Frontend",
1478        ProjectCategory::Backend => "Backend",
1479        ProjectCategory::Api => "API",
1480        ProjectCategory::Service => "Service",
1481        ProjectCategory::Library => "Library",
1482        ProjectCategory::Tool => "Tool",
1483        ProjectCategory::Documentation => "Documentation",
1484        ProjectCategory::Infrastructure => "Infrastructure",
1485        ProjectCategory::Unknown => "Unknown",
1486    }
1487}