Ursula
A slim, ergonomic framework to make Scala CLI apps with zero external dependencies.
Quick Start (Modern Approach)
After a bit of a refactor, Ursula now offers an ergonomic, type-safe API for building CLI apps. Here's a complete example:
import dev.alteration.branch.ursula.UrsulaApp
import dev.alteration.branch.ursula.args.Flags
import dev.alteration.branch.ursula.command.{Command, CommandContext}
object GreetApp extends UrsulaApp {
val commands = Seq(GreetCommand)
}
object GreetCommand extends Command {
// Concise flag definitions using Flags factory
val NameFlag = Flags.string(
name = "name",
shortKey = "n",
description = "Name to greet",
required = true
)
val LoudFlag = Flags.boolean(
name = "loud",
shortKey = "l",
description = "Greet loudly (uppercase)"
)
val trigger = "greet"
val description = "Greets someone"
val usage = "greet --name <name> [--loud]"
val examples = Seq(
"greet --name World",
"greet -n Alice --loud"
)
val flags = Seq(NameFlag, LoudFlag)
val arguments = Seq.empty
// Type-safe action using CommandContext
override def actionWithContext(ctx: CommandContext): Unit = {
val name = ctx.requiredFlag(NameFlag) // No .get needed!
val loud = ctx.booleanFlag(LoudFlag)
val greeting = if (loud) s"HELLO, ${name.toUpperCase}!" else s"Hello, $name!"
println(greeting)
}
}
Run it:
myapp greet --name World # Hello, World!
myapp greet -n Alice --loud # HELLO, ALICE!
myapp help # Show all commands
myapp greet --help # Show greet command help
Key Benefits
- ✅ Concise: Flag definitions using
Flagsfactory methods - ✅ Type-Safe: No unsafe
.getcalls withCommandContext - ✅ Clean:
ctx.requiredFlag(),ctx.booleanFlag(),ctx.flag() - ✅ Compatible: Old code continues to work
Documentation
- Commands and Flags - Building commands, using flags and arguments, CommandContext
- Migration Guide - Moving from traditional to modern approach
Anatomy of the Framework
Here is a general overview of how the pieces fit together.
How it works: UrsulaApp
You only need to make an object that extends the UrsulaApp trait, and provide a Seq[Command], which are your actions you wish to be available in your app. UrsulaApp has a final main method entrypoint, and does some processing automatically. It parses the arguments passed, and uses that to pull out the Command provided, and runs accordingly, passing on the arguments to it.
There are some built-in commands provided, currently only the HelpCommand, that are also automatically injected. This means that even if you only have:
object App extends UrsulaApp {
override val commands: Seq[Command] = Seq.empty
}
you already have a functioning cli app that has a help command that prints all the available commands accepted (as little as they are so far).
At this point, you need only implement some Commands that wrap the functionality you desire, and add them to the commands: Seq.
Running Your CLI
myapp greet --name World
myapp greet -n Alice
myapp help # Show all commands
myapp greet --help # Show greet command help
More Examples
Check out the examples directory for:
- BasicCliExample - Traditional approach (for comparison)
- ModernCliExample - Ergonomic approach with multiple commands
- FileToolsExample - Practical file processing tool with custom types
Each example is fully documented and runnable with sbt.
Next Steps
- Learn about Commands and Flags
- Read the Migration Guide if updating existing code