Command Definition

Command Definition

The @CommandDefinition annotation is used to define a command class.

Required Properties

PropertyTypeDescription
nameStringThe command name

Optional Properties

PropertyTypeDefaultDescription
aliasesString[]{}Alternative names for the command
descriptionString""Command description shown in help
generateHelpbooleanfalseAuto-generate --help option
disableParsingbooleanfalseSkip parsing (everything goes to @Arguments)
versionString""Version string (adds --version, -v option)
validatorClass<? extends CommandValidator>NullCommandValidator.classValidator to run before execution
resultHandlerClass<? extends ResultHandler>NullResultHandler.classHandler to run after execution
activatorClass<? extends CommandActivator>NullCommandActivator.classActivator to check if command is available
defaultValueProviderClass<? extends DefaultValueProvider>NullDefaultValueProvider.classDynamic default value resolver
stopAtFirstPositionalbooleanfalseStop option parsing after the first positional argument
helpUrlString""URL to documentation (shown in --help output)
helpGroupString""Group heading when listed as a subcommand in parent’s help
helpSectionProviderClass<? extends HelpSectionProvider>NullHelpSectionProvider.classProvider for dynamic help sections

Example

@CommandDefinition(
    name = "copy",
    aliases = {"cp"},
    description = "Copy files",
    generateHelp = true
)
public class CopyCommand implements Command<CommandInvocation> {
    
    @Option(shortName = 'r', description = "Recursive copy")
    private boolean recursive;

    @Override
    public CommandResult execute(CommandInvocation invocation) {
        // implementation
        return CommandResult.SUCCESS;
    }
}

Command Interface

Your command must implement the Command<T extends CommandInvocation> interface:

public interface Command<T extends CommandInvocation> {
    CommandResult execute(T commandInvocation) 
        throws CommandException, InterruptedException;
}

CommandResult

Return one of the following:

  • CommandResult.SUCCESS - Command completed successfully
  • CommandResult.FAILURE - Command failed
  • CommandResult.RETURN - Return from current subcommand

Dynamic Default Value Provider

The defaultValueProvider attribute specifies a class that resolves option defaults at runtime. This is useful when defaults come from configuration files, environment variables, or other external sources not known at compile time.

Implementing a Provider

Create a class that implements DefaultValueProvider:

public class ConfigDefaultProvider implements DefaultValueProvider {

    @Override
    public String defaultValue(ProcessedOption option) {
        // Use option.name() and option.parent().name() to build a config key
        String key = option.parent().name() + "." + option.name();
        return Configuration.get(key);  // returns null if not configured
    }
}

Registering the Provider

@CommandDefinition(
    name = "init",
    description = "Initialize a project",
    defaultValueProvider = ConfigDefaultProvider.class
)
public class InitCommand implements Command<CommandInvocation> {

    @Option(defaultValue = "hello")
    private String template;

    @Option
    private String editor;

    @Override
    public CommandResult execute(CommandInvocation invocation) {
        invocation.println("Template: " + template);
        invocation.println("Editor: " + editor);
        return CommandResult.SUCCESS;
    }
}

Value Precedence

When determining option values, the precedence is (highest to lowest):

  1. User-provided value – Explicitly set on the command line
  2. Dynamic default – Returned by the DefaultValueProvider (if non-null)
  3. Static default – The defaultValue from the annotation
  4. null – If nothing else is set

If the provider returns null for an option, aesh falls back to the static defaultValue. This lets you use annotation defaults as fallbacks:

// Provider returns "from-config" for template -> uses "from-config"
// Provider returns null for editor -> falls back to static default "vi"
@Option(defaultValue = "vi")
private String editor;

See Options - Dynamic Default Values for more details.

Stop at First Positional

When stopAtFirstPositional = true, option parsing stops as soon as the first positional argument is consumed. All remaining tokens are treated as positional arguments, even if they look like options.

This is useful for commands that pass arguments through to another process:

@CommandDefinition(
    name = "run",
    description = "Run a script",
    stopAtFirstPositional = true,
    generateHelp = true
)
public class RunCommand implements Command<CommandInvocation> {

    @Option(hasValue = false, description = "Enable verbose output")
    private boolean verbose;

    @Arguments
    private List<String> args;

    @Override
    public CommandResult execute(CommandInvocation invocation) {
        // args contains the script name and all arguments after it
        return CommandResult.SUCCESS;
    }
}

With the command above:

InputResult
run --verbose myscript.javaverbose=true, args=[myscript.java]
run --verbose myscript.java -Dfoo=bar --helpverbose=true, args=[myscript.java, -Dfoo=bar, --help]
run myscript.java --verboseverbose=false, args=[myscript.java, --verbose]
run --helpDisplays help output

Note that --help (and --version) before the first positional still work normally when generateHelp = true. Only tokens after the first positional are treated as passthrough arguments.

CommandInvocation

Provides access to:

  • println(String) - Output text to the console
  • print(String) - Output text without newline
  • stop() - Stop the console
  • getShell() - Access the shell
  • getHelpInfo(String) - Get help text

Help Group for Subcommands

The helpGroup property on @CommandDefinition (and @GroupCommandDefinition) controls how a subcommand appears in its parent’s help output. Subcommands with the same helpGroup value are displayed together under that heading.

Basic Usage

@GroupCommandDefinition(
    name = "cli",
    description = "My CLI tool",
    generateHelp = true,
    groupCommands = {
        BuildCommand.class, TestCommand.class,
        InstallCommand.class, PublishCommand.class,
        InfoCommand.class, VersionCommand.class
    }
)
public class CliCommand implements GroupCommand<CommandInvocation> {
    @Override
    public CommandResult execute(CommandInvocation invocation) {
        return CommandResult.SUCCESS;
    }
}

@CommandDefinition(name = "build", description = "Build the project", helpGroup = "Build")
public class BuildCommand implements Command<CommandInvocation> { /* ... */ }

@CommandDefinition(name = "test", description = "Run tests", helpGroup = "Build")
public class TestCommand implements Command<CommandInvocation> { /* ... */ }

@CommandDefinition(name = "install", description = "Install dependencies", helpGroup = "Publish")
public class InstallCommand implements Command<CommandInvocation> { /* ... */ }

@CommandDefinition(name = "publish", description = "Publish package", helpGroup = "Publish")
public class PublishCommand implements Command<CommandInvocation> { /* ... */ }

@CommandDefinition(name = "info", description = "Show project info")
public class InfoCommand implements Command<CommandInvocation> { /* ... */ }

@CommandDefinition(name = "version", description = "Show version")
public class VersionCommand implements Command<CommandInvocation> { /* ... */ }

Help output:

Usage: cli [<options>]
My CLI tool

Build:
    build     Build the project
    test      Run tests

Publish:
    install   Install dependencies
    publish   Publish package

Other:
    info      Show project info
    version   Show version

How It Works

  1. Subcommands with the same helpGroup value are grouped under that heading
  2. Named groups appear first, in the order their first subcommand was defined
  3. Subcommands without a helpGroup appear under a default heading
  4. If all subcommands have a helpGroup, there is no default group

This is separate from the helpGroup property on @Option, which groups options within a single command’s help output. See Options - Help Grouping.

Help Section Provider

The helpSectionProvider property lets you dynamically add sections to a command’s help output at render time. This is useful for showing external plugins, aliases, or dynamically discovered commands without statically defining them.

Implementing a Provider

Create a class that implements HelpSectionProvider:

public class PluginHelpProvider implements HelpSectionProvider {

    @Override
    public Map<String, List<HelpEntry>> getAdditionalSections() {
        Map<String, List<HelpEntry>> sections = new LinkedHashMap<>();

        // Discover plugins at runtime
        List<HelpEntry> plugins = new ArrayList<>();
        plugins.add(new HelpEntry("docker", "Docker integration plugin"));
        plugins.add(new HelpEntry("k8s", "Kubernetes deployment plugin"));
        sections.put("Plugins", plugins);

        return sections;
    }
}

Registering the Provider

@GroupCommandDefinition(
    name = "app",
    description = "My application",
    generateHelp = true,
    groupCommands = {RunCommand.class, BuildCommand.class},
    helpSectionProvider = PluginHelpProvider.class
)
public class AppCommand implements GroupCommand<CommandInvocation> {
    @Override
    public CommandResult execute(CommandInvocation invocation) {
        return CommandResult.SUCCESS;
    }
}

Help output:

Usage: app [<options>]
My application

app commands:
    run       Run the application
    build     Build the project

Plugins:
    docker    Docker integration plugin
    k8s       Kubernetes deployment plugin

Merging with helpGroup

If a provider section name matches an existing helpGroup from statically defined subcommands, the entries are appended to that group rather than creating a duplicate heading.

Key Points

  1. Zero startup cost – The provider class is stored as a reference and only instantiated when help is rendered
  2. Works with both @CommandDefinition and @GroupCommandDefinition
  3. HelpEntry is a simple value class with name() and description() (description is optional)
  4. Return an empty map (not null) if there are no additional sections to show