Automation Extension
The Payroll Console can be extended with custom commands — the primary mechanism for Automators to integrate domain-specific tooling, external formats, and LLM pipelines without forking the platform.
An extension is a standard .NET class library that is dropped into the extensions subfolder of the Payroll Console. All commands in the assembly are discovered automatically at startup.
Use Cases
| Scenario | Example |
|---|---|
| LLM-driven DSL | Parse natural-language payroll rules and import them as regulations |
| Foreign format import | Import third-party payroll data (XML, CSV, proprietary) directly into the backend |
| Domain automation | Tenant provisioning, bulk case setup, scheduled payrun triggers |
| Validation tooling | Cross-system reconciliation, pre-flight checks before a payrun |
| CI/CD integration | Custom pipeline steps with exit codes and structured output |
Architecture
An extension command consists of three parts:
| Part | Base Type | Purpose |
|---|---|---|
| Command | CommandBase<TParameters> |
Execution logic, REST API access, console output |
| Parameters | ICommandParameters |
Argument and toggle definitions |
| Registration | [Command("Name")] attribute |
Discovery and routing by the console host |
The console host provides a CommandContext with:
HttpClient— typed Payroll REST API clientConsole— structured output (title, text, success, error lines)DisplayLevel— respects the globalfull/compact/silenttoggle- Exit code — returned as
intfromExecute()
Project Setup
Reference the PayrollEngine.Client.Services NuGet package:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PayrollEngine.Client.Services" Version="0.9.0" />
</ItemGroup>
</Project>
CopyLocalLockFileAssemblies ensures all dependencies are copied to the output folder.
Implementing a Command
Parameters
Define command toggles via an enum and implement ICommandParameters:
public enum Order // toggle values map to command-line switches
{
IdentifierAscending, // /identifierAscending (default)
IdentifierDescending, // /identifierDescending
CreatedDescending // /createdDescending
}
public class ListTenantsParameters : ICommandParameters
{
public Order Order { get; private init; } = Order.IdentifierAscending; // default toggle
public Type[] Toggles => [ typeof(Order) ]; // register toggle enum with the console host
public string Test() => null; // no test mode for this command
public static ListTenantsParameters ParserFrom(CommandLineParser parser) =>
new() { Order = parser.GetEnumToggle(Order.IdentifierAscending) }; // parse from argv
}
Command
Implement CommandBase<TParameters> and annotate with [Command]:
[Command("ListTenants")] // registers the command name for discovery and routing
internal sealed class ListTenantsCommand : CommandBase<ListTenantsParameters>
{
protected override async Task<int> Execute(
CommandContext context, ListTenantsParameters parameters)
{
var console = context.Console;
console.DisplayTitleLine("List Tenants");
var service = new TenantService(context.HttpClient); // typed API client from context
var tenants = await service.QueryAsync<Tenant>(new(), new()
{
OrderBy = nameof(Tenant.Identifier) + " ASC" // OData-style ordering
});
foreach (var tenant in tenants)
console.DisplayTextLine($" {tenant.Identifier,-30}{tenant.Created,-25:g}");
console.DisplaySuccessLine($"Total {tenants.Count} tenants.");
return 0; // exit code 0 = success
}
public override ICommandParameters GetParameters(CommandLineParser parser) =>
ListTenantsParameters.ParserFrom(parser); // bind argv to parameters
public override void ShowHelp(ICommandConsole console)
{
console.DisplayTitleLine("- ListTenants");
console.DisplayTextLine(" List all payroll tenants");
console.DisplayTextLine(" Toggles:");
console.DisplayTextLine(" /identifierAscending (default), /identifierDescending");
console.DisplayTextLine(" /createdDescending");
console.DisplayTextLine(" Examples:");
console.DisplayTextLine(" ListTenants");
console.DisplayTextLine(" ListTenants /createdDescending");
}
}
Deployment
- Build the extension project in Release mode
- Copy the output DLL (and its dependencies) into the
extensionssubfolder of the Payroll Console installation - Verify discovery:
PayrollConsole Help ListTenants - Run the command:
PayrollConsole ListTenants
Using Extensions in Command Files
Custom commands integrate seamlessly into .pecmd scripts:
# provision new tenant and verify
ListTenants /createdDescending
PayrollImport NewTenant.json
PayrunTest NewTenant.pt.json
Parameters and toggles work identically to built-in commands.
LLM Integration Pattern
A typical LLM-to-payroll pipeline using an Automation Extension:
The extension handles format translation, validation, and structured feedback — keeping the LLM pipeline decoupled from the REST API details.
Next Steps
- Client Services — SDK overview and NuGet packages
- Client Tutorials — working example (
PayrollConsoleExtension) - Command Files — batch execution with
.pecmd - Technical Integration — broader integration patterns