Repository navigation
Syntax Reference
English | Français
Version: 1.0
Last Updated: 2026-10-10
Format: YAML or JSON
- Overview
- File Format
- Root Structure
- Metadata Section
- Global Options
- Commands Section
- Command Definition
- Arguments
- Options
- Repeatable Options
- Argument Types
- Validation Rules
- Complete Example
- Best Practices
See also: REPL Directives · Plugin Guide
The configuration file defines all CLI commands and REPL behavior for applications built with dynamic-cli.
Key Features:
- Define commands without writing code
- Support for positional arguments and options
- Automatic validation and type checking
- Extensible with custom handlers
Supported Formats:
- YAML (
.yaml,.yml) - Recommended for readability - JSON (
.json) - Compatible with existing tools
metadata:
version: "1.0.0"
prompt: "myapp"
prompt_suffix: " > "
global_options:
- name: "verbose"
# ...
commands:
- name: "init"
# ...{
"metadata": {
"version": "1.0.0",
"prompt": "myapp",
"prompt_suffix": " > "
},
"global_options": [
{
"name": "verbose"
}
],
"commands": [
{
"name": "init"
}
]
}Note: YAML examples are used throughout this document for readability.
The configuration file has three main sections:
metadata: # Application metadata
# ...
global_options: # Options available for ALL commands
# ...
commands: # List of available commands
# ...
directives: # Optional: overrides of the REPL directives
# ...metadata and commands are required. global_options is optional and
defaults to an empty list. directives is optional: see
REPL Directives.
Application-level information.
metadata:
version: string # Required - Configuration version
prompt: string # Required - REPL prompt text
prompt_suffix: string # Optional - Suffix after prompt (default: " > ")| Field | Type | Required | Description |
|---|---|---|---|
version |
string | ✅ Yes | Configuration file version (semantic versioning recommended) |
prompt |
string | ✅ Yes | Text displayed in REPL mode (e.g., "myapp", "rpn") |
prompt_suffix |
string | ❌ No | Text after prompt (default: " > "; typically " > " or "$ "). Since v0.9.0 (#67), this value is what ReplInterface actually renders — an earlier version hardcoded " > " regardless of this field. |
metadata:
version: "1.0.0"
prompt: "rpn"
prompt_suffix: " > "REPL Display:
rpn > _
Since v0.9.0 (DD-027).
In interactive REPL mode, a line ending in a trailing \ is not
dispatched immediately: the marker is stripped, the line is buffered,
the prompt switches to a distinct continuation prompt derived from
prompt/prompt_suffix above, and another line is read. The first
line that does not end in \ completes the command — every
buffered fragment plus this final line are joined with a single space
and dispatched exactly once, mirroring the familiar Unix shell
convention.
This is independent of Command Chaining (DD-026): chaining sequences several complete commands in one invocation; this feature lets a single command's options span several REPL input lines. The two compose without special-casing each other.
rpn > push \
...> 0
- Only a trailing
\triggers accumulation — no alternative explicit terminator (e.g.;) is recognized. - The reconstructed line (fragments joined by a single space,
\markers removed) is what reaches REPL history — never a raw partial fragment. -
Ctrl+Cwhile accumulating discards the buffer and returns to the normal prompt, the same as abandoning a continued line in a shell.
Continuation prompt derivation: the app-name segment of prompt
is replaced with ..., and prompt_suffix is appended with its
leading whitespace stripped — so it reads as one continuous mark
against the separator rather than leaving a stray gap:
prompt |
prompt_suffix |
Main prompt | Continuation prompt (default) |
|---|---|---|---|
rpn |
" > " (default) |
rpn > |
...> |
rpn |
" $ " |
rpn $ |
...$ |
The base segment (... above) can be overridden from Rust via
CliBuilder::prompt_multiline()
— there is no YAML field for it, since it is a display detail of the
interactive REPL rather than part of the command schema. An override
replaces only the base; prompt_suffix is still appended, without
stripping its leading whitespace this time — the override is the
caller's own spacing choice:
CliBuilder::new()
.prompt("rpn")
.prompt_multiline("_ _ _")
// prompt_suffix == " $ " → continuation prompt: "_ _ _ $ "Known constraint — no configurable abort/continue policy: a
command signalled complete (no trailing \) but still missing a
required argument or option fails immediately with today's exact
MissingArgument/MissingOption error — there is no re-prompt loop.
Deliberate, not an oversight: CLI one-shot mode
(CliInterface::run()), run_script(), and :load have no live
operator to re-prompt, so they always abort on an incomplete command;
the REPL keeps the same abort behaviour for CLI/REPL parity. Not
scheduled to change; revisit only if a concrete need appears.
Scope: applies only to ReplInterface::run() (interactive REPL).
In CLI one-shot mode, the invoking shell has already collapsed any
\-continued line before dynamic-cli ever sees its argv — nothing
to implement there. run_script() and the REPL's :load
directive are not affected: each line is dispatched
independently, and a trailing \ in a script line is read like any
other token.
Options that are available for ALL commands in the application.
global_options:
- name: string # Required - Unique identifier
short: string # Optional - Single character (e.g., "v")
long: string # Optional - Long form (e.g., "verbose")
option_type: ArgumentType # Required - Data type
required: boolean # Optional - Must be provided? (default: false)
default: string # Optional - Default value if not provided
description: string # Required - Help text
choices: [string] # Optional - List of valid values| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ Yes | Internal identifier (used in handlers) |
short |
string | ⬜ No | Short form flag (single character, e.g., "v" for -v) |
long |
string | ⬜ No | Long form flag (e.g., "verbose" for --verbose) |
option_type |
string | ✅ Yes | One of: string, integer, float, bool, path
|
required |
boolean | ⬜ No | If true, must be provided by user (default: false) |
default |
string | ⬜ No | Default value (as string, will be parsed to type) |
description |
string | ✅ Yes | User-facing help text |
choices |
array | ⬜ No | Restrict values to specific list |
Verbose Flag:
- name: "verbose"
short: "v"
long: "verbose"
option_type: "bool"
required: false
default: "false"
description: "Enable verbose output"Usage:
myapp -v command
myapp --verbose commandOutput Directory:
- name: "output"
short: "o"
long: "output"
option_type: "path"
required: false
description: "Output directory for results"Usage:
myapp -o /tmp/results command
myapp --output ./output commandFormat Selection:
- name: "format"
short: "f"
long: "format"
option_type: "string"
required: false
default: "text"
description: "Output format"
choices: ["text", "json", "yaml"]Usage:
myapp --format json command
myapp -f yaml command- At least one of
shortorlongmust be provided - Both can be provided for maximum flexibility
- Global options are passed to every command handler
List of available commands in the application.
commands:
- name: string # Required - Command name
aliases: [string] # Optional - Alternative names (default: [])
description: string # Required - Help text
required: boolean # Optional - Handler must be registered? (default: false)
arguments: [ArgumentDefinition] # Optional - Positional arguments (default: [])
options: [OptionDefinition] # Optional - Command-specific options (default: [])
implementation: string # Required - Handler function name| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ Yes | Primary command name (no spaces, lowercase recommended) |
aliases |
array | ❌ No | Alternative names (e.g., ["quit", "q"] for exit; default: []) |
description |
string | ✅ Yes | User-facing help text |
required |
boolean | ❌ No | If true, a handler must be registered for this command, or building the application fails (default: false) |
arguments |
array | ❌ No | List of positional arguments (default: []) |
options |
array | ❌ No | Command-specific options (default: []) |
implementation |
string | ✅ Yes | Identifier for command handler (referenced in code) |
commands:
- name: "input"
aliases: ["load", "open"]
description: "Load a configuration file"
required: true
arguments:
- name: "file"
arg_type: "path"
required: true
description: "Path to input file"
validation:
- must_exist: true
- extensions: [".yaml", ".json"]
options: []
implementation: "load_config"Usage:
myapp input config.yaml
myapp load config.yaml
myapp open config.jsonDetailed breakdown of a single command.
- name: "command-name"
aliases: ["alt1", "alt2"]
description: "What this command does"
required: false
arguments:
- name: "arg1"
arg_type: "string"
required: true
description: "First argument"
validation: []
- name: "arg2"
arg_type: "integer"
required: false
description: "Optional second argument"
validation:
- min: 1
max: 100
options:
- name: "option1"
short: "o"
long: "option"
option_type: "bool"
required: false
default: "false"
description: "Enable option"
choices: []
implementation: "handler_name"
continue_on_failure: false
requires_success: falsename (string, required):
- Primary identifier for the command
- Used in CLI:
myapp <name> - Convention: lowercase, no spaces, use hyphens for multi-word (e.g.,
run-simulation) - May not start with
:, which marks REPL directives
aliases (array of strings, optional, default []):
- Alternative names for the same command
- Example:
aliases: ["quit", "q", "exit"] - All aliases invoke the same handler
- Like
name, an alias may not start with:
description (string, required):
- User-facing help text
- Displayed in
helpcommand output - Should be concise but clear (one sentence recommended)
required (boolean, optional, default false):
-
true: a handler must be registered for this command; building the application fails otherwise -
false: the command is skipped at build time when no handler is registered for it - Says nothing about whether the command is executed: running it or not is up to the user
arguments (array, optional, default []):
- List of positional arguments
- Order matters (parsed left-to-right)
- See Arguments section for details
options (array, optional, default []):
- Command-specific flags/options
- Independent of
global_options - See Options section for details
implementation (string, required):
- Identifier used in Rust code to link to handler
- Convention: snake_case
- Example:
"load_config"maps toLoadConfigHandler
continue_on_failure (boolean, optional, default false):
- Governs command chaining (v0.8.0, DD-026) — has no effect when this command is not part of a chain.
-
false(default): if this command fails, the chain stops immediately — identical to today's single-command behaviour, applied per segment. -
true: if this command fails, the chain-position error is reported and execution proceeds to the next segment. Intended for genuinely optional steps (e.g. a non-critical secondary export).
requires_success (boolean, optional, default false):
- Also part of command chaining (DD-026).
Not related to
requiredabove —requiredis a startup-time check ("a handler must be registered"),requires_successis a per-invocation chain guard evaluated at dispatch time. -
false(default): this command runs regardless of any earlier failure in the chain (moot unless an earlier command'scontinue_on_failureistrue). -
true: if any earlier command in the chain has already failed (regardless of that failure's owncontinue_on_failurevalue), this command is skipped — not executed, not counted as an additional failure.
Sync vs. async is not a config concern (ref. #8). This field is agnostic to whether the handler behind it is sync or async — there is no
async: trueflag, and there won't be one. Whether a command executes synchronously or asynchronously is decided entirely on the Rust side, by which method the application calls when wiring up the handler:// Sync handler .register_sync_handler("load_config", Box::new(LoadConfigHandler)) // Async handler — same implementation name convention, same YAML .register_async_handler("fetch_remote", Box::new(FetchRemoteHandler))
CliBuilder::build()resolves eachimplementationname against whichever kind was registered. Adding a config-level flag would create a second source of truth that could disagree with the actual Rust registration — the binding alone is authoritative, deliberately.
Since v0.8.0 (DD-026).
A single CLI invocation (or a run_script()
line) can chain more than one command: once a command's declared arity
is exhausted, the next bare token is checked against the command
registry — if it resolves to a command name (or alias), it starts the
next command in the chain; if it doesn't, the line is rejected exactly
as an over-long single command always was. There is no separator token
(;, &&, |) — chaining is a consequence of exhausted arity alone,
not a distinct syntax to opt into. continue_on_failure and
requires_success, described above, govern per-command behaviour
within a chain.
For each command in the sequence:
- Its options (including repeatable-option occurrences, DD-024) and positional arguments are consumed up to its declared arity.
- The next token is looked up against the command registry.
- Resolves to a command name or alias → starts the next segment.
- Doesn't resolve → the same "too many arguments" error a single, non-chained command would raise — no behaviour change for a genuinely too-long single command.
Since v0.9.0 (DD-028).
continue_on_failure is set in YAML, independently of the handler's
own code — nothing ties the two together by default, so a handler
written to assume its failures always stop the chain could silently
end up configured with continue_on_failure: true (or vice versa).
There is no new YAML field for this: it's a Rust-side consistency
check layered on the existing field, checked once, at registration
time, before the application ever runs.
A handler author can declare an expectation by overriding
expected_fault_tolerance() on CommandHandler or
AsyncCommandHandler (both default to None — no opinion, no check):
impl CommandHandler for SolveCommand {
fn execute(&self, context: &mut dyn ExecutionContext, args: &ParsedArgs) -> Result<()> {
// ...
}
fn expected_fault_tolerance(&self) -> Option<bool> {
Some(false) // this handler was written assuming it always stops the chain
}
}CommandRegistry::register_sync/register_async compare this value
(when Some) against the command's configured continue_on_failure,
and reject registration with RegistryError::FaultToleranceMismatch
on a contradiction — catching a misconfigured YAML value at startup
instead of at first use in a chain. A handler that never overrides the
method (the default, None) is entirely unaffected, regardless of
what continue_on_failure is set to.
This does not verify the handler's internal logic — that its
execute() actually returns Err(...) when it's supposed to. That
remains outside any signature-level check, covered by the
application's own tests.
- A failing command is reported as
Error in command {n}/{total} ('{name}'): <normal error output>— the chain position is prefixed; the rest of the message is unchanged from a single-command failure. - A skipped command (via
requires_success) is reported asSkipped: command {n}/{total} ('{name}') — a preceding command failed. - The process exit code always reflects the first failure
encountered in the chain — the "triggering" failure — even if a
later
continue_on_failure: truecommand also fails, or a downstream command is skipped as a result.
If a command line supplies one token more than that command's declared
arity, and that leftover token happens to also be a registered command
name, it is silently read as the start of the next segment instead of
raising an error — segmentation cannot distinguish "a stray extra
value" from "the next command" once arity is exhausted. There is no
---style escape to force a value to be treated as positional rather
than a command boundary. Not scheduled for a fix; revisit only if a
concrete need appears.
-
CliInterface::run()(CLI one-shot) andrun_script()inherit chaining automatically — both dispatch through the same internal resolution path. - The REPL's
:loaddirective does not chain: each loaded line still goes through the REPL's own single-command path, unchanged.
commands:
- name: "configure"
description: "Register an input source"
aliases: []
required: false
arguments:
- name: "path"
arg_type: "path"
required: true
description: "Source file"
validation: []
options: []
implementation: "configure_handler"
- name: "solve"
description: "Run once all sources are configured"
aliases: []
required: false
arguments: []
options: []
implementation: "solve_handler"
requires_success: trueUsage:
myapp configure model.yml configure scenario.yml solve
# "configure" runs twice, then "solve" — only if both configure steps succeededPositional arguments for commands.
arguments:
- name: string # Required - Identifier
arg_type: ArgumentType # Required - Data type
required: boolean # Required - Must be provided?
description: string # Required - Help text
validation: [Rule] # Optional - Validation rules (default: [])| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ Yes | Internal identifier for argument |
arg_type |
string | ✅ Yes | One of: string, integer, float, bool, path
|
required |
boolean | ✅ Yes | If true, user must provide this argument |
description |
string | ✅ Yes | Help text for user |
validation |
array | ❌ No | Validation rules (default: []) |
File Path Argument:
- name: "file"
arg_type: "path"
required: true
description: "Input file path"
validation:
- must_exist: true
- extensions: [".yaml", ".json", ".yml"]Usage: myapp load config.yaml
Numeric Range:
- name: "iterations"
arg_type: "integer"
required: true
description: "Number of iterations"
validation:
- min: 1
max: 10000Usage: myapp simulate 5000
Optional String:
- name: "output-name"
arg_type: "string"
required: false
description: "Output file name"
validation: []Usage:
myapp export result.csv # Provides name
myapp export # Uses defaultArguments are parsed in order from left to right:
arguments:
- name: "source" # First positional
arg_type: "path"
required: true
- name: "destination" # Second positional
arg_type: "path"
required: trueUsage: myapp copy source.txt dest.txt
Command-specific flags and options.
options:
- name: string # Required - Identifier
short: string # Optional - Single character
long: string # Optional - Long form
option_type: ArgumentType # Required - Data type
required: boolean # Optional - Must be provided? (default: false)
default: string # Optional - Default value
description: string # Required - Help text
choices: [string] # Optional - Valid valuesSame as Global Options fields.
Boolean Flag:
- name: "recursive"
short: "r"
long: "recursive"
option_type: "bool"
required: false
default: "false"
description: "Process directories recursively"Usage:
myapp process -r /path
myapp process --recursive /pathInteger Option:
- name: "threads"
short: "t"
long: "threads"
option_type: "integer"
required: false
default: "4"
description: "Number of parallel threads"Usage:
myapp simulate --threads 8
myapp simulate -t 8
myapp simulate # Uses default: 4Choice Restriction:
- name: "level"
short: "l"
long: "level"
option_type: "string"
required: false
default: "info"
description: "Logging level"
choices: ["debug", "info", "warn", "error"]Usage:
myapp run --level debug
myapp run -l warn
myapp run # Uses default: info
myapp run -l invalid # ERROR: invalid choiceSince v0.6.0 (DD-024).
A repeatable option can appear more than once on the same command line.
Each occurrence starts with a discriminant — a bare token immediately
after the flag, drawn from choices — followed by zero or more key=value
sub-parameters. dynamic-cli accumulates every occurrence in command-line
order; the handler receives them all.
options:
- name: string # Required
long: string # Optional
short: string # Optional
option_type: ArgumentType # Required
required: boolean # Optional (default: false)
description: string # Required
choices: [string] # Required when repeatable — valid discriminants
repeatable: true # Required to enable this behaviour
option_parameters: # Required — one entry per discriminant
<discriminant>:
- name: string
arg_type: ArgumentType
required: boolean
description: string
validation: [] # Optional
secure: false # Optional-
choicesmust be non-empty and lists the valid discriminants. -
option_parameterskeys must matchchoicesexactly — no orphan key, no discriminant left undeclared. Config loading fails otherwise. - Each
option_parameters[discriminant]entry is a regularArgumentDefinition(same fields as positionalarguments:), but onlyname/arg_type/required/description/validation/secureare meaningful — ordering has no effect, since sub-parameters are matched by key, not position. -
defaultis not allowed whenrepeatable: true: the option's absence already means zero occurrences, so an implicit default would be ambiguous. -
option_parameterson an option withrepeatable: false(or absent) is a config error. - Two occurrences of the same discriminant with different
key=valuepairs are both kept. Two occurrences that are strictly identical (same discriminant, same key=value pairs) are rejected — this is the only overlap the framework can detect without domain knowledge; partially-overlapping occurrences (samefile, differentresolution, say) are accepted and left to the handler to reconcile.
- name: "output"
long: "output"
option_type: "string"
required: false
description: "Write results in one or more output kinds"
choices: ["csv", "plot"]
repeatable: true
option_parameters:
csv:
- name: "file"
arg_type: "path"
required: true
description: "Destination CSV file"
- name: "resolution"
arg_type: "integer"
required: false
description: "Time-step resolution"
plot:
- name: "file"
arg_type: "path"
required: true
description: "Destination image file"Usage:
myapp export --output csv file=results.csv resolution=100
myapp export --output csv file=results.csv --output plot file=chart.png
myapp export --output csv file=a.csv --output csv file=b.csv # both kept — different `file`
myapp export --output csv file=a.csv --output csv file=a.csv # ERROR: DuplicateOptionOccurrence
myapp export --output json file=out.json # ERROR: UnknownDiscriminant
myapp export --output csv # ERROR: MissingRequiredOptionParameter (file)
myapp export --output csv file=a.csv depth=3 # ERROR: UnknownOptionParameter (depth)CommandHandler::execute (and validate) receive a ParsedArgs value
(see the executor module docs)
rather than a flat HashMap<String, String>, precisely so a repeatable option's occurrences
can be represented alongside scalar options:
let occurrences = args.get_repeated("output").unwrap_or(&[]);
for occ in occurrences {
match occ.discriminant.as_str() {
"csv" => { /* occ.params.get("file"), occ.params.get("resolution") */ }
"plot" => { /* occ.params.get("file") */ }
_ => unreachable!(), // config validation already rejected other discriminants
}
}See CHANGELOG.md (v0.6.0) for the full HashMap<String, String> →
ParsedArgs migration path for existing handlers.
Supported data types for arguments and options.
| Type | Description | Example Values | Rust Type |
|---|---|---|---|
string |
Text value |
"hello", "config.yaml"
|
String |
integer |
Whole number |
42, -10, 0
|
i64 |
float |
Decimal number |
3.14, -0.5, 1.0
|
f64 |
bool |
Boolean flag |
true, false
|
bool |
path |
File/directory path |
"./file.txt", "/tmp"
|
PathBuf |
string:
- Accepts any text
- No parsing required
- Example:
name: "John Doe"
integer:
- Must be valid integer
- Supports negative values
- Range:
-9,223,372,036,854,775,808to9,223,372,036,854,775,807 - Example:
count: 42
float:
- Must be valid decimal number
- Supports scientific notation
- Example:
value: 3.14orvalue: 1.5e-3
bool:
- For flags: presence =
true, absence =false - For values:
"true","false","yes","no","1","0" - Case-insensitive
- Example:
--verbose(true) vs no flag (false)
path:
- File or directory path
- Can be relative or absolute
- Validation can check existence
- Example:
file: "./data/input.csv"
Constraints applied to arguments and options.
validation:
- must_exist: trueApplies to: path type
Effect: Checks if file/directory exists before execution
Error if: Path does not exist
validation:
- extensions: [".txt", ".csv", ".json"]Applies to: path type (files only)
Effect: Validates file has one of the specified extensions
Error if: File extension not in list
validation:
- min: 1
max: 100Applies to: integer, float types
Effect: Value must be within range (inclusive)
Fields:
-
min(optional): Minimum value -
max(optional): Maximum value - Both can be omitted for one-sided bounds
Examples:
# Only minimum
- min: 0
# Only maximum
- max: 100
# Both
- min: 1
max: 10Multiple rules can be applied to a single argument:
validation:
- must_exist: true
- extensions: [".yaml", ".json"]Effect: File must exist AND have .yaml or .json extension
Use empty array if no validation needed:
validation: []Full configuration file for a data processing application.
# ═══════════════════════════════════════════════════════════
# Data Processor CLI Configuration
# ═══════════════════════════════════════════════════════════
metadata:
version: "1.0.0"
prompt: "dataproc"
prompt_suffix: " > "
# ───────────────────────────────────────────────────────────
# GLOBAL OPTIONS (available for all commands)
# ───────────────────────────────────────────────────────────
global_options:
- name: "verbose"
short: "v"
long: "verbose"
option_type: "bool"
required: false
default: "false"
description: "Enable verbose output"
- name: "output-dir"
short: "o"
long: "output"
option_type: "path"
required: false
description: "Output directory for results"
- name: "format"
short: "f"
long: "format"
option_type: "string"
required: false
default: "text"
description: "Output format"
choices: ["text", "json", "yaml"]
# ───────────────────────────────────────────────────────────
# COMMANDS
# ───────────────────────────────────────────────────────────
commands:
# ═════════════════════════════════════════════════════════
# Command: load
# ═════════════════════════════════════════════════════════
- name: "load"
aliases: ["open", "input"]
description: "Load a data file"
required: true
arguments:
- name: "file"
arg_type: "path"
required: true
description: "Path to data file"
validation:
- must_exist: true
- extensions: [".csv", ".json", ".yaml"]
options: []
implementation: "load_data"
# ═════════════════════════════════════════════════════════
# Command: process
# ═════════════════════════════════════════════════════════
- name: "process"
aliases: ["run"]
description: "Process loaded data"
required: false
arguments: []
options:
- name: "threads"
short: "t"
long: "threads"
option_type: "integer"
required: false
default: "4"
description: "Number of parallel threads"
choices: []
- name: "batch-size"
short: "b"
long: "batch"
option_type: "integer"
required: false
default: "1000"
description: "Batch size for processing"
choices: []
implementation: "process_data"
# ═════════════════════════════════════════════════════════
# Command: export
# ═════════════════════════════════════════════════════════
- name: "export"
aliases: ["save"]
description: "Export results to file"
required: false
arguments:
- name: "output-file"
arg_type: "path"
required: true
description: "Output file path"
validation: []
options:
- name: "compress"
short: "c"
long: "compress"
option_type: "bool"
required: false
default: "false"
description: "Compress output file"
choices: []
implementation: "export_results"
# ═════════════════════════════════════════════════════════
# Command: help
# ═════════════════════════════════════════════════════════
- name: "help"
aliases: ["h", "?"]
description: "Show help information"
required: false
arguments:
- name: "command"
arg_type: "string"
required: false
description: "Command to get help for"
validation: []
options: []
implementation: "show_help"
# ═════════════════════════════════════════════════════════
# Command: exit
# ═════════════════════════════════════════════════════════
- name: "exit"
aliases: ["quit", "q"]
description: "Exit the application"
required: false
arguments: []
options: []
implementation: "exit_app"CLI Mode:
# With global options
dataproc --verbose load data.csv
dataproc -v -o ./results process --threads 8
dataproc --format json export output.json
# Without global options
dataproc load data.csv
dataproc process --threads 8 --batch 2000
dataproc export output.csv --compressREPL Mode:
dataproc > load data.csv
Data loaded successfully.
dataproc > process --threads 8
Processing... Done.
dataproc > export results.csv
Results exported to results.csv
dataproc > help export
Command: export
Description: Export results to file
...
dataproc > exit
Goodbye!
Commands:
- Use lowercase
- Use hyphens for multi-word:
run-simulation(notrunSimulationorrun_simulation) - Keep names concise but descriptive
- Provide meaningful aliases:
["quit", "q", "exit"]
Arguments/Options:
- Use lowercase with hyphens:
output-dir(notoutputDir) - Be consistent across the application
- Use short forms wisely: single character, commonly recognized (
-vfor verbose)
Mark as required: true when:
- Command MUST be executed for application workflow
- Argument MUST be provided for command to work
- Option is essential (rare - consider if it should be an argument instead)
Mark as required: false when:
- Command is optional convenience feature
- Argument has sensible default behavior
- Option enhances but doesn't block functionality
Always validate when:
- Accepting file paths (check existence, extensions)
- Accepting numeric values with constraints (use ranges)
- Accepting enums (use
choices)
Example:
# Good: Comprehensive validation
validation:
- must_exist: true
- extensions: [".csv", ".json"]# Bad: No validation for file input
validation: []Write clear, concise descriptions:
- ✅ Good: "Load a configuration file"
- ❌ Bad: "Loads stuff"
- ✅ Good: "Number of parallel threads (1-16)"
- ❌ Bad: "threads"
Provide sensible defaults for optional fields:
# Good: Sensible default
- name: "threads"
default: "4" # Bad: Required but should have default
- name: "threads"
required: true # User must always specifyUse global_options for:
- Verbose/debug flags
- Output format selection
- Configuration file paths
- Logging levels
Use command options for:
- Command-specific behavior
- Parameters that only make sense for one command
Provide helpful aliases:
# Good: Common shortcuts
aliases: ["quit", "q", "exit"]# Good: Alternative names
aliases: ["load", "open", "input"]# Avoid: Confusing or too many
aliases: ["a", "b", "c", "xyz123"]Use clear, consistent naming:
# Good: Clear purpose
implementation: "load_config"implementation: "run_simulation"# Bad: Vague
implementation: "handler1"implementation: "do_stuff"For large applications, consider:
- One configuration file per mode (cli.yaml, repl.yaml)
- Shared commands in separate included file
- Environment-specific overrides (dev.yaml, prod.yaml)
Comment complex sections:
# ═══════════════════════════════════════════════════════════
# SIMULATION COMMANDS
# These commands control the numerical simulation engine
# ═══════════════════════════════════════════════════════════
commands:
- name: "simulate"
# Advanced users can override time step
options:
- name: "dt"
description: "Time step in seconds (experts only)"The complete example above in JSON format:
{
"metadata": {
"version": "1.0.0",
"prompt": "dataproc",
"prompt_suffix": " > "
},
"global_options": [
{
"name": "verbose",
"short": "v",
"long": "verbose",
"option_type": "bool",
"required": false,
"default": "false",
"description": "Enable verbose output"
}
],
"commands": [
{
"name": "load",
"aliases": ["open", "input"],
"description": "Load a data file",
"required": true,
"arguments": [
{
"name": "file",
"arg_type": "path",
"required": true,
"description": "Path to data file",
"validation": [
{"must_exist": true},
{"extensions": [".csv", ".json"]}
]
}
],
"options": [],
"implementation": "load_data"
}
]
}This reference covers all syntax elements for dynamic-cli configuration files:
✅ Metadata - Application information
✅ Global Options - Options for all commands
✅ Commands - Individual command definitions
✅ Arguments - Positional parameters
✅ Options - Named flags and parameters
✅ Types - Data type specifications
✅ Validation - Constraint rules
✅ Examples - Real-world usage
✅ Best Practices - Recommendations
For implementation details, see the dynamic-cli API documentation.
Version: 1.0
Framework: dynamic-cli
Date: 2026-01-11