use clap::{Parser, Subcommand, ValueEnum}; #[derive(Parser)] #[command(name = "nyx")] #[command(about = "A fast vulnerability scanner with project indexing")] #[command(version)] pub struct Cli { #[command(subcommand)] pub(crate) command: Commands, } impl Commands { /// Whether this command produces structured (machine-readable) output on /// stdout, meaning human status messages must be suppressed entirely. pub fn is_structured_output(&self) -> bool { matches!(self, Commands::Scan { format, .. } if *format == OutputFormat::Json || *format == OutputFormat::Sarif) } } /// Output format for scan results. #[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Default)] pub enum OutputFormat { #[default] Console, Json, Sarif, } impl std::fmt::Display for OutputFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { OutputFormat::Console => write!(f, "console"), OutputFormat::Json => write!(f, "json"), OutputFormat::Sarif => write!(f, "sarif"), } } } /// Index mode for scan operations. #[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Default)] pub enum IndexMode { /// Use index if available, build if missing (default) #[default] Auto, /// Skip indexing entirely, scan filesystem directly Off, /// Force rebuild index before scanning Rebuild, } /// Analysis mode for scan operations. #[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Default)] pub enum ScanMode { /// Run all analyses: AST patterns + CFG + taint (default) #[default] Full, /// Run AST pattern queries only (no CFG/taint) Ast, /// Run CFG structural analyses + taint only (no AST patterns) Cfg, /// Alias for cfg (CFG + taint analysis) Taint, } #[derive(Subcommand)] pub enum Commands { /// Scan project for vulnerabilities Scan { /// Path to scan (defaults to current directory) #[arg(default_value = ".")] path: String, /// Index mode: auto (default), off (no index), rebuild (force rebuild) #[arg(long, value_enum, default_value_t = IndexMode::Auto)] index: IndexMode, /// Output format #[arg(short, long, value_enum, default_value_t = OutputFormat::Console)] format: OutputFormat, /// Severity filter expression: HIGH, HIGH,MEDIUM, or >=MEDIUM /// /// Filters findings AFTER all severity normalization (e.g. nonprod /// downgrades). Only findings matching the expression are emitted. /// Case-insensitive. Shell-quote expressions containing ">". #[arg(long)] severity: Option, /// Analysis mode: full (default), ast, cfg, taint #[arg(long, value_enum, default_value_t = ScanMode::Full)] mode: ScanMode, /// Scan all targets (alias for --mode full) #[arg(long, hide = true)] all_targets: bool, /// Preserve original severity for test/vendor/build paths /// /// By default, findings in non-production paths are downgraded by one /// severity tier. This flag preserves original severity. #[arg(long, alias = "include-nonprod")] keep_nonprod_severity: bool, /// Suppress all human-readable status output #[arg(long)] quiet: bool, /// Exit with code 1 if any finding meets or exceeds this severity /// /// Useful for CI gating. Example: --fail-on HIGH #[arg(long)] fail_on: Option, /// Disable attack-surface ranking (findings are sorted by exploitability by default) #[arg(long)] no_rank: bool, /// Show inline-suppressed findings (dimmed, tagged [SUPPRESSED]) #[arg(long)] show_suppressed: bool, /// Show all findings: disables category filtering, rollups, and LOW budgets #[arg(long = "all")] show_all: bool, /// Include Quality findings (excluded by default) #[arg(long)] include_quality: bool, /// Maximum total LOW findings to show #[arg(long, default_value_t = 20)] max_low: u32, /// Maximum LOW findings per file #[arg(long, default_value_t = 1)] max_low_per_file: u32, /// Maximum LOW findings per rule #[arg(long, default_value_t = 10)] max_low_per_rule: u32, /// Number of example locations in rollup findings #[arg(long, default_value_t = 5)] rollup_examples: u32, /// Show all instances for a specific rule (bypasses rollup for that rule) #[arg(long)] show_instances: Option, /// Minimum attack-surface score to include in output /// /// Findings with a rank score below this threshold are suppressed. /// Requires ranking to be enabled (has no effect with --no-rank). /// Example: --min-score 50 #[arg(long)] min_score: Option, /// Minimum confidence level to include in output /// /// Values: low, medium, high. Findings below this level are dropped. /// JSON/SARIF include all unless filtered. #[arg(long)] min_confidence: Option, // ── Deprecated aliases (hidden) ───────────────────────────────── /// Deprecated: use --index off #[arg(long, hide = true)] no_index: bool, /// Deprecated: use --index rebuild #[arg(long, hide = true)] rebuild_index: bool, /// Deprecated: use --severity HIGH #[arg(long, hide = true)] high_only: bool, /// Deprecated: use --mode ast #[arg(long, hide = true)] ast_only: bool, /// Deprecated: use --mode cfg #[arg(long, hide = true)] cfg_only: bool, }, /// Manage project indexes Index { #[command(subcommand)] action: IndexAction, }, /// List all indexed projects List { /// Show detailed information #[arg(short, long)] verbose: bool, }, /// Remove project from index Clean { /// Project name or path to clean project: Option, /// Clean all projects #[arg(long)] all: bool, }, /// Manage analysis configuration Config { #[command(subcommand)] action: ConfigAction, }, } #[derive(Subcommand)] pub enum ConfigAction { /// Print effective merged configuration as TOML Show, /// Print configuration directory path Path, /// Add a label rule to nyx.local AddRule { /// Language slug (e.g. javascript, rust, python) #[arg(long)] lang: String, /// Function or property name to match #[arg(long)] matcher: String, /// Rule kind: source, sanitizer, or sink #[arg(long)] kind: String, /// Capability: env_var, html_escape, shell_escape, url_encode, json_parse, file_io, or all #[arg(long)] cap: String, }, /// Add a terminator function to nyx.local AddTerminator { /// Language slug (e.g. javascript, rust, python) #[arg(long)] lang: String, /// Function name that terminates execution (e.g. process.exit) #[arg(long)] name: String, }, } #[derive(Subcommand)] pub enum IndexAction { /// Build or update index for current project Build { /// Path to index (defaults to current directory) #[arg(default_value = ".")] path: String, /// Force full rebuild #[arg(short, long)] force: bool, }, /// Show index status and statistics Status { /// Project path to check #[arg(default_value = ".")] path: String, }, }