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