- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
- Unified Component Architecture: components/mod.nu, main_provisioning/
{components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
- Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
ADDING_COMMANDS.md documents new-command workflow.
- Platform service manager: service-manager.nu (+573), startup.nu (+611),
service-check.nu (+255); autostart/bootstrap/health/target refactored.
- Nushell 0.112.2 migration: removed all try/catch and bash redirections;
external commands prefixed with ^; type signatures enforced. Driven by
scripts/refactor-try-catch{,-simplified}.nu.
- TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
tty-filter.sh, tty-commands.conf.
- New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
- Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
refactored (-454), removed legacy loaders/file_loader.nu (-330).
- Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
- Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
test_services.
- README + CHANGELOG updated.
13 KiB
Provisioning CLI - Flow-Aware TTY Command Management
Architecture Overview
The provisioning wrapper (provisioning/core/cli/provisioning) is a flow controller that manages three execution paths for command handling:
- Standalone TTY - Interactive commands that exit after execution
- Pipeline TTY - Interactive commands that output for piping to other commands
- Regular - Standard Nushell command processing
This design enables:
- Interactive commands (TTY input) without blocking Nushell
- Inter-command piping of TTY output to subsequent commands
- Same-command flow (TTY input → Nushell processing in one execution)
- Daemon optimization for non-interactive commands
How Flow Management Works
Execution Flow
User Command: provisioning <cmd> <args>
↓
Bash wrapper (provisioning)
↓
┌──────────────────────────────────────┐
│ Phase 1: TTY Command Detection │
│ - Read tty-commands.conf registry │
│ - Match command pattern │
└──────────────────────────────────────┘
↓
├─→ Not a TTY command → Continue to Nushell (normal processing)
│
└─→ TTY command found → Check flow type
↓
├─→ flow=exit → Execute wrapper, exit immediately
├─→ flow=pipe → Execute wrapper, output to stdout, exit (allows piping)
└─→ flow=continue → Execute wrapper, capture output, continue to Nushell
($env.TTY_OUTPUT available in Nushell)
Flow Types Explained
1. Standalone TTY Commands (flow=exit)
Use case: Interactive forms, setup wizards, authentication dialogs
Example: provisioning setup wizard
Flow:
Bash wrapper → TTY filter detects "setup wizard" → flow=exit
↓
Execute wrapper: core/shlib/setup-wizard-tty.sh
↓
User interaction (TypeDialog form)
↓
Exit wrapper → Exit bash wrapper
↓
Never reaches Nushell
Registry entry:
"setup wizard" "core/shlib/setup-wizard-tty.sh" "exit"
2. Pipeline TTY Commands (flow=pipe)
Use case: Getting user input to pipe to another command
Example: provisioning auth get-key | provisioning deploy --api-key-stdin
Flow:
Bash wrapper → TTY filter detects "auth get-key" → flow=pipe
↓
Execute wrapper: core/shlib/auth-get-key-tty.sh
↓
User provides API key via TTY prompt
↓
Wrapper outputs API key to stdout
↓
Exit wrapper (process exits, pipe has captured output)
↓
Next command receives API key from stdin
Registry entry:
"auth get-key" "core/shlib/auth-get-key-tty.sh" "pipe"
Wrapper requirements (flow=pipe):
- Must output result to stdout
- Output must be newline-terminated
- Exit with proper code (0=success, non-zero=error)
3. Continue-to-Nushell TTY Commands (flow=continue)
Use case: TTY input that needs further processing in Nushell
Example: provisioning auth integrate --provider azure
Flow:
Bash wrapper → TTY filter detects "auth integrate" → flow=continue
↓
Execute wrapper: core/shlib/auth-integrate-tty.sh
↓
User provides credentials via TTY prompt
↓
Wrapper outputs credentials (usually JSON) to stdout
↓
Filter CAPTURES output to $TTY_OUTPUT environment variable
↓
Set $env.PROVISIONING_BYPASS_DAEMON=true (skip daemon)
↓
Return 0 WITHOUT EXITING (continue to Nushell)
↓
Nushell dispatcher receives both:
- CLI args: --provider azure
- TTY output: $env.TTY_OUTPUT (credentials JSON)
↓
Nushell script processes both, completes integration
Registry entry:
"auth integrate" "core/shlib/auth-integrate-tty.sh" "continue"
Wrapper requirements (flow=continue):
- Must output result to stdout (usually JSON for structured data)
- Exit with proper code (0=success, non-zero=error)
Nushell script requirements (receives flow=continue output):
export def "provisioning auth integrate" [--provider: string] {
# Check if TTY output exists (guard pattern)
let tty_output = ($env.TTY_OUTPUT? | default "")
if ($tty_output | is-empty) {
error make {msg: "No credentials provided via TTY"}
}
# Parse TTY output (credentials)
let credentials = ($tty_output | from json)
# Use both TTY input ($credentials) and CLI args ($provider)
# Complete integration logic...
# Clear sensitive data after use
hide-env TTY_OUTPUT
}
4. Regular Commands
Use case: Standard provisioning operations
Example: provisioning server list
Flow:
Bash wrapper → TTY filter checks registry → Not found → Return 1
↓
Continue to normal processing:
- Fast-path checks (help, workspace, env, etc.)
- Daemon check (if applicable)
- Nushell dispatcher
Registry Format
File: provisioning/core/cli/tty-commands.conf
Three-field format: "PATTERN" "WRAPPER_PATH" "FLOW_TYPE"
# Exact command match (e.g., "setup wizard" matches "provisioning setup wizard")
"setup wizard" "core/shlib/setup-wizard-tty.sh" "exit"
# Paths are relative to $PROVISIONING
"auth get-key" "core/shlib/auth-get-key-tty.sh" "pipe"
# Flow types: exit | pipe | continue
"auth integrate" "core/shlib/auth-integrate-tty.sh" "continue"
Flow Type Decision Matrix
| Interaction | Flow Type | Example |
|---|---|---|
| Interactive form, no output needed | exit |
Setup wizard, auth login |
| User input → pipe to next command | pipe |
API key for piping to deploy |
| User input → same-command Nushell processing | continue |
Credentials for integration |
Adding New TTY Commands
Step 1: Create Wrapper Script
Create wrapper in provisioning/core/shlib/:
#!/bin/bash
set -euo pipefail
main() {
local input
# Get input from user
read -rsp "Prompt: " input
echo # Newline
# For flow=pipe: output to stdout
# For flow=continue: output to stdout (will be captured by filter)
echo "$input"
return 0
}
main "$@"
Make it executable:
chmod +x provisioning/core/shlib/your-wrapper-tty.sh
Step 2: Add Registry Entry
Edit provisioning/core/cli/tty-commands.conf:
# Standalone TTY
"your command" "core/shlib/your-wrapper-tty.sh" "exit"
# Pipeline TTY
"get something" "core/shlib/get-something-tty.sh" "pipe"
# Continue-to-Nushell TTY
"setup something" "core/shlib/setup-something-tty.sh" "continue"
Step 3: No Wrapper Modifications Required
The provisioning wrapper automatically:
- Reads registry
- Matches command pattern
- Routes based on flow type
- Handles all three flows
No need to modify provisioning wrapper for new commands!
Wrapper Script Requirements
For All Wrappers
- Shebang:
#!/bin/bash - Safety:
set -euo pipefail - Arguments: Accept
"${@}"from wrapper - Exit codes: 0=success, non-zero=error
- Validation:
shellcheckpasses without warnings
For flow=exit Wrappers
- Complete all interaction in wrapper
- Exit with proper code (0=success, non-zero=error)
- Output shown directly to user (from wrapper)
For flow=pipe Wrappers
- Get input from user (TTY)
- Output result to stdout
- Output must be newline-terminated
- Exit with proper code (0=success, non-zero=error)
For flow=continue Wrappers
- Get input from user (TTY)
- Output result to stdout (usually JSON)
- Exit with proper code (0=success, non-zero=error)
- Filter captures output → $TTY_OUTPUT
- Nushell script reads $env.TTY_OUTPUT
Environment Variables
Exported by Filter (flow=continue only)
$TTY_OUTPUT: Captured output from wrapper (available in Nushell as$env.TTY_OUTPUT)$PROVISIONING_BYPASS_DAEMON: Set to "true" to skip daemon (flow=continue automatically sets this)$TTY_WRAPPER_EXECUTED: Set to "true" when TTY wrapper was executed
Usage in Nushell
# Access TTY output in Nushell script
export def "provisioning auth integrate" [--provider: string] {
let tty_output = ($env.TTY_OUTPUT? | default "")
# Parse if JSON
let creds = ($tty_output | from json)
# Use both TTY output and CLI args
integration-logic $provider $creds
# Clear after use (security)
hide-env TTY_OUTPUT
}
Daemon Interaction
The flow filter intelligently manages daemon usage:
For flow=exit and flow=pipe
- ✅ Daemon can be used - No stdin required
- No output needs to be captured and passed to Nushell
- Daemon optimization available (~100ms startup improvement)
For flow=continue
- ❌ Daemon MUST be bypassed - stdin required for TTY interaction
PROVISIONING_BYPASS_DAEMON=trueautomatically set by filter- Direct Nushell execution (preserves stdin for TTY)
- Zero overhead (same as non-daemon path)
Testing TTY Commands
Test Standalone (flow=exit)
provisioning setup wizard
# Expected: TypeDialog form, user interaction, exits
Test Pipeline (flow=pipe)
provisioning auth get-key | wc -c
# Expected: Prompts for API key, outputs to pipe
Test Continue (flow=continue)
provisioning auth integrate --provider azure
# Expected: Prompts for credentials, passes to Nushell with $env.TTY_OUTPUT
Test Regular Command
provisioning server list
# Expected: Normal Nushell processing
Troubleshooting
Command Not Executed
- Check: Is command in tty-commands.conf?
- Check: Does pattern exactly match command?
- Check: Is wrapper path correct and executable?
Wrapper Not Found
- Error message:
Warning: TTY wrapper not found or not executable: /path/to/wrapper - Check: File exists at
$PROVISIONING/wrapper-path - Check: File is executable:
chmod +x wrapper-path
Output Not Piping (flow=pipe)
- Check: Wrapper outputs to stdout (not stderr)
- Check: Output is newline-terminated:
echo "output" - Check: No daemon interference (PROVISIONING_BYPASS_DAEMON not set)
Nushell Not Receiving Output (flow=continue)
- Check:
$env.TTY_OUTPUTaccessible in Nushell:echo $env.TTY_OUTPUT - Check: Output format (usually JSON):
echo $env.TTY_OUTPUT | from json - Check: Wrapper exits with 0:
echo $?
Implementation Details
Filter Location and Function
File: provisioning/core/cli/tty-filter.sh
Function: filter_tty_command()
Lines: ~104 (includes documentation and three flow paths)
Integration in Wrapper
File: provisioning/core/cli/provisioning
Lines: ~20 (sources filter, calls function, continues to Nushell)
Registry Parsing
- File:
provisioning/core/cli/tty-commands.conf - Method: Line-by-line bash read (no jq dependency)
- Format: Three-field bash array (bash-compatible)
- Sections: Organized by flow type for clarity
Performance Implications
startup time
- flow=exit/pipe: Daemon available for startup optimization (~100ms improvement)
- flow=continue: Daemon bypassed (stdin needed), ~500ms traditional path
- Regular commands: Normal daemon/non-daemon path selection
Memory
- flow=continue: Wrapper output stored in
$TTY_OUTPUTenvironment variable - Typical size: < 1KB (credentials, keys, etc.)
- Cleared after Nushell processing (or via
hide-env)
Security Considerations
Sensitive Data in $TTY_OUTPUT
- Credentials captured in
$TTY_OUTPUT - Nushell scripts should clear after use:
hide-env TTY_OUTPUT - Wrapper output may be logged: Use standard Unix conventions (hide passwords from output)
Wrapper Location Restriction
- Wrappers should be in
provisioning/core/shlib/orprovisioning/scripts/ - Registry reads only wrappers from these trusted locations
- Pattern validation prevents arbitrary script execution
No Shell Injection
- All variables quoted:
"$variable" - No eval or command substitution with user input
- Pattern matching uses exact string match (no regex)
Related Files
- Filter:
provisioning/core/cli/tty-filter.sh - Registry:
provisioning/core/cli/tty-commands.conf - Wrapper:
provisioning/core/cli/provisioning - Example wrappers:
provisioning/core/shlib/auth-get-key-tty.sh,provisioning/core/shlib/auth-integrate-tty.sh
Key Insights
The provisioning wrapper is not just a pass-through - it's a flow controller that:
- Detects TTY requirements (registry matching)
- Manages execution paths (three flows: exit, pipe, continue)
- Controls exit behavior (standalone vs pipeline vs same-command)
- Enables inter-command piping (TTY output to pipes)
- Supports Nushell integration (TTY→Nushell continuation)
- Optimizes with daemon (skip when stdin needed)
This solves:
- "el tema no es sólo un filter" → ✅ Flow controller with three execution paths
- "cómo gestionar el flow por medio del provisioning command" → ✅ Registry + flow types
- "usamos tty para input de una API key, se lo pasamos a un script de nushell" → ✅ Pipeline + continue flows
Version: 1.0.0 Last Updated: January 2026 Status: ✅ Production Ready