// -----------------------------------------------------------------------------
// 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;
}
}