Script Commands
When you think of powerful CLI tools - git, docker, kubectl - they all share a common pattern: they're organized
around commands. You don't just run git with flags; you run git commit, git push, git branch - each a distinct
operation with its own arguments.
Rad lets you build tools like this through first-class command support. You can define multiple commands in a single script, each with their own arguments and implementation.
Basic Syntax¶
Let's start with a very simple example:
1 2 3 4 5 6 7 8 | |
This script defines a single command called greet that takes a name argument.
Invoke it by specifying the command name followed by its arguments:
> ./script.rad greet Alice
Hello, Alice!
Let's break down the syntax:
command greet:- Defines a command namedgreetname str- The command takes one required string argument callednamecalls greet_user- Specifies which function to execute when this command runsfn greet_user():- Defines the function that implements the command logic (defined after commands)
Command arguments (like name) become script-wide variables, accessible throughout your script.
Multiple Commands¶
The power of commands emerges when you define several in one script. Let's create a simple deployment tool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Now you can invoke either command:
> ./tool.rad deploy staging
Deploying to staging...
Deployment complete!
> ./tool.rad status production
Checking status of production...
Environment production is healthy
Each command has its own arguments and implementation, but they live in the same script and can share code.
Adding Descriptions¶
Commands should include descriptions to make your tool self-documenting. Use the familiar --- ... --- header syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
These descriptions appear in the help output:
> ./tool.rad -h
Usage:
tool.rad [command] [OPTIONS]
Commands:
deploy Deploy the application to an environment
status Check the health of an environment
Notice how Rad automatically generates a usage string listing all available commands.
Multi-line descriptions
Just like script headers, command descriptions can span multiple lines:
command deploy:
---
Deploy the application to an environment.
This will build, test, and deploy your application.
---
Important: The first line appears in the script's overall help output, so keep it concise. Additional lines
only appear when you request help for that specific command (./tool.rad deploy -h).
Command Arguments¶
Each command can define its own arguments using the same syntax you learned in Args. Let's expand our deployment tool:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
The arguments work exactly as they do in the args: block - you can use defaults, optional types, constraints, and
comments for help text.
Invoke with positional arguments:
> ./tool.rad deploy staging feature-branch
Running tests...
Deploying feature-branch to staging...
✅ Deployment complete!
Or use flags (especially for booleans):
> ./tool.rad deploy --env=production --skip-tests
⚠️ Skipping tests
Deploying main to production...
✅ Deployment complete!
Shared Args¶
Often you want arguments that apply to all commands - like a --verbose flag or a --config path. Define these in
an args: block to share them across commands:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
Shared args are available to all commands:
> ./tool.rad deploy staging --verbose
Config: ~/.config/tool.yaml
Deploying to staging...
✅ Deployed!
> ./tool.rad status production --verbose
Config: ~/.config/tool.yaml
Checking production...
Environment healthy
Shared args are flag-only
When commands exist, shared args can only be passed as flags (like --verbose, -v, or --config=value), not
positionally. This keeps the invocation clear: the first positional argument is always the command name.
Both long form (--verbose) and short form (-v) work for shared args.
Command-specific args can be positional or flags, just like regular script args.
Command Callbacks¶
We've been using function references (calls on_deploy), which is the recommended approach for most commands.
However, for very short implementations, you can also use inline lambdas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Shared Logic¶
You can write code after all command blocks that runs before any callback is invoked. This is useful for setup logic that all commands need:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
When you run ./script.rad deploy staging, the flow is:
- Parse arguments
- Run shared logic (lines 12-14)
- Run the callback (
on_deploy)
This pattern is useful for loading configuration files, setting up connections, or validating preconditions that apply to all commands.
Getting Help¶
Rad automatically generates help documentation for your commands. There are two levels of help:
Script-level help shows all available commands:
> ./tool.rad -h
Usage:
tool.rad [command] [OPTIONS]
Commands:
deploy Deploy the application
rollback Rollback a deployment
status Check environment status
Command-level help shows arguments for a specific command:
> ./tool.rad deploy -h
Deploy the application
Usage:
deploy <env> [branch] [OPTIONS]
Command args:
--env str Environment to deploy to
--branch str Branch to deploy from (default "main")
--skip-tests Skip running tests before deploy
-v, --verbose Enable verbose output
--config str (default "~/.config/tool.yaml")
Notice how the help includes:
- The command description
- Required and optional arguments
- Default values
- Shared args (like
--verboseand--config) - Help text from
#comments
Practical Example¶
Here's a concise, realistic example that demonstrates the "dev script" pattern - a common use case for replacing messy Makefiles or complex package.json script sections with a single, readable CLI entry point.
Dev Script¶
Instead of remembering different commands for building, testing, and running your project, you can wrap them in a
single dev.rad script. This demonstrates shared arguments, boolean flags, and how to pass arguments down to underlying tools:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | |
Usage:
> ./dev.rad start
🚀 Starting server on http://localhost:3000...
> ./dev.rad start --port 8080 --detach
🚀 Starting server on http://localhost:8080...
Server started in background
> ./dev.rad test --grep "login_flow" --watch
🧪 Running tests...
> ./dev.rad build
📦 Building for production...
✅ Build complete in ./dist
Notice how this example uses:
- Shared args (
--verbose) available to all commands - Command-specific arguments with defaults (
port,detach,grep,watch) - Shared logic that runs before any callback
- Function references for callbacks (
calls on_start, etc.) - Integration with shell commands to wrap existing tools
- Clear, self-documenting help text
Summary¶
- Script commands partition scripts into operations using
command name:blocks - Each command has:
- Its own arguments (using standard
argssyntax) - A description block (
--- ... ---) - A callback implementation (function reference or inline lambda)
- Its own arguments (using standard
- Shared args (from
args:block) are available to all commands- Must be passed as flags when commands exist
- Shared logic runs before any callback. Write code after command blocks for setup that all commands need.
- Help is automatic:
./script -hlists available commands./script command -hshows command-specific help
- Callbacks:
- Function references:
calls function_name(recommended) - Inline lambdas:
calls fn():(for short implementations)
- Function references:
- Use script commands to build CLI tools, not just scripts
Next¶
Rad provides a powerful system for looking up values from predefined resource files, which is particularly useful for building interactive tools.
We'll explore this in the next section: Resources.