using System.CommandLine; using StellaOps.Cli.Extensions; namespace StellaOps.Cli.Commands; /// /// CLI commands for AI model bundle management. /// Sprint: SPRINT_20251226_019_AI_offline_inference /// Task: OFFLINE-13, OFFLINE-14 /// internal static class ModelCommandGroup { internal static Command BuildModelCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var model = new Command("model", "AI model bundle management for offline inference."); model.Add(BuildModelListCommand(services, verboseOption, cancellationToken)); model.Add(BuildModelPullCommand(services, verboseOption, cancellationToken)); model.Add(BuildModelVerifyCommand(services, verboseOption, cancellationToken)); model.Add(BuildModelInfoCommand(services, verboseOption, cancellationToken)); model.Add(BuildModelRemoveCommand(services, verboseOption, cancellationToken)); return model; } private static Command BuildModelListCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var bundlePathOption = new Option("--bundle-path", new[] { "-p" }) { Description = "Path to model bundles directory (defaults to ~/.stellaops/models)." }; var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var command = new Command("list", "List available model bundles.") { bundlePathOption, outputOption, verboseOption }; command.SetAction(async parseResult => { var bundlePath = parseResult.GetValue(bundlePathOption); var output = parseResult.GetValue(outputOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); await CommandHandlers.HandleModelListAsync( services, bundlePath, output, verbose, cancellationToken); }); return command; } private static Command BuildModelPullCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var modelNameArg = new Argument("model-name") { Description = "Model name to pull (e.g., llama3-8b, mistral-7b, phi-3)." }; var quantOption = new Option("--quant", new[] { "-q" }) { Description = "Quantization level (e.g., Q4_K_M, Q5_K_M, FP16)." }.SetDefaultValue("Q4_K_M"); var offlineOption = new Option("--offline") { Description = "Pull from local cache or USB transfer (no network)." }; var sourceOption = new Option("--source", new[] { "-s" }) { Description = "Source path for offline pull (USB mount, network share)." }; var bundlePathOption = new Option("--bundle-path", new[] { "-p" }) { Description = "Destination path for model bundles (defaults to ~/.stellaops/models)." }; var verifyOption = new Option("--verify") { Description = "Verify bundle integrity after pull." }.SetDefaultValue(true); var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var command = new Command("pull", "Pull a model bundle for offline inference.") { modelNameArg, quantOption, offlineOption, sourceOption, bundlePathOption, verifyOption, outputOption, verboseOption }; command.SetAction(async parseResult => { var modelName = parseResult.GetValue(modelNameArg) ?? string.Empty; var quant = parseResult.GetValue(quantOption) ?? "Q4_K_M"; var offline = parseResult.GetValue(offlineOption); var source = parseResult.GetValue(sourceOption); var bundlePath = parseResult.GetValue(bundlePathOption); var verify = parseResult.GetValue(verifyOption); var output = parseResult.GetValue(outputOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); await CommandHandlers.HandleModelPullAsync( services, modelName, quant, offline, source, bundlePath, verify, output, verbose, cancellationToken); }); return command; } private static Command BuildModelVerifyCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var modelNameArg = new Argument("model-name") { Description = "Model name or path to verify." }; var bundlePathOption = new Option("--bundle-path", new[] { "-p" }) { Description = "Path to model bundles directory (defaults to ~/.stellaops/models)." }; var checkSignatureOption = new Option("--check-signature") { Description = "Verify cryptographic signature on the bundle." }.SetDefaultValue(true); var trustRootOption = new Option("--trust-root") { Description = "Path to trust root public key for signature verification." }; var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var command = new Command("verify", "Verify model bundle integrity and signature.") { modelNameArg, bundlePathOption, checkSignatureOption, trustRootOption, outputOption, verboseOption }; command.SetAction(async parseResult => { var modelName = parseResult.GetValue(modelNameArg) ?? string.Empty; var bundlePath = parseResult.GetValue(bundlePathOption); var checkSignature = parseResult.GetValue(checkSignatureOption); var trustRoot = parseResult.GetValue(trustRootOption); var output = parseResult.GetValue(outputOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); await CommandHandlers.HandleModelVerifyAsync( services, modelName, bundlePath, checkSignature, trustRoot, output, verbose, cancellationToken); }); return command; } private static Command BuildModelInfoCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var modelNameArg = new Argument("model-name") { Description = "Model name to get information for." }; var bundlePathOption = new Option("--bundle-path", new[] { "-p" }) { Description = "Path to model bundles directory (defaults to ~/.stellaops/models)." }; var outputOption = new Option("--output", new[] { "-o" }) { Description = "Output format: table (default), json." }.SetDefaultValue("table").FromAmong("table", "json"); var command = new Command("info", "Display model bundle metadata and requirements.") { modelNameArg, bundlePathOption, outputOption, verboseOption }; command.SetAction(async parseResult => { var modelName = parseResult.GetValue(modelNameArg) ?? string.Empty; var bundlePath = parseResult.GetValue(bundlePathOption); var output = parseResult.GetValue(outputOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); await CommandHandlers.HandleModelInfoAsync( services, modelName, bundlePath, output, verbose, cancellationToken); }); return command; } private static Command BuildModelRemoveCommand( IServiceProvider services, Option verboseOption, CancellationToken cancellationToken) { var modelNameArg = new Argument("model-name") { Description = "Model name to remove." }; var bundlePathOption = new Option("--bundle-path", new[] { "-p" }) { Description = "Path to model bundles directory (defaults to ~/.stellaops/models)." }; var forceOption = new Option("--force", new[] { "-f" }) { Description = "Force removal without confirmation." }; var command = new Command("remove", "Remove a model bundle.") { modelNameArg, bundlePathOption, forceOption, verboseOption }; command.SetAction(async parseResult => { var modelName = parseResult.GetValue(modelNameArg) ?? string.Empty; var bundlePath = parseResult.GetValue(bundlePathOption); var force = parseResult.GetValue(forceOption); var verbose = parseResult.GetValue(verboseOption); await CommandHandlers.HandleModelRemoveAsync( services, modelName, bundlePath, force, verbose, cancellationToken); }); return command; } }