Plan: CLI Restructure — Command Groups, JMESPath --query, --output Format#
Context#
The Orchestrator CLI has 9 flat commands with inconsistent categorization. All output is Spectre.Console tables only — no machine-readable format for scripting. The user wants Azure CLI-style organization with JMESPath query support and output format options.
Step 1: Add JMESPath NuGet package#
File: src/BenefitManager.Orchestrator/BenefitManager.Orchestrator.csproj
- [x] Package added (
JmesPath.Netv1.1.0 —DevLab.JmesPathnamespace)
Step 2: Create QuerySettings and ManageSettings base classes#
New file: src/BenefitManager.Orchestrator/Commands/QuerySettings.cs
public enum OutputFormat { Table, Json, Md }
public class QuerySettings : CommandSettings
{
[CommandOption("--output|-o")]
[Description("Output format: table, json, or md (default: table)")]
[DefaultValue(OutputFormat.Table)]
public OutputFormat Output { get; init; } = OutputFormat.Table;
[CommandOption("--query")]
[Description("JMESPath expression to filter/reshape output (uses camelCase property names)")]
public string? Query { get; init; }
}
New file: src/BenefitManager.Orchestrator/Commands/ManageSettings.cs
- [x]
QuerySettingscreated with--outputand--query - [x]
ManageSettingscreated as empty marker
Step 3: Create OutputFormatter#
New file: src/BenefitManager.Orchestrator/Infrastructure/OutputFormatter.cs
Static helper. Flow:
1. Serialize IReadOnlyList<T> to JSON (camelCase, JsonStringEnumConverter)
2. If --query set, apply JMESPath via new JmesPath().Transform(json, query)
3. Render based on --output:
- json → pretty-print to TextWriter output (defaults to Console.Out)
- table → parse JsonElement, auto-detect columns from first object's properties, build Spectre.Console.Table
- md → pipe-delimited markdown table to TextWriter output
Key signatures:
public static class OutputFormatter
{
public static void Render<T>(IReadOnlyList<T> data, OutputFormat format, string? query = null, IAnsiConsole? console = null, TextWriter? output = null);
}
IAnsiConsole and TextWriter parameters enable test injection.
Dynamic table rendering handles JMESPath-reshaped data (columns from property names of first JSON object). Booleans render as ✓/✗ in table/md modes. Empty collections print "No results." via the console. Invalid JMESPath is caught and printed as an error.
- [x]
OutputFormattercreated with json/table/md rendering - [x] JMESPath integration working
- [x] Dynamic column detection from JsonElement
Step 4: Reparent Settings classes#
Query commands — base changed to QuerySettings:
- CustomersSettings in CustomersCommand.cs
- StagesSettings in StagesCommand.cs
- VersionsSettings in VersionsCommand.cs
- StatusSettings in StatusCommand.cs
Manage commands — base changed to ManageSettings:
- RemoveSettings in RemoveCommand.cs
- ServiceSettings in ServiceCommand.cs
- IisSettings in IisCommand.cs
- StageRemoveSettings in StageRemoveCommand.cs
- [x] Query settings reparented
- [x] Manage settings reparented
Step 5: Refactor query commands to use OutputFormatter#
Replace manual Table construction with OutputFormatter.Render(data, settings.Output, settings.Query).
CustomersCommand, StagesCommand, VersionsCommand — each reduced to ~5 lines.
StatusCommand — special case:
- Dual path: legacy per-server grouped visual for format=table && query=null, OutputFormatter otherwise.
- ServerStatusResult record added to ManagementModels.cs:
public record ServerStatusResult(string Server, List<InstallationSummary> Installations, string? Error);
- [x]
CustomersCommanduses OutputFormatter - [x]
StagesCommanduses OutputFormatter - [x]
VersionsCommanduses OutputFormatter - [x]
StatusCommandrefactored with ServerStatusResult + dual rendering path
Step 6: Restructure Program.cs with AddBranch#
app.Configure(cli =>
{
cli.AddCommand<DeployCommand>("deploy")...
cli.AddBranch<QuerySettings>("query", query =>
{
query.SetDescription("Query data from environments and agents.");
query.AddCommand<CustomersCommand>("customers")...
query.AddCommand<StagesCommand>("stages")...
query.AddCommand<VersionsCommand>("versions")...
query.AddCommand<StatusCommand>("status")...
});
cli.AddBranch<ManageSettings>("manage", manage =>
{
manage.SetDescription("Manage deployments, services, IIS pools, and stages.");
manage.AddCommand<RemoveCommand>("remove")...
manage.AddCommand<ServiceCommand>("service")...
manage.AddCommand<IisCommand>("iis")...
manage.AddCommand<StageRemoveCommand>("remove-stage")...
});
});
- [x]
querybranch with 4 subcommands - [x]
managebranch with 4 subcommands - [x]
deployat root - [x] Examples updated with group prefix
Step 7: Tests#
New file: tests/BenefitManager.Orchestrator.Tests/OutputFormatterTests.cs
- [x] Render json: verify camelCase output
- [x] Render table: verify columns detected from record properties
- [x] Render md: verify pipe-delimited markdown with header separator
- [x] JMESPath filter:
[?stageCount > \2`]` on CustomerSummary list - [x] JMESPath projection:
[].{n: name}reshapes columns - [x] Empty collection: "No results." message
- [x] Invalid JMESPath: error message, no exception
- [x] Existing 27 tests still pass (34 total pass)
Files Summary#
| Action | File |
|---|---|
| Modify | src/BenefitManager.Orchestrator/BenefitManager.Orchestrator.csproj |
| Modify | src/BenefitManager.Orchestrator/Program.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/CustomersCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/StagesCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/VersionsCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/StatusCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/RemoveCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/ServiceCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/IisCommand.cs |
| Modify | src/BenefitManager.Orchestrator/Commands/StageRemoveCommand.cs |
| Modify | src/BenefitManager.Contracts/Models/ManagementModels.cs |
| New | src/BenefitManager.Orchestrator/Commands/QuerySettings.cs |
| New | src/BenefitManager.Orchestrator/Commands/ManageSettings.cs |
| New | src/BenefitManager.Orchestrator/Infrastructure/OutputFormatter.cs |
| New | tests/BenefitManager.Orchestrator.Tests/OutputFormatterTests.cs |