| name | pacx-command-writer |
| description | Guide for writing new PACX CLI commands (verbs) following established project conventions. Use this skill when asked to create, scaffold, or add a new command to the Greg.Xrm.Command repository.
|
PACX Command Writer
Every PACX command is a pair of files in Greg.Xrm.Command.Core\Commands\<Domain>\. There is no manual DI registration — Extensions.RegisterCommandExecutors() auto-scans all ICommandExecutor<T> implementations at startup.
File 1 — <Verb>Command.cs (options holder)
This class declares CLI options via attributes and optionally handles cross-option validation and usage examples. It must not contain business logic.
using Greg.Xrm.Command.Parsing;
using Greg.Xrm.Command.Services;
using System.ComponentModel.DataAnnotations;
namespace Greg.Xrm.Command.Commands.<Domain>
{
[Command("<noun>", "<verb>", HelpText = "One-sentence description.")]
[Alias("<verb>", "<noun>")]
public class <Verb>Command : IValidatableObject, ICanProvideUsageExample
{
[Option("name", "n", Order = 1, HelpText = "...")]
[Required]
public string? Name { get; set; }
[Option("description", "d", Order = 10, HelpText = "...")]
public string? Description { get; set; }
[Option("solution", "s", Order = 50, HelpText = "Unmanaged solution name. Uses the current default solution if omitted.")]
public string? SolutionName { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if ()
yield return new ValidationResult("...", new[] { nameof(OptionA), nameof(OptionB) });
}
public void WriteUsageExamples(MarkdownWriter writer)
{
writer.WriteParagraph("Minimal usage:");
writer.WriteCodeBlock("pacx <noun> <verb> --name \"value\"", "Powershell");
}
}
}
[Option] conventions
| Property | Rule |
|---|
longName | camelCase — e.g. schemaName, displayName, requiredLevel |
shortName | Single char or short abbreviation, unique within the command — e.g. "n", "sn", "par" |
HelpText | Always set — shown in pacx help and interactive mode |
Order | Required options: 1–9; grouped optional options: 10–49; --solution: 50+ |
DefaultValue | Set on the attribute when there is a meaningful default the user should see |
[Required] | Mark all mandatory options |
[Command] and [Alias] conventions
- Verb order in
[Command] is always noun first: [Command("table", "create", ...)].
[Alias] reverses the order when natural: [Alias("create", "table")].
- Only add
[Alias] when the reversed form is genuinely useful.
File 2 — <Verb>CommandExecutor.cs (business logic)
using Greg.Xrm.Command.Services.Connection;
using Greg.Xrm.Command.Services.Output;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using System.ServiceModel;
namespace Greg.Xrm.Command.Commands.<Domain>
{
public class <Verb>CommandExecutor(
IOutput output,
IOrganizationServiceRepository organizationServiceRepository) : ICommandExecutor<<Verb>Command>
{
public async Task<CommandResult> ExecuteAsync(<Verb>Command command, CancellationToken cancellationToken)
{
output.Write("Connecting to the current dataverse environment...");
var crm = await organizationServiceRepository.GetCurrentConnectionAsync();
output.WriteLine("Done", ConsoleColor.Green);
try
{
var solutionName = command.SolutionName;
if (string.IsNullOrWhiteSpace(solutionName))
{
solutionName = await organizationServiceRepository.GetCurrentDefaultSolutionAsync();
if (solutionName == null)
return CommandResult.Fail("No solution name provided and no current solution name found in the settings.");
}
output.Write("Performing the operation...");
var request = new OrganizationRequest();
var response = await crm.ExecuteAsync(request);
output.WriteLine(" Done", ConsoleColor.Green);
var result = CommandResult.Success();
result["EntityId"] = response.;
return result;
}
catch (FaultException<OrganizationServiceFault> ex)
{
return CommandResult.Fail(ex.Message, ex);
}
}
private static string ComputeSchemaName(string displayName, string publisherPrefix)
{
}
}
}
Executor conventions
- Never write to
Console directly — always use IOutput.
- If you need additional output formatting you can use
IAnsiConsole from Greg.Xrm.Command.Services.Output, which wraps Spectre.Console functionality.
- Inline progress pattern:
output.Write("Step…") → do async work → output.WriteLine(" Done", ConsoleColor.Green).
- Highlight important user-provided values in yellow:
output.Write(command.SchemaName, ConsoleColor.Yellow).
- Resolve omitted
--solution via organizationServiceRepository.GetCurrentDefaultSolutionAsync().
- Catch
FaultException<OrganizationServiceFault> at minimum. Catch Exception when non-Dataverse failures are possible.
- Put non-trivial computation in
private static helper methods.
- Populate
CommandResult with key/value output pairs for structured consumers.
Tests
Once the command and executor files are in place, use the pacx-unit-test-writer skill to generate the corresponding test files.
Checklist before finishing