provisioning/docs/book/development/COMMAND_HANDLER_GUIDE.html
Jesús Pérez 6a59d34bb1
chore: update provisioning configuration and documentation
Update configuration files, templates, and internal documentation
for the provisioning repository system.

Configuration Updates:
- KMS configuration modernization
- Plugin system settings
- Service port mappings
- Test cluster topologies
- Installation configuration examples
- VM configuration defaults
- Cedar authorization policies

Documentation Updates:
- Library module documentation
- Extension API guides
- AI system documentation
- Service management guides
- Test environment setup
- Plugin usage guides
- Validator configuration documentation

All changes are backward compatible.
2025-12-11 21:50:42 +00:00

739 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="en" class="ayu sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Command Handler Guide - Provisioning Platform Documentation</title>
<!-- Custom HTML head -->
<meta name="description" content="Complete documentation for the Provisioning Platform - Infrastructure automation with Nushell, KCL, and Rust">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../";
const default_light_theme = "ayu";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('ayu')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Provisioning Platform Documentation</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/provisioning/provisioning-platform" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/provisioning/provisioning-platform/edit/main/provisioning/docs/src/development/COMMAND_HANDLER_GUIDE.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="command-handler-developer-guide"><a class="header" href="#command-handler-developer-guide">Command Handler Developer Guide</a></h1>
<p><strong>Target Audience</strong>: Developers working on the provisioning CLI
<strong>Last Updated</strong>: 2025-09-30
<strong>Related</strong>: <a href="../architecture/adr/ADR-006-provisioning-cli-refactoring.html">ADR-006 CLI Refactoring</a></p>
<h2 id="overview"><a class="header" href="#overview">Overview</a></h2>
<p>The provisioning CLI uses a <strong>modular, domain-driven architecture</strong> that separates concerns into focused command handlers. This guide shows you how to work with this architecture.</p>
<h3 id="key-architecture-principles"><a class="header" href="#key-architecture-principles">Key Architecture Principles</a></h3>
<ol>
<li><strong>Separation of Concerns</strong>: Routing, flag parsing, and business logic are separated</li>
<li><strong>Domain-Driven Design</strong>: Commands organized by domain (infrastructure, orchestration, etc.)</li>
<li><strong>DRY (Dont Repeat Yourself)</strong>: Centralized flag handling eliminates code duplication</li>
<li><strong>Single Responsibility</strong>: Each module has one clear purpose</li>
<li><strong>Open/Closed Principle</strong>: Easy to extend, no need to modify core routing</li>
</ol>
<h3 id="architecture-components"><a class="header" href="#architecture-components">Architecture Components</a></h3>
<pre><code>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
</code></pre>
<h2 id="adding-new-commands"><a class="header" href="#adding-new-commands">Adding New Commands</a></h2>
<h3 id="step-1-choose-the-right-domain-handler"><a class="header" href="#step-1-choose-the-right-domain-handler">Step 1: Choose the Right Domain Handler</a></h3>
<p>Commands are organized by domain. Choose the appropriate handler:</p>
<div class="table-wrapper"><table><thead><tr><th>Domain</th><th>Handler</th><th>Responsibility</th></tr></thead><tbody>
<tr><td><code>infrastructure.nu</code></td><td>Server/taskserv/cluster/infra lifecycle</td><td></td></tr>
<tr><td><code>orchestration.nu</code></td><td>Workflow/batch operations, orchestrator control</td><td></td></tr>
<tr><td><code>development.nu</code></td><td>Module discovery, layers, versions, packaging</td><td></td></tr>
<tr><td><code>workspace.nu</code></td><td>Workspace and template management</td><td></td></tr>
<tr><td><code>configuration.nu</code></td><td>Environment, settings, initialization</td><td></td></tr>
<tr><td><code>utilities.nu</code></td><td>SSH, SOPS, cache, providers, utilities</td><td></td></tr>
<tr><td><code>generation.nu</code></td><td>Generate commands (server, taskserv, etc.)</td><td></td></tr>
</tbody></table>
</div>
<h3 id="step-2-add-command-to-handler"><a class="header" href="#step-2-add-command-to-handler">Step 2: Add Command to Handler</a></h3>
<p><strong>Example: Adding a new server command <code>server status</code></strong></p>
<p>Edit <code>provisioning/core/nulib/main_provisioning/commands/infrastructure.nu</code>:</p>
<pre><code class="language-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" =&gt; { handle_server $ops $flags }
"taskserv" | "task" =&gt; { handle_taskserv $ops $flags }
"cluster" =&gt; { handle_cluster $ops $flags }
"infra" | "infras" =&gt; { handle_infra $ops $flags }
_ =&gt; {
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
}
</code></pre>
<p><strong>Thats it!</strong> The command is now available as <code>provisioning server status</code>.</p>
<h3 id="step-3-add-shortcuts-optional"><a class="header" href="#step-3-add-shortcuts-optional">Step 3: Add Shortcuts (Optional)</a></h3>
<p>If you want shortcuts like <code>provisioning s status</code>:</p>
<p>Edit <code>provisioning/core/nulib/main_provisioning/dispatcher.nu</code>:</p>
<pre><code class="language-nushell">export def get_command_registry []: nothing -&gt; record {
{
# Infrastructure commands
"s" =&gt; "infrastructure server" # Already exists
"server" =&gt; "infrastructure server" # Already exists
# Your new shortcut (if needed)
# Example: "srv-status" =&gt; "infrastructure server status"
# ... rest of registry
}
}
</code></pre>
<p><strong>Note</strong>: Most shortcuts are already configured. You only need to add new shortcuts if youre creating completely new command categories.</p>
<h2 id="modifying-existing-handlers"><a class="header" href="#modifying-existing-handlers">Modifying Existing Handlers</a></h2>
<h3 id="example-enhancing-the-taskserv-command"><a class="header" href="#example-enhancing-the-taskserv-command">Example: Enhancing the <code>taskserv</code> Command</a></h3>
<p>Lets say you want to add better error handling to the taskserv command:</p>
<p><strong>Before:</strong></p>
<pre><code class="language-nushell">def handle_taskserv [ops: string, flags: record] {
let args = build_module_args $flags $ops
run_module $args "taskserv" --exec
}
</code></pre>
<p><strong>After:</strong></p>
<pre><code class="language-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
}
</code></pre>
<h2 id="working-with-flags"><a class="header" href="#working-with-flags">Working with Flags</a></h2>
<h3 id="using-centralized-flag-handling"><a class="header" href="#using-centralized-flag-handling">Using Centralized Flag Handling</a></h3>
<p>The <code>flags.nu</code> module provides centralized flag handling:</p>
<pre><code class="language-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
</code></pre>
<h3 id="available-flag-parsing"><a class="header" href="#available-flag-parsing">Available Flag Parsing</a></h3>
<p>The <code>parse_common_flags</code> function normalizes these flags:</p>
<div class="table-wrapper"><table><thead><tr><th>Flag Record Field</th><th>Description</th></tr></thead><tbody>
<tr><td><code>show_version</code></td><td>Version display (<code>--version</code>, <code>-v</code>)</td></tr>
<tr><td><code>show_info</code></td><td>Info display (<code>--info</code>, <code>-i</code>)</td></tr>
<tr><td><code>show_about</code></td><td>About display (<code>--about</code>, <code>-a</code>)</td></tr>
<tr><td><code>debug_mode</code></td><td>Debug mode (<code>--debug</code>, <code>-x</code>)</td></tr>
<tr><td><code>check_mode</code></td><td>Check mode (<code>--check</code>, <code>-c</code>)</td></tr>
<tr><td><code>auto_confirm</code></td><td>Auto-confirm (<code>--yes</code>, <code>-y</code>)</td></tr>
<tr><td><code>wait</code></td><td>Wait for completion (<code>--wait</code>, <code>-w</code>)</td></tr>
<tr><td><code>keep_storage</code></td><td>Keep storage (<code>--keepstorage</code>)</td></tr>
<tr><td><code>infra</code></td><td>Infrastructure name (<code>--infra</code>)</td></tr>
<tr><td><code>outfile</code></td><td>Output file (<code>--outfile</code>)</td></tr>
<tr><td><code>output_format</code></td><td>Output format (<code>--out</code>)</td></tr>
<tr><td><code>template</code></td><td>Template name (<code>--template</code>)</td></tr>
<tr><td><code>select</code></td><td>Selection (<code>--select</code>)</td></tr>
<tr><td><code>settings</code></td><td>Settings file (<code>--settings</code>)</td></tr>
<tr><td><code>new_infra</code></td><td>New infra name (<code>--new</code>)</td></tr>
</tbody></table>
</div>
<h3 id="adding-new-flags"><a class="header" href="#adding-new-flags">Adding New Flags</a></h3>
<p>If you need to add a new flag:</p>
<ol>
<li><strong>Update main <code>provisioning</code> file</strong> to accept the flag</li>
<li><strong>Update <code>flags.nu:parse_common_flags</code></strong> to normalize it</li>
<li><strong>Update <code>flags.nu:build_module_args</code></strong> to pass it to modules</li>
</ol>
<p><strong>Example: Adding <code>--timeout</code> flag</strong></p>
<pre><code class="language-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 -&gt; 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 -&gt; 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)..."
}
</code></pre>
<h2 id="adding-new-shortcuts"><a class="header" href="#adding-new-shortcuts">Adding New Shortcuts</a></h2>
<h3 id="shortcut-naming-conventions"><a class="header" href="#shortcut-naming-conventions">Shortcut Naming Conventions</a></h3>
<ul>
<li><strong>1-2 letters</strong>: Ultra-short for common commands (<code>s</code> for server, <code>ws</code> for workspace)</li>
<li><strong>3-4 letters</strong>: Abbreviations (<code>orch</code> for orchestrator, <code>tmpl</code> for template)</li>
<li><strong>Aliases</strong>: Alternative names (<code>task</code> for taskserv, <code>flow</code> for workflow)</li>
</ul>
<h3 id="example-adding-a-new-shortcut"><a class="header" href="#example-adding-a-new-shortcut">Example: Adding a New Shortcut</a></h3>
<p>Edit <code>provisioning/core/nulib/main_provisioning/dispatcher.nu</code>:</p>
<pre><code class="language-nushell">export def get_command_registry []: nothing -&gt; record {
{
# ... existing shortcuts
# Add your new shortcut
"db" =&gt; "infrastructure database" # New: db command
"database" =&gt; "infrastructure database" # Full name
# ... rest of registry
}
}
</code></pre>
<p><strong>Important</strong>: After adding a shortcut, update the help system in <code>help_system.nu</code> to document it.</p>
<h2 id="testing-your-changes"><a class="header" href="#testing-your-changes">Testing Your Changes</a></h2>
<h3 id="running-the-test-suite"><a class="header" href="#running-the-test-suite">Running the Test Suite</a></h3>
<pre><code class="language-bash"># Run comprehensive test suite
nu tests/test_provisioning_refactor.nu
</code></pre>
<h3 id="test-coverage"><a class="header" href="#test-coverage">Test Coverage</a></h3>
<p>The test suite validates:</p>
<ul>
<li>✅ Main help display</li>
<li>✅ Category help (infrastructure, orchestration, development, workspace)</li>
<li>✅ Bi-directional help routing</li>
<li>✅ All command shortcuts</li>
<li>✅ Category shortcut help</li>
<li>✅ Command routing to correct handlers</li>
</ul>
<h3 id="adding-tests-for-your-changes"><a class="header" href="#adding-tests-for-your-changes">Adding Tests for Your Changes</a></h3>
<p>Edit <code>tests/test_provisioning_refactor.nu</code>:</p>
<pre><code class="language-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
}
</code></pre>
<h3 id="manual-testing"><a class="header" href="#manual-testing">Manual Testing</a></h3>
<pre><code class="language-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
</code></pre>
<h2 id="common-patterns"><a class="header" href="#common-patterns">Common Patterns</a></h2>
<h3 id="pattern-1-simple-command-handler"><a class="header" href="#pattern-1-simple-command-handler">Pattern 1: Simple Command Handler</a></h3>
<p><strong>Use Case</strong>: Command just needs to execute a module with standard flags</p>
<pre><code class="language-nushell">def handle_simple_command [ops: string, flags: record] {
let args = build_module_args $flags $ops
run_module $args "module_name" --exec
}
</code></pre>
<h3 id="pattern-2-command-with-validation"><a class="header" href="#pattern-2-command-with-validation">Pattern 2: Command with Validation</a></h3>
<p><strong>Use Case</strong>: Need to validate input before execution</p>
<pre><code class="language-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 &lt;arg&gt;"
exit 1
}
# Execute
let args = build_module_args $flags $ops
run_module $args "module_name" --exec
}
</code></pre>
<h3 id="pattern-3-command-with-subcommands"><a class="header" href="#pattern-3-command-with-subcommands">Pattern 3: Command with Subcommands</a></h3>
<p><strong>Use Case</strong>: Command has multiple subcommands (like <code>server create</code>, <code>server delete</code>)</p>
<pre><code class="language-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" =&gt; { handle_create $rest_ops $flags }
"delete" =&gt; { handle_delete $rest_ops $flags }
"list" =&gt; { handle_list $rest_ops $flags }
_ =&gt; {
print "❌ Unknown subcommand: $subcommand"
print "Available: create, delete, list"
exit 1
}
}
}
</code></pre>
<h3 id="pattern-4-command-with-flag-based-routing"><a class="header" href="#pattern-4-command-with-flag-based-routing">Pattern 4: Command with Flag-Based Routing</a></h3>
<p><strong>Use Case</strong>: Command behavior changes based on flags</p>
<pre><code class="language-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
}
}
</code></pre>
<h2 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h2>
<h3 id="1-keep-handlers-focused"><a class="header" href="#1-keep-handlers-focused">1. Keep Handlers Focused</a></h3>
<p>Each handler should do <strong>one thing well</strong>:</p>
<ul>
<li>✅ Good: <code>handle_server</code> manages all server operations</li>
<li>❌ Bad: <code>handle_server</code> also manages clusters and taskservs</li>
</ul>
<h3 id="2-use-descriptive-error-messages"><a class="header" href="#2-use-descriptive-error-messages">2. Use Descriptive Error Messages</a></h3>
<pre><code class="language-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"
</code></pre>
<h3 id="3-leverage-centralized-functions"><a class="header" href="#3-leverage-centralized-functions">3. Leverage Centralized Functions</a></h3>
<p>Dont repeat code - use centralized functions:</p>
<pre><code class="language-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
}
</code></pre>
<h3 id="4-document-your-changes"><a class="header" href="#4-document-your-changes">4. Document Your Changes</a></h3>
<p>Update relevant documentation:</p>
<ul>
<li><strong>ADR-006</strong>: If architectural changes</li>
<li><strong>CLAUDE.md</strong>: If new commands or shortcuts</li>
<li><strong>help_system.nu</strong>: If new categories or commands</li>
<li><strong>This guide</strong>: If new patterns or conventions</li>
</ul>
<h3 id="5-test-thoroughly"><a class="header" href="#5-test-thoroughly">5. Test Thoroughly</a></h3>
<p>Before committing:</p>
<ul>
<li><input disabled="" type="checkbox"/>
Run test suite: <code>nu tests/test_provisioning_refactor.nu</code></li>
<li><input disabled="" type="checkbox"/>
Test manual execution</li>
<li><input disabled="" type="checkbox"/>
Test with <code>--check</code> flag</li>
<li><input disabled="" type="checkbox"/>
Test with <code>--debug</code> flag</li>
<li><input disabled="" type="checkbox"/>
Test help: both <code>provisioning cmd help</code> and <code>provisioning help cmd</code></li>
<li><input disabled="" type="checkbox"/>
Test shortcuts</li>
</ul>
<h2 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
<h3 id="issue-module-not-found"><a class="header" href="#issue-module-not-found">Issue: “Module not found”</a></h3>
<p><strong>Cause</strong>: Incorrect import path in handler</p>
<p><strong>Fix</strong>: Use relative imports with <code>.nu</code> extension:</p>
<pre><code class="language-nushell"># ✅ Correct
use ../flags.nu *
use ../../lib_provisioning *
# ❌ Wrong
use ../main_provisioning/flags *
use lib_provisioning *
</code></pre>
<h3 id="issue-parse-mismatch-expected-colon"><a class="header" href="#issue-parse-mismatch-expected-colon">Issue: “Parse mismatch: expected colon”</a></h3>
<p><strong>Cause</strong>: Missing type signature format</p>
<p><strong>Fix</strong>: Use proper Nushell 0.107 type signature:</p>
<pre><code class="language-nushell"># ✅ Correct
export def my_function [param: string]: nothing -&gt; string {
"result"
}
# ❌ Wrong
export def my_function [param: string] -&gt; string {
"result"
}
</code></pre>
<h3 id="issue-command-not-routing-correctly"><a class="header" href="#issue-command-not-routing-correctly">Issue: “Command not routing correctly”</a></h3>
<p><strong>Cause</strong>: Shortcut not in command registry</p>
<p><strong>Fix</strong>: Add to <code>dispatcher.nu:get_command_registry</code>:</p>
<pre><code class="language-nushell">"myshortcut" =&gt; "domain command"
</code></pre>
<h3 id="issue-flags-not-being-passed"><a class="header" href="#issue-flags-not-being-passed">Issue: “Flags not being passed”</a></h3>
<p><strong>Cause</strong>: Not using <code>build_module_args</code></p>
<p><strong>Fix</strong>: Use centralized flag builder:</p>
<pre><code class="language-nushell">let args = build_module_args $flags $ops
run_module $args "module" --exec
</code></pre>
<h2 id="quick-reference"><a class="header" href="#quick-reference">Quick Reference</a></h2>
<h3 id="file-locations"><a class="header" href="#file-locations">File Locations</a></h3>
<pre><code>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
</code></pre>
<h3 id="key-functions"><a class="header" href="#key-functions">Key Functions</a></h3>
<pre><code class="language-nushell"># In flags.nu
parse_common_flags [flags: record]: nothing -&gt; record
build_module_args [flags: record, extra: string = ""]: nothing -&gt; string
set_debug_env [flags: record]
get_debug_flag [flags: record]: nothing -&gt; string
# In dispatcher.nu
get_command_registry []: nothing -&gt; record
dispatch_command [args: list, flags: record]
# In help_system.nu
provisioning-help [category?: string]: nothing -&gt; string
help-infrastructure []: nothing -&gt; string
help-orchestration []: nothing -&gt; string
# ... (one for each category)
# In commands/*.nu
handle_*_command [command: string, ops: string, flags: record]
# Example: handle_infrastructure_command, handle_workspace_command
</code></pre>
<h3 id="testing-commands"><a class="header" href="#testing-commands">Testing Commands</a></h3>
<pre><code class="language-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
</code></pre>
<h2 id="further-reading"><a class="header" href="#further-reading">Further Reading</a></h2>
<ul>
<li><strong><a href="../architecture/adr/ADR-006-provisioning-cli-refactoring.html">ADR-006: CLI Refactoring</a></strong> - Complete architectural decision record</li>
<li><strong><a href="project-structure.html">Project Structure</a></strong> - Overall project organization</li>
<li><strong><a href="workflow.html">Workflow Development</a></strong> - Workflow system architecture</li>
<li><strong><a href="integration.html">Development Integration</a></strong> - Integration patterns</li>
</ul>
<h2 id="contributing"><a class="header" href="#contributing">Contributing</a></h2>
<p>When contributing command handler changes:</p>
<ol>
<li><strong>Follow existing patterns</strong> - Use the patterns in this guide</li>
<li><strong>Update documentation</strong> - Keep docs in sync with code</li>
<li><strong>Add tests</strong> - Cover your new functionality</li>
<li><strong>Run test suite</strong> - Ensure nothing breaks</li>
<li><strong>Update CLAUDE.md</strong> - Document new commands/shortcuts</li>
</ol>
<p>For questions or issues, refer to ADR-006 or ask the team.</p>
<hr />
<p><em>This guide is part of the provisioning project documentation. Last updated: 2025-09-30</em></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../development/TASKSERV_QUICK_GUIDE.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../development/configuration.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../development/TASKSERV_QUICK_GUIDE.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../development/configuration.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>