# Command Handler Developer Guide **Target Audience**: Developers working on the provisioning CLI **Last Updated**: 2025-09-30 **Related**: [ADR-006 CLI Refactoring](../architecture/adr/adr-006-provisioning-cli-refactoring.md) ## Overview The provisioning CLI uses a **modular, domain-driven architecture** that separates concerns into focused command handlers. This guide shows you how to work with this architecture. ### Key Architecture Principles 1. **Separation of Concerns**: Routing, flag parsing, and business logic are separated 2. **Domain-Driven Design**: Commands organized by domain (infrastructure, orchestration, etc.) 3. **DRY (Don't Repeat Yourself)**: Centralized flag handling eliminates code duplication 4. **Single Responsibility**: Each module has one clear purpose 5. **Open/Closed Principle**: Easy to extend, no need to modify core routing ### Architecture Components ```plaintext provisioning/core/nulib/ ├── provisioning (211 lines) - Main entry point ├── main_provisioning/ │ ├── flags.nu (139 lines) - Centralized flag handling │ ├── dispatcher.nu (264 lines) - Command routing │ ├── help_system.nu - Categorized help system │ └── commands/ - Domain-focused handlers │ ├── infrastructure.nu (117 lines) - Server, taskserv, cluster, infra │ ├── orchestration.nu (64 lines) - Workflow, batch, orchestrator │ ├── development.nu (72 lines) - Module, layer, version, pack │ ├── workspace.nu (56 lines) - Workspace, template │ ├── generation.nu (78 lines) - Generate commands │ ├── utilities.nu (157 lines) - SSH, SOPS, cache, providers │ └── configuration.nu (316 lines) - Env, show, init, validate ``` ## Adding New Commands ### Step 1: Choose the Right Domain Handler Commands are organized by domain. Choose the appropriate handler: | Domain | Handler | Responsibility | | -------- | --------- | ---------------- | | infrastructure | `infrastructure.nu` | Server/taskserv/cluster/infra lifecycle | | orchestration | `orchestration.nu` | Workflow/batch operations, orchestrator control | | development | `development.nu` | Module discovery, layers, versions, packaging | | workspace | `workspace.nu` | Workspace and template management | | configuration | `configuration.nu` | Environment, settings, initialization | | utilities | `utilities.nu` | SSH, SOPS, cache, providers, utilities | | generation | `generation.nu` | Generate commands (server, taskserv, etc.) | ### Step 2: Add Command to Handler **Example: Adding a new server command `server status`** Edit `provisioning/core/nulib/main_provisioning/commands/infrastructure.nu`: ```nushell # Add to the handle_infrastructure_command match statement export def handle_infrastructure_command [ command: string ops: string flags: record ] { set_debug_env $flags match $command { "server" => { handle_server $ops $flags } "taskserv" | "task" => { handle_taskserv $ops $flags } "cluster" => { handle_cluster $ops $flags } "infra" | "infras" => { handle_infra $ops $flags } _ => { print $"❌ Unknown infrastructure command: ($command)" print "" print "Available infrastructure commands:" print " server - Server operations (create, delete, list, ssh, status)" # Updated print " taskserv - Task service management" print " cluster - Cluster operations" print " infra - Infrastructure management" print "" print "Use 'provisioning help infrastructure' for more details" exit 1 } } } # Add the new command handler def handle_server [ops: string, flags: record] { let args = build_module_args $flags $ops run_module $args "server" --exec } ``` **That's it!** The command is now available as `provisioning server status`. ### Step 3: Add Shortcuts (Optional) If you want shortcuts like `provisioning s status`: Edit `provisioning/core/nulib/main_provisioning/dispatcher.nu`: ```nushell export def get_command_registry []: nothing -> record { { # Infrastructure commands "s" => "infrastructure server" # Already exists "server" => "infrastructure server" # Already exists # Your new shortcut (if needed) # Example: "srv-status" => "infrastructure server status" # ... rest of registry } } ``` **Note**: Most shortcuts are already configured. You only need to add new shortcuts if you're creating completely new command categories. ## Modifying Existing Handlers ### Example: Enhancing the `taskserv` Command Let's say you want to add better error handling to the taskserv command: **Before:** ```nushell def handle_taskserv [ops: string, flags: record] { let args = build_module_args $flags $ops run_module $args "taskserv" --exec } ``` **After:** ```nushell def handle_taskserv [ops: string, flags: record] { # Validate taskserv name if provided let first_arg = ($ops | split row " " | get -o 0) if ($first_arg | is-not-empty) and $first_arg not-in ["create", "delete", "list", "generate", "check-updates", "help"] { # Check if taskserv exists let available_taskservs = (^$env.PROVISIONING_NAME module discover taskservs | from json) if $first_arg not-in $available_taskservs { print $"❌ Unknown taskserv: ($first_arg)" print "" print "Available taskservs:" $available_taskservs | each { |ts| print $" • ($ts)" } exit 1 } } let args = build_module_args $flags $ops run_module $args "taskserv" --exec } ``` ## Working with Flags ### Using Centralized Flag Handling The `flags.nu` module provides centralized flag handling: ```nushell # Parse all flags into normalized record let parsed_flags = (parse_common_flags { version: $version, v: $v, info: $info, debug: $debug, check: $check, yes: $yes, wait: $wait, infra: $infra, # ... etc }) # Build argument string for module execution let args = build_module_args $parsed_flags $ops # Set environment variables based on flags set_debug_env $parsed_flags ``` ### Available Flag Parsing The `parse_common_flags` function normalizes these flags: | Flag Record Field | Description | | ------------------- | ------------- | | `show_version` | Version display (`--version`, `-v`) | | `show_info` | Info display (`--info`, `-i`) | | `show_about` | About display (`--about`, `-a`) | | `debug_mode` | Debug mode (`--debug`, `-x`) | | `check_mode` | Check mode (`--check`, `-c`) | | `auto_confirm` | Auto-confirm (`--yes`, `-y`) | | `wait` | Wait for completion (`--wait`, `-w`) | | `keep_storage` | Keep storage (`--keepstorage`) | | `infra` | Infrastructure name (`--infra`) | | `outfile` | Output file (`--outfile`) | | `output_format` | Output format (`--out`) | | `template` | Template name (`--template`) | | `select` | Selection (`--select`) | | `settings` | Settings file (`--settings`) | | `new_infra` | New infra name (`--new`) | ### Adding New Flags If you need to add a new flag: 1. **Update main `provisioning` file** to accept the flag 2. **Update `flags.nu:parse_common_flags`** to normalize it 3. **Update `flags.nu:build_module_args`** to pass it to modules **Example: Adding `--timeout` flag** ```nushell # 1. In provisioning main file (parameter list) def main [ # ... existing parameters --timeout: int = 300 # Timeout in seconds # ... rest of parameters ] { # ... existing code let parsed_flags = (parse_common_flags { # ... existing flags timeout: $timeout }) } # 2. In flags.nu:parse_common_flags export def parse_common_flags [flags: record]: nothing -> record { { # ... existing normalizations timeout: ($flags.timeout? | default 300) } } # 3. In flags.nu:build_module_args export def build_module_args [flags: record, extra: string = ""]: nothing -> string { # ... existing code let str_timeout = if ($flags.timeout != 300) { $"--timeout ($flags.timeout) " } else { "" } # ... rest of function $"($extra) ($use_check)($use_yes)($use_wait)($str_timeout)..." } ``` ## Adding New Shortcuts ### Shortcut Naming Conventions - **1-2 letters**: Ultra-short for common commands (`s` for server, `ws` for workspace) - **3-4 letters**: Abbreviations (`orch` for orchestrator, `tmpl` for template) - **Aliases**: Alternative names (`task` for taskserv, `flow` for workflow) ### Example: Adding a New Shortcut Edit `provisioning/core/nulib/main_provisioning/dispatcher.nu`: ```nushell export def get_command_registry []: nothing -> record { { # ... existing shortcuts # Add your new shortcut "db" => "infrastructure database" # New: db command "database" => "infrastructure database" # Full name # ... rest of registry } } ``` **Important**: After adding a shortcut, update the help system in `help_system.nu` to document it. ## Testing Your Changes ### Running the Test Suite ```bash # Run comprehensive test suite nu tests/test_provisioning_refactor.nu ``` ### Test Coverage The test suite validates: - ✅ Main help display - ✅ Category help (infrastructure, orchestration, development, workspace) - ✅ Bi-directional help routing - ✅ All command shortcuts - ✅ Category shortcut help - ✅ Command routing to correct handlers ### Adding Tests for Your Changes Edit `tests/test_provisioning_refactor.nu`: ```nushell # Add your test function export def test_my_new_feature [] { print "\n🧪 Testing my new feature..." let output = (run_provisioning "my-command" "test") assert_contains $output "Expected Output" "My command works" } # Add to main test runner export def main [] { # ... existing tests let results = [ # ... existing test calls (try { test_my_new_feature; "passed" } catch { "failed" }) ] # ... rest of main } ``` ### Manual Testing ```bash # Test command execution provisioning/core/cli/provisioning my-command test --check # Test with debug mode provisioning/core/cli/provisioning --debug my-command test # Test help provisioning/core/cli/provisioning my-command help provisioning/core/cli/provisioning help my-command # Bi-directional ``` ## Common Patterns ### Pattern 1: Simple Command Handler **Use Case**: Command just needs to execute a module with standard flags ```nushell def handle_simple_command [ops: string, flags: record] { let args = build_module_args $flags $ops run_module $args "module_name" --exec } ``` ### Pattern 2: Command with Validation **Use Case**: Need to validate input before execution ```nushell def handle_validated_command [ops: string, flags: record] { # Validate let first_arg = ($ops | split row " " | get -o 0) if ($first_arg | is-empty) { print "❌ Missing required argument" print "Usage: provisioning command " exit 1 } # Execute let args = build_module_args $flags $ops run_module $args "module_name" --exec } ``` ### Pattern 3: Command with Subcommands **Use Case**: Command has multiple subcommands (like `server create`, `server delete`) ```nushell def handle_complex_command [ops: string, flags: record] { let subcommand = ($ops | split row " " | get -o 0) let rest_ops = ($ops | split row " " | skip 1 | str join " ") match $subcommand { "create" => { handle_create $rest_ops $flags } "delete" => { handle_delete $rest_ops $flags } "list" => { handle_list $rest_ops $flags } _ => { print "❌ Unknown subcommand: $subcommand" print "Available: create, delete, list" exit 1 } } } ``` ### Pattern 4: Command with Flag-Based Routing **Use Case**: Command behavior changes based on flags ```nushell def handle_flag_routed_command [ops: string, flags: record] { if $flags.check_mode { # Dry-run mode print "🔍 Check mode: simulating command..." let args = build_module_args $flags $ops run_module $args "module_name" # No --exec, returns output } else { # Normal execution let args = build_module_args $flags $ops run_module $args "module_name" --exec } } ``` ## Best Practices ### 1. Keep Handlers Focused Each handler should do **one thing well**: - ✅ Good: `handle_server` manages all server operations - ❌ Bad: `handle_server` also manages clusters and taskservs ### 2. Use Descriptive Error Messages ```nushell # ❌ Bad print "Error" # ✅ Good print "❌ Unknown taskserv: kubernetes-invalid" print "" print "Available taskservs:" print " • kubernetes" print " • containerd" print " • cilium" print "" print "Use 'provisioning taskserv list' to see all available taskservs" ``` ### 3. Leverage Centralized Functions Don't repeat code - use centralized functions: ```nushell # ❌ Bad: Repeating flag handling def handle_bad [ops: string, flags: record] { let use_check = if $flags.check_mode { "--check " } else { "" } let use_yes = if $flags.auto_confirm { "--yes " } else { "" } let str_infra = if ($flags.infra | is-not-empty) { $"--infra ($flags.infra) " } else { "" } # ... 10 more lines of flag handling run_module $"($ops) ($use_check)($use_yes)($str_infra)..." "module" --exec } # ✅ Good: Using centralized function def handle_good [ops: string, flags: record] { let args = build_module_args $flags $ops run_module $args "module" --exec } ``` ### 4. Document Your Changes Update relevant documentation: - **ADR-006**: If architectural changes - **CLAUDE.md**: If new commands or shortcuts - **help_system.nu**: If new categories or commands - **This guide**: If new patterns or conventions ### 5. Test Thoroughly Before committing: - [ ] Run test suite: `nu tests/test_provisioning_refactor.nu` - [ ] Test manual execution - [ ] Test with `--check` flag - [ ] Test with `--debug` flag - [ ] Test help: both `provisioning cmd help` and `provisioning help cmd` - [ ] Test shortcuts ## Troubleshooting ### Issue: "Module not found" **Cause**: Incorrect import path in handler **Fix**: Use relative imports with `.nu` extension: ```nushell # ✅ Correct use ../flags.nu * use ../../lib_provisioning * # ❌ Wrong use ../main_provisioning/flags * use lib_provisioning * ``` ### Issue: "Parse mismatch: expected colon" **Cause**: Missing type signature format **Fix**: Use proper Nushell 0.107 type signature: ```nushell # ✅ Correct export def my_function [param: string]: nothing -> string { "result" } # ❌ Wrong export def my_function [param: string] -> string { "result" } ``` ### Issue: "Command not routing correctly" **Cause**: Shortcut not in command registry **Fix**: Add to `dispatcher.nu:get_command_registry`: ```nushell "myshortcut" => "domain command" ``` ### Issue: "Flags not being passed" **Cause**: Not using `build_module_args` **Fix**: Use centralized flag builder: ```nushell let args = build_module_args $flags $ops run_module $args "module" --exec ``` ## Quick Reference ### File Locations ```plaintext provisioning/core/nulib/ ├── provisioning - Main entry, flag definitions ├── main_provisioning/ │ ├── flags.nu - Flag parsing (parse_common_flags, build_module_args) │ ├── dispatcher.nu - Routing (get_command_registry, dispatch_command) │ ├── help_system.nu - Help (provisioning-help, help-*) │ └── commands/ - Domain handlers (handle_*_command) tests/ └── test_provisioning_refactor.nu - Test suite docs/ ├── architecture/ │ └── adr-006-provisioning-cli-refactoring.md - Architecture docs └── development/ └── COMMAND_HANDLER_GUIDE.md - This guide ``` ### Key Functions ```nushell # In flags.nu parse_common_flags [flags: record]: nothing -> record build_module_args [flags: record, extra: string = ""]: nothing -> string set_debug_env [flags: record] get_debug_flag [flags: record]: nothing -> string # In dispatcher.nu get_command_registry []: nothing -> record dispatch_command [args: list, flags: record] # In help_system.nu provisioning-help [category?: string]: nothing -> string help-infrastructure []: nothing -> string help-orchestration []: nothing -> string # ... (one for each category) # In commands/*.nu handle_*_command [command: string, ops: string, flags: record] # Example: handle_infrastructure_command, handle_workspace_command ``` ### Testing Commands ```bash # Run full test suite nu tests/test_provisioning_refactor.nu # Test specific command provisioning/core/cli/provisioning my-command test --check # Test with debug provisioning/core/cli/provisioning --debug my-command test # Test help provisioning/core/cli/provisioning help my-command provisioning/core/cli/provisioning my-command help # Bi-directional ``` ## Further Reading - **[ADR-006: CLI Refactoring](../architecture/adr/adr-006-provisioning-cli-refactoring.md)** - Complete architectural decision record - **[Project Structure](project-structure.md)** - Overall project organization - **[Workflow Development](workflow.md)** - Workflow system architecture - **[Development Integration](integration.md)** - Integration patterns ## Contributing When contributing command handler changes: 1. **Follow existing patterns** - Use the patterns in this guide 2. **Update documentation** - Keep docs in sync with code 3. **Add tests** - Cover your new functionality 4. **Run test suite** - Ensure nothing breaks 5. **Update CLAUDE.md** - Document new commands/shortcuts For questions or issues, refer to ADR-006 or ask the team. --- *This guide is part of the provisioning project documentation. Last updated: 2025-09-30*