Command Definition
The @CommandDefinition annotation is used to define a command class.
Required Properties
| Property | Type | Description |
|---|---|---|
name | String | The command name |
Optional Properties
| Property | Type | Default | Description |
|---|---|---|---|
aliases | String[] | {} | Alternative names for the command |
description | String | "" | Command description shown in help |
generateHelp | boolean | false | Auto-generate --help option |
disableParsing | boolean | false | Skip parsing (everything goes to @Arguments) |
version | String | "" | Version string (adds --version, -v option) |
validator | Class<? extends CommandValidator> | NullCommandValidator.class | Validator to run before execution |
resultHandler | Class<? extends ResultHandler> | NullResultHandler.class | Handler to run after execution |
activator | Class<? extends CommandActivator> | NullCommandActivator.class | Activator to check if command is available |
defaultValueProvider | Class<? extends DefaultValueProvider> | NullDefaultValueProvider.class | Dynamic default value resolver |
stopAtFirstPositional | boolean | false | Stop option parsing after the first positional argument |
helpUrl | String | "" | URL to documentation (shown in --help output) |
helpGroup | String | "" | Group heading when listed as a subcommand in parent’s help |
helpSectionProvider | Class<? extends HelpSectionProvider> | NullHelpSectionProvider.class | Provider 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 successfullyCommandResult.FAILURE- Command failedCommandResult.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):
- User-provided value – Explicitly set on the command line
- Dynamic default – Returned by the
DefaultValueProvider(if non-null) - Static default – The
defaultValuefrom the annotation - 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:
| Input | Result |
|---|---|
run --verbose myscript.java | verbose=true, args=[myscript.java] |
run --verbose myscript.java -Dfoo=bar --help | verbose=true, args=[myscript.java, -Dfoo=bar, --help] |
run myscript.java --verbose | verbose=false, args=[myscript.java, --verbose] |
run --help | Displays 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 consoleprint(String)- Output text without newlinestop()- Stop the consolegetShell()- Access the shellgetHelpInfo(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 versionHow It Works
- Subcommands with the same
helpGroupvalue are grouped under that heading - Named groups appear first, in the order their first subcommand was defined
- Subcommands without a
helpGroupappear under a default heading - 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 pluginMerging 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
- Zero startup cost – The provider class is stored as a reference and only instantiated when help is rendered
- Works with both
@CommandDefinitionand@GroupCommandDefinition - HelpEntry is a simple value class with
name()anddescription()(description is optional) - Return an empty map (not null) if there are no additional sections to show