// ----------------------------------------------------------------------------- // WitnessCommandGroup.cs // Sprint: SPRINT_3700_0005_0001_witness_ui_cli // Tasks: CLI-001, CLI-002, CLI-003, CLI-004 // Description: CLI command group for reachability witness operations. // ----------------------------------------------------------------------------- using System.CommandLine; using Microsoft.Extensions.DependencyInjection; using StellaOps.Cli.Extensions; using Spectre.Console; namespace StellaOps.Cli.Commands; /// /// CLI command group for reachability witness operations. /// internal static class WitnessCommandGroup { internal static Command BuildWitnessCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var witness = new Command("witness", "Reachability witness operations."); witness.Add(BuildWitnessShowCommand(services, verboseOption, cancellationToken)); witness.Add(BuildWitnessVerifyCommand(services, verboseOption, cancellationToken)); witness.Add(BuildWitnessListCommand(services, verboseOption, cancellationToken)); witness.Add(BuildWitnessExportCommand(services, verboseOption, cancellationToken)); return witness; } private static Command BuildWitnessShowCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var witnessIdArg = new Argument("witness-id") { Description = "The witness ID to display (e.g., wit:sha256:abc123)." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: text (default), json, yaml." }.SetDefaultValue("text").FromAmong("text", "json", "yaml"); var noColorOption = new Option("--no-color") { Description = "Disable colored output." }; var pathOnlyOption = new Option("--path-only") { Description = "Show only the call path, not full witness details." }; var command = new Command("show", "Display a witness with call path visualization.") { witnessIdArg, formatOption, noColorOption, pathOnlyOption, verboseOption }; command.SetAction(parseResult => { var witnessId = parseResult.GetValue(witnessIdArg)!; var format = parseResult.GetValue(formatOption)!; var noColor = parseResult.GetValue(noColorOption); var pathOnly = parseResult.GetValue(pathOnlyOption); var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleWitnessShowAsync( services, witnessId, format, noColor, pathOnly, verbose, cancellationToken); }); return command; } private static Command BuildWitnessVerifyCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var witnessIdArg = new Argument("witness-id") { Description = "The witness ID to verify." }; var publicKeyOption = new Option("--public-key", new[] { "-k" }) { Description = "Path to public key file (default: fetch from authority)." }; var offlineOption = new Option("--offline") { Description = "Verify using local key only, don't fetch from server." }; var command = new Command("verify", "Verify a witness signature.") { witnessIdArg, publicKeyOption, offlineOption, verboseOption }; command.SetAction(parseResult => { var witnessId = parseResult.GetValue(witnessIdArg)!; var publicKeyPath = parseResult.GetValue(publicKeyOption); var offline = parseResult.GetValue(offlineOption); var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleWitnessVerifyAsync( services, witnessId, publicKeyPath, offline, verbose, cancellationToken); }); return command; } private static Command BuildWitnessListCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var scanOption = new Option("--scan", new[] { "-s" }) { Description = "Scan ID to list witnesses for.", Required = true }; var vulnOption = new Option("--vuln", new[] { "-v" }) { Description = "Filter witnesses by CVE/vulnerability ID." }; var tierOption = new Option("--tier") { Description = "Filter by confidence tier: confirmed, likely, present, unreachable." }.FromAmong("confirmed", "likely", "present", "unreachable"); var reachableOnlyOption = new Option("--reachable-only") { Description = "Show only reachable witnesses." }; // EBPF-003: Add --probe-type filter option // Sprint: SPRINT_20260122_038_Scanner_ebpf_probe_type var probeTypeOption = new Option("--probe-type", new[] { "-p" }) { Description = "Filter by eBPF probe type: kprobe, kretprobe, uprobe, uretprobe, tracepoint, usdt, fentry, fexit." }.FromAmong("kprobe", "kretprobe", "uprobe", "uretprobe", "tracepoint", "usdt", "fentry", "fexit"); var formatOption = new Option("--format", new[] { "-f" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var limitOption = new Option("--limit", new[] { "-l" }) { Description = "Maximum number of witnesses to return." }.SetDefaultValue(50); var command = new Command("list", "List witnesses for a scan.") { scanOption, vulnOption, tierOption, reachableOnlyOption, probeTypeOption, formatOption, limitOption, verboseOption }; command.SetAction(parseResult => { var scanId = parseResult.GetValue(scanOption)!; var vuln = parseResult.GetValue(vulnOption); var tier = parseResult.GetValue(tierOption); var reachableOnly = parseResult.GetValue(reachableOnlyOption); var probeType = parseResult.GetValue(probeTypeOption); var format = parseResult.GetValue(formatOption)!; var limit = parseResult.GetValue(limitOption); var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleWitnessListAsync( services, scanId, vuln, tier, reachableOnly, probeType, format, limit, verbose, cancellationToken); }); return command; } private static Command BuildWitnessExportCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var witnessIdArg = new Argument("witness-id") { Description = "The witness ID to export." }; var formatOption = new Option("--format", new[] { "-f" }) { Description = "Export format: json (default), sarif." }.SetDefaultValue("json").FromAmong("json", "sarif"); var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output file path (default: stdout)." }; var includeDsseOption = new Option("--include-dsse") { Description = "Include DSSE envelope in export." }; var command = new Command("export", "Export a witness to file.") { witnessIdArg, formatOption, outputOption, includeDsseOption, verboseOption }; command.SetAction(parseResult => { var witnessId = parseResult.GetValue(witnessIdArg)!; var format = parseResult.GetValue(formatOption)!; var outputPath = parseResult.GetValue(outputOption); var includeDsse = parseResult.GetValue(includeDsseOption); var verbose = parseResult.GetValue(verboseOption); return CommandHandlers.HandleWitnessExportAsync( services, witnessId, format, outputPath, includeDsse, verbose, cancellationToken); }); return command; } }