CommandInvocation API
The CommandInvocation interface is the primary way commands interact with the shell environment. It provides methods for output, input, shell control, and access to command metadata.
Overview
When a command is executed, Æsh passes a CommandInvocation object to the execute() method. This object serves as the bridge between your command logic and the shell environment.
@CommandDefinition(name = "example", description = "Example command")
public class ExampleCommand implements Command<CommandInvocation> {
@Override
public CommandResult execute(CommandInvocation invocation) {
// Use invocation to interact with the shell
invocation.println("Hello from the command!");
return CommandResult.SUCCESS;
}
}Interface Definition
public interface CommandInvocation {
// Output methods
void print(String message);
void println(String message);
// Shell access
Shell getShell();
// Control methods
void stop();
// Help system
String getHelpInfo();
String getHelpInfo(String commandName);
// Configuration access
CommandInvocationConfiguration getConfiguration();
// Operator support
Operator getOperator();
// Piped input
String getInputLine() throws InterruptedException;
}Output Methods
print(String message)
Outputs text to the console without a trailing newline.
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.print("Processing");
invocation.print("...");
invocation.print(" done!\n");
return CommandResult.SUCCESS;
}Output: Processing... done!
println(String message)
Outputs text to the console with a trailing newline.
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Line 1");
invocation.println("Line 2");
invocation.println("Line 3");
return CommandResult.SUCCESS;
}Output:
Line 1
Line 2
Line 3Formatted Output
Combine with String.format() for formatted output:
@Override
public CommandResult execute(CommandInvocation invocation) {
String name = "Alice";
int count = 42;
invocation.println(String.format("User: %s, Items: %d", name, count));
return CommandResult.SUCCESS;
}Shell Access
getShell()
Returns the Shell object for advanced terminal interaction.
Shell shell = invocation.getShell();The Shell interface provides:
| Method | Description |
|---|---|
readLine() | Read a line of input from the user |
readLine(String prompt) | Read input with a custom prompt |
readLine(Prompt prompt) | Read input with a Prompt object (supports masking) |
write(String text) | Write text directly to the terminal |
writeln(String text) | Write text with newline |
clear() | Clear the terminal screen |
getSize() | Get terminal dimensions (rows, columns) |
enableAlternateBuffer() | Switch to alternate screen buffer |
enableMainBuffer() | Switch back to main screen buffer |
Reading User Input
@Override
public CommandResult execute(CommandInvocation invocation) throws InterruptedException {
Shell shell = invocation.getShell();
// Simple input
String name = shell.readLine("Enter your name: ");
invocation.println("Hello, " + name + "!");
return CommandResult.SUCCESS;
}Reading Password (Masked Input)
@Override
public CommandResult execute(CommandInvocation invocation) throws InterruptedException {
Shell shell = invocation.getShell();
String username = shell.readLine("Username: ");
String password = shell.readLine(new Prompt("Password: ", '*'));
// Authenticate user...
invocation.println("Authenticating " + username + "...");
return CommandResult.SUCCESS;
}Getting Terminal Size
@Override
public CommandResult execute(CommandInvocation invocation) {
Shell shell = invocation.getShell();
Size size = shell.getSize();
invocation.println("Terminal: " + size.getWidth() + "x" + size.getHeight());
return CommandResult.SUCCESS;
}Clearing the Screen
@Override
public CommandResult execute(CommandInvocation invocation) {
Shell shell = invocation.getShell();
shell.clear();
invocation.println("Screen cleared!");
return CommandResult.SUCCESS;
}Control Methods
stop()
Stops the console/shell. This is typically used when a command needs to terminate the entire application.
@CommandDefinition(name = "exit", description = "Exit the shell")
public class ExitCommand implements Command<CommandInvocation> {
@Override
public CommandResult execute(CommandInvocation invocation) {
invocation.println("Goodbye!");
invocation.stop();
return CommandResult.SUCCESS;
}
}Note: For most applications, using the built-in exit command via .addExitCommand() is recommended.
Help System
getHelpInfo()
Returns the help text for the current command.
@Override
public CommandResult execute(CommandInvocation invocation) {
if (showHelp) {
invocation.println(invocation.getHelpInfo());
return CommandResult.SUCCESS;
}
// Normal execution...
return CommandResult.SUCCESS;
}getHelpInfo(String commandName)
Returns help text for a specific command.
@Override
public CommandResult execute(CommandInvocation invocation) {
// Display help for another command
String copyHelp = invocation.getHelpInfo("copy");
invocation.println(copyHelp);
return CommandResult.SUCCESS;
}Configuration Access
getConfiguration()
Returns the CommandInvocationConfiguration for accessing runtime settings.
@Override
public CommandResult execute(CommandInvocation invocation) {
CommandInvocationConfiguration config = invocation.getConfiguration();
// Access configuration values
// ...
return CommandResult.SUCCESS;
}Operator Support
getOperator()
Returns the current command-line operator, useful for piping and chaining commands.
@Override
public CommandResult execute(CommandInvocation invocation) {
Operator operator = invocation.getOperator();
switch (operator) {
case PIPE:
// Output is being piped to another command
break;
case REDIRECT_OUT:
// Output is being redirected to a file
break;
case NONE:
// Normal execution
break;
}
return CommandResult.SUCCESS;
}Operator values:
| Operator | Symbol | Description |
|---|---|---|
NONE | - | No operator, normal execution |
PIPE | | | Output piped to next command |
REDIRECT_OUT | > | Output redirected to file |
REDIRECT_OUT_APPEND | >> | Output appended to file |
REDIRECT_IN | < | Input from file |
AND | && | Execute next if success |
OR | || | Execute next if failure |
END | ; | Execute next unconditionally |
Piped Input
getInputLine()
Reads a line of input when the command is receiving piped data.
@CommandDefinition(name = "uppercase", description = "Convert input to uppercase")
public class UppercaseCommand implements Command<CommandInvocation> {
@Override
public CommandResult execute(CommandInvocation invocation) throws InterruptedException {
String line;
while ((line = invocation.getInputLine()) != null) {
invocation.println(line.toUpperCase());
}
return CommandResult.SUCCESS;
}
}Usage: echo "hello world" | uppercase
Output: HELLO WORLD
Complete Example
@CommandDefinition(
name = "wizard",
description = "Interactive setup wizard",
generateHelp = true
)
public class WizardCommand implements Command<CommandInvocation> {
@Option(shortName = 's', hasValue = false, description = "Skip confirmation")
private boolean skipConfirmation;
@Override
public CommandResult execute(CommandInvocation invocation) throws InterruptedException {
Shell shell = invocation.getShell();
// Clear screen and show header
shell.clear();
invocation.println("=== Setup Wizard ===\n");
// Collect user information
String name = shell.readLine("Enter your name: ");
String email = shell.readLine("Enter your email: ");
String password = shell.readLine(new Prompt("Enter password: ", '*'));
// Display terminal info
Size size = shell.getSize();
invocation.println("\nTerminal size: " + size.getWidth() + "x" + size.getHeight());
// Confirm
if (!skipConfirmation) {
invocation.println("\nConfiguration summary:");
invocation.println(String.format(" Name: %s", name));
invocation.println(String.format(" Email: %s", email));
String confirm = shell.readLine("\nSave configuration? (yes/no): ");
if (!confirm.equalsIgnoreCase("yes")) {
invocation.println("Setup cancelled.");
return CommandResult.FAILURE;
}
}
// Save configuration...
invocation.println("\nConfiguration saved successfully!");
return CommandResult.SUCCESS;
}
}Custom CommandInvocation
For advanced use cases like dependency injection, you can create a custom CommandInvocation subtype that provides access to application services.
Step 1: Define the Custom Interface
public interface MyCommandInvocation extends CommandInvocation {
// Add custom methods for your application services
DatabaseConnection getDatabase();
UserSession getCurrentUser();
ConfigurationService getConfig();
}Step 2: Implement the Custom CommandInvocation
import org.aesh.command.invocation.CommandInvocation;
import org.aesh.command.invocation.CommandInvocationConfiguration;
import org.aesh.command.shell.Shell;
import org.aesh.command.Operator;
public class MyCommandInvocationImpl implements MyCommandInvocation {
private final CommandInvocation delegate;
private final DatabaseConnection database;
private final UserSession userSession;
private final ConfigurationService config;
public MyCommandInvocationImpl(
CommandInvocation delegate,
DatabaseConnection database,
UserSession userSession,
ConfigurationService config) {
this.delegate = delegate;
this.database = database;
this.userSession = userSession;
this.config = config;
}
// Delegate standard CommandInvocation methods
@Override
public void print(String msg) {
delegate.print(msg);
}
@Override
public void println(String msg) {
delegate.println(msg);
}
@Override
public Shell getShell() {
return delegate.getShell();
}
@Override
public void stop() {
delegate.stop();
}
@Override
public String getHelpInfo() {
return delegate.getHelpInfo();
}
@Override
public String getHelpInfo(String commandName) {
return delegate.getHelpInfo(commandName);
}
@Override
public CommandInvocationConfiguration getConfiguration() {
return delegate.getConfiguration();
}
@Override
public Operator getOperator() {
return delegate.getOperator();
}
@Override
public String getInputLine() throws InterruptedException {
return delegate.getInputLine();
}
// Custom methods for application services
@Override
public DatabaseConnection getDatabase() {
return database;
}
@Override
public UserSession getCurrentUser() {
return userSession;
}
@Override
public ConfigurationService getConfig() {
return config;
}
}Step 3: Create the CommandInvocationProvider
import org.aesh.command.invocation.CommandInvocationProvider;
public class MyCommandInvocationProvider
implements CommandInvocationProvider<MyCommandInvocation> {
private final DatabaseConnection database;
private final UserSession userSession;
private final ConfigurationService config;
public MyCommandInvocationProvider(
DatabaseConnection database,
UserSession userSession,
ConfigurationService config) {
this.database = database;
this.userSession = userSession;
this.config = config;
}
@Override
public MyCommandInvocation enhanceCommandInvocation(
CommandInvocation commandInvocation) {
return new MyCommandInvocationImpl(
commandInvocation,
database,
userSession,
config
);
}
}Step 4: Create Commands Using the Custom Invocation
@CommandDefinition(name = "query", description = "Query database")
public class QueryCommand implements Command<MyCommandInvocation> {
@Argument(description = "SQL query to execute")
private String query;
@Override
public CommandResult execute(MyCommandInvocation invocation) {
// Access injected dependencies
DatabaseConnection db = invocation.getDatabase();
UserSession user = invocation.getCurrentUser();
// Check permissions
if (!user.hasPermission("query.execute")) {
invocation.println("Permission denied: You cannot execute queries.");
return CommandResult.FAILURE;
}
// Execute query
try {
invocation.println("Executing query as user: " + user.getUsername());
ResultSet results = db.executeQuery(query);
printResults(results, invocation);
return CommandResult.SUCCESS;
} catch (SQLException e) {
invocation.println("Query error: " + e.getMessage());
return CommandResult.FAILURE;
}
}
private void printResults(ResultSet results, MyCommandInvocation invocation) {
// Print query results...
}
}
@CommandDefinition(name = "whoami", description = "Show current user")
public class WhoamiCommand implements Command<MyCommandInvocation> {
@Override
public CommandResult execute(MyCommandInvocation invocation) {
UserSession user = invocation.getCurrentUser();
invocation.println("Logged in as: " + user.getUsername());
invocation.println("Roles: " + String.join(", ", user.getRoles()));
return CommandResult.SUCCESS;
}
}Step 5: Configure and Start Æsh
import org.aesh.command.CommandRuntime;
import org.aesh.command.impl.registry.AeshCommandRegistryBuilder;
import org.aesh.command.registry.CommandRegistry;
import org.aesh.command.settings.Settings;
import org.aesh.command.settings.SettingsBuilder;
import org.aesh.readline.Readline;
import org.aesh.readline.ReadlineConsole;
public class MyApplication {
public static void main(String[] args) throws Exception {
// Initialize your application services
DatabaseConnection database = new DatabaseConnection("jdbc:postgresql://localhost/mydb");
UserSession userSession = new UserSession("admin", Arrays.asList("admin", "user"));
ConfigurationService config = new ConfigurationService();
// Create the custom invocation provider
MyCommandInvocationProvider invocationProvider = new MyCommandInvocationProvider(
database,
userSession,
config
);
// Build the command registry with your commands
CommandRegistry<MyCommandInvocation> registry =
AeshCommandRegistryBuilder.<MyCommandInvocation>builder()
.command(QueryCommand.class)
.command(WhoamiCommand.class)
.create();
// Configure settings with the custom invocation provider
Settings<MyCommandInvocation, CommandInvocation,
CommandInputProcessor, CommandProcessorContext, CommandOutput> settings =
SettingsBuilder.<MyCommandInvocation>builder()
.commandRegistry(registry)
.commandInvocationProvider(invocationProvider)
.enableHistory(true)
.persistHistory(true)
.historyFile(new File(System.getProperty("user.home"), ".myapp_history"))
.build();
// Create and start the console
ReadlineConsole console = new ReadlineConsole(settings);
console.setPrompt("[myapp]$ ");
console.start();
}
}Alternative: Using AeshConsoleRunner (Simplified)
For simpler setups, you can also configure the provider through SettingsBuilder:
public class MyApplication {
public static void main(String[] args) {
// Initialize services
DatabaseConnection database = new DatabaseConnection("jdbc:postgresql://localhost/mydb");
UserSession userSession = new UserSession("admin", Arrays.asList("admin", "user"));
ConfigurationService config = new ConfigurationService();
// Create provider
MyCommandInvocationProvider provider = new MyCommandInvocationProvider(
database, userSession, config
);
// Build settings with the provider
Settings settings = SettingsBuilder.builder()
.commandInvocationProvider(provider)
.build();
// Start the console
AeshConsoleRunner.builder()
.settings(settings)
.command(QueryCommand.class)
.command(WhoamiCommand.class)
.addExitCommand()
.prompt("[myapp]$ ")
.start();
}
}Example Session
[myapp]$ whoami
Logged in as: admin
Roles: admin, user
[myapp]$ query SELECT * FROM users LIMIT 5
Executing query as user: admin
+----+----------+-------------------+
| id | username | email |
+----+----------+-------------------+
| 1 | alice | alice@example.com |
| 2 | bob | bob@example.com |
+----+----------+-------------------+
[myapp]$ exit
Goodbye!See Advanced Topics - Custom Command Invocation for more patterns including Spring integration.
Thread Safety
The CommandInvocation object is not thread-safe. It should only be used within the execute() method on the thread that called it. Do not share the invocation object between threads.
Best Practices
Always use invocation for output - Use
invocation.println()instead ofSystem.out.println()to ensure output goes to the correct terminal.Handle InterruptedException - Methods like
readLine()andgetInputLine()throwInterruptedException. Always declare it in your method signature.Check for null input -
getInputLine()returnsnullwhen there’s no more piped input.Use Prompt for sensitive input - Always use
Promptwith a mask character for passwords and other sensitive data.Clear sensitive data - Zero out password character arrays after use.
char[] password = shell.readLine(new Prompt("Password: ", '*')).toCharArray();
// Use password...
Arrays.fill(password, '\0'); // Clear sensitive data