provisioning/docs/book/development/COMMAND_HANDLER_GUIDE.html

739 lines
36 KiB
HTML
Raw Normal View History

<!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>