Nushell CLI Subcommand Pattern
This skill covers the pattern for organizing Nushell commands into hierarchical CLI tools using def main and quoted subcommand names.
The Core Pattern
Basic Subcommand Structure
# Entry point
def main [] {
help main
}
# Subcommands use quoted names with spaces
def "main create" [name: string] {
mkdir $name
print $"Created ($name)"
}
def "main delete" [name: string] {
rm -r $name
print $"Deleted ($name)"
}
def "main list" [] {
ls | select name type size
}
How it works:
- •
def main []is the entry point - •Spaces in quoted names create subcommands:
"main create" - •More spaces = deeper nesting:
"main sub subsub"
Usage as a module:
nu tool.nu # Calls main, shows help nu tool.nu create foo # Calls "main create" nu tool.nu list # Calls "main list"
Using Custom Names
You can use any name instead of "main":
# Entry point with custom name
def mytool [] {
help mytool
}
def "mytool create" [name: string] {
mkdir $name
}
def "mytool list" [] {
ls
}
Usage:
nu mytool.nu # Calls mytool nu mytool.nu create foo # Calls "mytool create"
The name you use in def name [] becomes your command prefix. "main" is conventional but not required.
Executable Scripts with Shebang
For standalone executable tools, add a shebang:
#!/usr/bin/env nu
# Entry point - executed when script runs
def main [] {
help main
}
# Subcommands
def "main create" [name: string] {
mkdir $name
}
def "main delete" [name: string] {
rm -r $name
}
def "main list" [] {
ls
}
Usage as executable:
chmod +x mytool.nu ./mytool.nu # Executes main directly ./mytool.nu create foo # Calls "main create" ./mytool.nu list # Calls "main list"
Key differences with shebang:
- •Script is directly executable
- •
def main []is called when script runs without arguments - •Still need "main" prefix for subcommands
- •Can be installed to PATH and used like any CLI tool
Multi-Level Hierarchies
Add more spaces for deeper nesting:
def main [] {
help main
}
# Level 1 - namespace stub
def "main repo" [] {
help main repo
}
# Level 2 - actual commands
def "main repo create" [name: string] {
git init $name
}
def "main repo clone" [url: string] {
git clone $url
}
# Level 1 - another namespace
def "main config" [] {
help main config
}
# Level 2 - actual commands
def "main config get" [key: string] {
# Implementation
}
def "main config set" [key: string, value: string] {
# Implementation
}
Creates hierarchy:
main
├── repo
│ ├── create
│ └── clone
└── config
├── get
└── set
Usage:
nu tool.nu # Shows main help nu tool.nu repo # Shows repo help nu tool.nu repo create myrepo # Runs command nu tool.nu config get key # Runs command
Directory-Based Modules
For larger projects, split commands across multiple files using directory modules.
Directory Structure
mytool/ ├── mod.nu # Entry point (required for directory modules) ├── database.nu # Database subcommands ├── server.nu # Server subcommands └── utils.nu # Utility subcommands
mod.nu - The Entry Point
Critical: mod.nu is required for directory modules. When you use mytool, Nushell automatically loads mytool/mod.nu.
# Entry point
export def main [] {
help main
}
# Re-export submodules
export use ./database.nu *
export use ./server.nu *
export use ./utils.nu *
What this does:
- •
export def main []- entry point when module is called - •
export use ./file.nu *- makes all commands from that file available - •Without
mod.nu, the directory won't be recognized as a module
Submodule Files
Each file defines commands with the FULL hierarchy:
database.nu:
# Namespace stub
export def "main db" [] {
help main db
}
# Full path from root in every command
export def "main db connect" [host: string] {
# Implementation
}
export def "main db migrate" [] {
# Implementation
}
export def "main db backup" [path: string] {
# Implementation
}
server.nu:
# Namespace stub
export def "main server" [] {
help main server
}
# Full path from root
export def "main server start" [--port: int = 8080] {
# Implementation
}
export def "main server stop" [] {
# Implementation
}
Key points:
- •Use
export defso mod.nu can re-export them - •Use FULL path from root:
"main db connect"not just"connect" - •Commands in same file share the same prefix
How It Works Together
- •User runs:
use mytool * - •Nushell loads
mytool/mod.nu - •
mod.nudoesexport use ./database.nu * - •Commands like
"main db connect"become available - •User can call:
main db connect localhost
Benefits:
- •Organization: Related commands in same file
- •Maintainability: Easy to find and edit
- •Modularity: Can test individual files
- •Scalability: Add new files without changing structure
Namespace Stubs
Commands without parameters can show help for that level:
def "main repo" [] {
help main repo
}
This creates a "namespace" - calling it shows help instead of running logic.
When to use:
- •Multi-level hierarchies
- •Grouping related commands
- •Providing help at each level
Best Practices
1. Always Provide Main Entry
# ✅ Correct
def main [] {
help main
}
# ❌ Wrong - no entry point
# (missing entirely)
2. Use Consistent Prefixes
All commands in a file should share the same prefix:
# ✅ Correct - consistent
def "main db connect" [] { }
def "main db migrate" [] { }
def "main db backup" [] { }
# ❌ Wrong - inconsistent
def "main db connect" [] { }
def "main migrate" [] { } # Missing "db"
def "database backup" [] { } # Wrong prefix
3. Use Full Paths in Submodules
When splitting across files, always use full path from root:
# ✅ Correct - full path
export def "main server start" [] { }
# ❌ Wrong - partial path
export def "server start" [] { }
4. Export in Submodules
# ✅ Correct - export so mod.nu can re-export
export def "main cmd" [] { }
# ❌ Wrong - not exported, won't be available
def "main cmd" [] { }
5. Use Relative Paths in mod.nu
# ✅ Correct - relative path export use ./database.nu * # ❌ Wrong - absolute path export use ~/mytool/database.nu *
Complete Example
Directory structure:
calculator/ ├── mod.nu ├── basic.nu └── scientific.nu
mod.nu:
export def main [] {
help main
}
export use ./basic.nu *
export use ./scientific.nu *
basic.nu:
export def "main basic" [] {
help main basic
}
export def "main basic add" [a: int, b: int] {
$a + $b
}
export def "main basic subtract" [a: int, b: int] {
$a - $b
}
scientific.nu:
export def "main sci" [] {
help main sci
}
export def "main sci sqrt" [x: float] {
$x ** 0.5
}
export def "main sci pow" [base: float, exp: float] {
$base ** $exp
}
Usage:
use calculator * main # Shows help main basic # Shows basic help main basic add 5 3 # Returns 8 main sci sqrt 16 # Returns 4
Pattern Summary
Single file:
- •
def main []- entry point - •
def "main subcommand"- subcommands with quoted names - •Spaces create hierarchy
Multi-file (directory module):
- •Create directory with
mod.nu - •
mod.nuhasexport def main []andexport use ./file.nu * - •Each file uses
export def "full path command" - •All commands use full path from root
Checklist
- • Entry point:
def main []calls help - • Subcommands: Use quoted names with spaces
- • Nesting: More spaces = deeper levels
- • Directory module: Create
mod.nuas entry point - • Re-exports: Use
export use ./file.nu *in mod.nu - • Full paths: Commands in submodules use complete hierarchy
- • Exports: Use
export defin submodule files - • Consistency: Same prefix for related commands
- • Namespace stubs: Commands that just show help
Working Example
A complete working example is available in examples/calculator/ demonstrating all the patterns covered in this skill. See examples/README.md for usage instructions and testing.
Related Skills
- •nushell-usage - Core Nushell patterns and syntax
- •nushell-testing - Testing CLI commands
- •nushell-structured-data - Record patterns for CLI outputs