304 lines
10 KiB
C#
304 lines
10 KiB
C#
using System.CommandLine;
|
|
using StellaOps.Cli.Extensions;
|
|
|
|
namespace StellaOps.Cli.Commands;
|
|
|
|
/// <summary>
|
|
/// CLI commands for AI model bundle management.
|
|
/// Sprint: SPRINT_20251226_019_AI_offline_inference
|
|
/// Task: OFFLINE-13, OFFLINE-14
|
|
/// </summary>
|
|
internal static class ModelCommandGroup
|
|
{
|
|
internal static Command BuildModelCommand(
|
|
IServiceProvider services,
|
|
Option<bool> 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<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var bundlePathOption = new Option<string?>("--bundle-path", new[] { "-p" })
|
|
{
|
|
Description = "Path to model bundles directory (defaults to ~/.stellaops/models)."
|
|
};
|
|
|
|
var outputOption = new Option<string?>("--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<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var modelNameArg = new Argument<string>("model-name")
|
|
{
|
|
Description = "Model name to pull (e.g., llama3-8b, mistral-7b, phi-3)."
|
|
};
|
|
|
|
var quantOption = new Option<string?>("--quant", new[] { "-q" })
|
|
{
|
|
Description = "Quantization level (e.g., Q4_K_M, Q5_K_M, FP16)."
|
|
}.SetDefaultValue("Q4_K_M");
|
|
|
|
var offlineOption = new Option<bool>("--offline")
|
|
{
|
|
Description = "Pull from local cache or USB transfer (no network)."
|
|
};
|
|
|
|
var sourceOption = new Option<string?>("--source", new[] { "-s" })
|
|
{
|
|
Description = "Source path for offline pull (USB mount, network share)."
|
|
};
|
|
|
|
var bundlePathOption = new Option<string?>("--bundle-path", new[] { "-p" })
|
|
{
|
|
Description = "Destination path for model bundles (defaults to ~/.stellaops/models)."
|
|
};
|
|
|
|
var verifyOption = new Option<bool>("--verify")
|
|
{
|
|
Description = "Verify bundle integrity after pull."
|
|
}.SetDefaultValue(true);
|
|
|
|
var outputOption = new Option<string?>("--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<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var modelNameArg = new Argument<string>("model-name")
|
|
{
|
|
Description = "Model name or path to verify."
|
|
};
|
|
|
|
var bundlePathOption = new Option<string?>("--bundle-path", new[] { "-p" })
|
|
{
|
|
Description = "Path to model bundles directory (defaults to ~/.stellaops/models)."
|
|
};
|
|
|
|
var checkSignatureOption = new Option<bool>("--check-signature")
|
|
{
|
|
Description = "Verify cryptographic signature on the bundle."
|
|
}.SetDefaultValue(true);
|
|
|
|
var trustRootOption = new Option<string?>("--trust-root")
|
|
{
|
|
Description = "Path to trust root public key for signature verification."
|
|
};
|
|
|
|
var outputOption = new Option<string?>("--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<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var modelNameArg = new Argument<string>("model-name")
|
|
{
|
|
Description = "Model name to get information for."
|
|
};
|
|
|
|
var bundlePathOption = new Option<string?>("--bundle-path", new[] { "-p" })
|
|
{
|
|
Description = "Path to model bundles directory (defaults to ~/.stellaops/models)."
|
|
};
|
|
|
|
var outputOption = new Option<string?>("--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<bool> verboseOption,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var modelNameArg = new Argument<string>("model-name")
|
|
{
|
|
Description = "Model name to remove."
|
|
};
|
|
|
|
var bundlePathOption = new Option<string?>("--bundle-path", new[] { "-p" })
|
|
{
|
|
Description = "Path to model bundles directory (defaults to ~/.stellaops/models)."
|
|
};
|
|
|
|
var forceOption = new Option<bool>("--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;
|
|
}
|
|
}
|