Command-line API

eval_lcl retains value stdout; -o TREE selects the actual runtime tree. parse_lcl renders static inspection, and -o EVAL captures the actual runtime tree including ordinary failures. See text diagnostics for cache leaves, inline error reasons and logger.traceback_style.

A console stream supplied through CliConfig.log_config is borrowed from the application. Invocation cleanup does not close that stream, so accepted log records can drain before the logger scope ends. File or override definitions still take precedence over framework defaults.

Workflow variable defaults occupy a separate lowest-priority binding environment. Command.default_bindings retains these declarations without evaluating them; each invocation owns fresh factory caches. Existing entrance, preset, parameter, configuration, and CLI precedence is unchanged. Help marks defaulted variables optional, safely renders fixed values (including None), and displays factories as <factory>. A dataclass field fallback also makes a workflow input optional when all of its unresolved uses have a field fallback. These field fallbacks apply only to their mapping locations and do not create LCL bindings.

lclang.cli is a typed async framework for Python scripts whose parameters may come from declared defaults, .lclcfg files, and command-line overrides. A decorated command handler receives one CliContext, including its invocation Frame, as-of date, dry-run flag, and logging context, and returns a deterministic CliResult.

CliEntrance(group, lcl_mixin={"service": service}) supplies shared host values and callables to every command routed through the entrance, including commands inside nested groups and workflows converted with to_cli(). The entrance copies the mapping shallowly at definition time; the referenced objects remain shared. Binding names are validated and trailing ! markers retain masking.

Each invocation combines these defaults with the selected command's host preset before config expressions and logger settings are evaluated. Precedence is entrance lcl_mixin, command/workflow preset, parameter defaults, config, then CLI overrides, with later layers winning. Workflow lcl_mixin is part of its command preset and therefore wins over entrance defaults. Host helpers do not automatically become CLI parameters. Dynamic using targets keep the loader's existing scope and do not receive host presets.

Original command definitions remain unchanged. Invocations own separate Frames and caches, while supplied objects retain their ordinary shared Python state. Running a command directly with command.run() does not apply entrance bindings.

The CLI resolves logging from the same final Frame as application configuration. CliConfig.log_config supplies a LoggerHandlerConfig declaration; .lclcfg settings and -o overrides replace the corresponding logger.* leaves. logger.console controls the console and logger.file.<sink> declares named files. logger.file.default supplies missing fields without creating a sink. Explicit sink values always win over this template, regardless of which source supplied the template. Thus a CLI default.enabled=False preserves a configured sink's explicit enabled=True; override that sink directly to close it.

The logger scope starts after configuration is resolved and remains active through command execution, workflow resources, and Frame cleanup. Preparation errors go directly to stderr. Normal output drain completes before returning. Overlapping CLI entry invocations are rejected because root logging is global. All diagnostic console records use stderr; successful command results retain stdout. The shared format uses local time with microseconds and a numeric offset by default; -o logger.timezone utc selects UTC with a Z suffix. The timezone setting also applies under --verbose. Verbose records display TRACE while remaining DEBUG records. Their default prefix contains time, pid and thread; other records retain source name, file/line and function. Explicit formats remain effective. Each permanent file segment begins with its absolute path. Execution audit records contain the banner, exact redacted argv and lazy winning configuration without fixed initial positions.

--verbose enables runtime DEBUG diagnostics and lowers global and enabled sink thresholds to DEBUG, retaining lower existing thresholds. Disabled sinks stay disabled and source filters remain effective. Preparation records are not replayed. Value rendering remains bounded and respects trailing-bang masks. -v/--version retains its existing meaning.

Verbose records describe successful evaluations, input sources, cached values and shared results. A first failure includes a short reason, the actual execution phase and the request/owner context. Root requests summarize propagation and committed failure caches once. Cached failures reference the same failure identifier. The final exception diagnostic shows that identifier and retains complete source and value snapshots.

Each new evaluation has one completion event; its returned value is not logged again as another new-evaluation read. Successful roots add one dependency tree. Dynamic using targets emit one resolution event with the target path, declaration position and evaluation ID. Group reports share identical call prefixes and keep member order. Python callables include their implementation location when known.

Command execution and resource cleanup produce one final exception report on stderr. File reports retain application and external-library Python stacks and native causes by default; -o logger.traceback_style full includes lclang frames. The traceback portion uses concise references for LCL exceptions whose full diagnostic was already shown. A later output failure is reported separately and references an earlier command failure when present. Successful command output remains stdout.

Logger expressions can use __as_of_date__, __dryrun__, __verbose__, __ymd__, __execution_timestamp__, __command__, and __cli_params__. The date and command metadata retain the existing runtime-key semantics. Ordinary CLI values are strings; numeric, Boolean and collection overrides use LCL[...]. No logger-specific coercion is added. Unknown logger keys are errors, including in Workflow commands where valid logger keys bypass business-input validation. See the logger reference for the complete sink contract.

Built-in commands use this same routing model. Exact valueless -o FORCE makes a marked RESULT=LCL[...] parse strictly; ordinary override parsing remains permissive. Invalid marked logger overrides fail when their effective field is validated.

Dry-run remains an explicit handler decision, so the framework never pretends to know whether an application-specific side effect is safe.

Parameter help

Command help groups documented parameters by their full parent scope, with unqualified names in a first (global) block. Scopes and rows sort alphabetically (ignoring case), and rows retain full names for copying into -o overrides. Type annotations omit the collections.abc. prefix, including nested types.

Static defaults make an input optional and appear as default=.... Help uses the same precedence as execution: entrance bindings, command/workflow presets, then declared parameter defaults. A preset entry containing None, False, zero, or an empty container is still a default. ParameterDoc.default=None retains its existing meaning of no declared default. Defaults use bounded, single-line representations; masked values appear as *masked*.

Help never loads the selected -c file, evaluates configuration expressions, initializes logging, or runs the command. Extra host helpers are not parameters.

Trusted command discovery

lclang.cli.scan_commands(module, name, description) imports the developer's own trusted Python package and submodules in deterministic order. It collects public module-level Commands, deduplicates re-exports by identity and rejects distinct commands sharing a name. Importing developer-owned code is the intended extension mechanism; this API is not a scanner for hostile or untrusted modules.

See the CLI tutorial for a progressive application and workflow reference for conversion.

See errors and diagnostic codes for exception fields, specific codes, cause chains, and executable troubleshooting examples. Ordinary library validation uses LclValidationError; native callback and operation failures retain their original exception in __cause__.

The exception guide describes each failure family, grouped execution and cleanup failures, actual diagnostic output, and application handling with except* and native control signals.

Native controls, including iterator termination and groups that contain it, propagate after owned Frames close. Earlier ordinary failures remain in their cause chain. Logger configuration or startup failures also retain invocation cleanup failures in a combined diagnostic rather than hiding them during final cleanup.