Lifecycle
How a CLI invocation flows through maltty, from process.argv to process exit.
Phases
Every invocation passes through five phases: parse, validate, bootstrap, execute, and exit.
1. Parse and validate
Yargs parses process.argv, matches a registered command, strips internal keys, and validates args against the command's Zod schema. Validation failures exit with code 1.
2. Bootstrap
The config client discovers and validates the config file. createContext() assembles the context with args, config, meta, format, store, colors, log, prompts, and spinner.
3. Execute
The runner executes the middleware onion (described below), then exits with code 0 on success or the error's exit code on failure.
Middleware Onion
Middleware follows a nested onion model. Root middleware wraps command middleware, which wraps the handler:
Each middleware calls next() to pass control inward. Code before next() runs on the way in; code after next() runs on the way out.
Root middleware
Declared on cli(). Runs for every command. Use it for cross-cutting concerns.
Command middleware
Declared on command(). Runs only when that command is matched. Use it for command-specific setup.
Short-circuiting
A middleware that does not call next() prevents all inner layers from running. The outer middleware's post-next() code still executes.
Data flow
Middleware communicates with handlers and other middleware via ctx.store:
Error Handling
The CLI boundary catches all errors, logs the message, and calls process.exit with the appropriate code. Handlers never call process.exit directly.
See Context for the full ctx.fail() API and all other context properties.