Obsidian includes a built-in CLI to run custom commands from the terminal. Any class annotated with @Command and implementing Runnable is automatically discovered at startup — no registration needed.
@Command(name = "greet", description = "Greet a user", aliases = {"g"})
public class GreetCommand implements Runnable {
@Param(index = 0, name = "name", description = "Name to greet")
private String name;
@Option(name = "--upper", description = "Print in uppercase", flag = true)
private boolean upper;
@Override
public void run() {
String message = "Hello, " + name + "!";
Printer.ok(upper ? message.toUpperCase() : message);
}
}
obsidian greet Alice # Run a command
obsidian greet Alice --upper # With an option
obsidian --help # List all commands
obsidian greet --help # Help for a specific command
obsidian --version # Framework version
@Command(name = "migrate", description = "Run database migrations")
public class MigrateCommand implements Runnable {
@Param(index = 0, name = "target", description = "Target migration", required = false)
private String target;
// Captures all remaining positional args
@Param(index = 1, name = "files", description = "Migration files", variadic = true)
private String[] files;
// Option with a default value
@Option(name = "--env", description = "Target environment", defaultValue = "development")
private String env;
// Boolean flag — no value expected
@Option(name = "--dry-run", description = "Simulate without applying", flag = true)
private boolean dryRun;
// Required option — fails if missing
@Option(name = "--db", description = "Database name", required = true)
private String db;
@Override
public void run() { ... }
}
Printer.ok("Migration completed"); // green [OK]
Printer.error("Connection failed"); // red [ERROR]
Printer.warning("Deprecated usage"); // yellow [WARNING]
Printer.info("Starting server..."); // blue [INFO]
Printer.note("Config file loaded"); // cyan [NOTE]
Printer.caution("This will drop data"); // red [CAUTION]
Printer.print("Plain text output");
Printer.print(); // blank line
String name = CliComponents.Prompt.text("What is your name?", "Guest");
boolean confirmed = CliComponents.Prompt.confirm("Are you sure?", false);
String env = CliComponents.Prompt.select(
"Select environment",
"development", "staging", "production"
);
// Progress bar
CliComponents.ProgressBar bar = new CliComponents.ProgressBar(100, "Importing");
for (int i = 0; i <= 100; i++) {
bar.update(i);
Thread.sleep(20);
}
bar.finish();
// Spinner
CliComponents.Spinner spinner = new CliComponents.Spinner("Connecting to database...");
spinner.start();
// ... do work ...
spinner.stop("Connected!");
new CliComponents.Table("ID", "Name", "Status")
.addRow("1", "Alice", "active")
.addRow("2", "Bob", "inactive")
.addRow("3", "Carol", "active")
.print();
aliases in @Command to provide shorthand names