Skip to content

Automation Extension

Automator|Client Services · .NET 10

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 client
  • Console — structured output (title, text, success, error lines)
  • DisplayLevel — respects the global full / compact / silent toggle
  • Exit code — returned as int from Execute()

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

  1. Build the extension project in Release mode
  2. Copy the output DLL (and its dependencies) into the extensions subfolder of the Payroll Console installation
  3. Verify discovery: PayrollConsole Help ListTenants
  4. 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:

LLM Integration Pattern

The extension handles format translation, validation, and structured feedback — keeping the LLM pipeline decoupled from the REST API details.

Next Steps