# Table of Contents * [NAME](#name) * [SYNOPSIS](#synopsis) * [DESCRIPTION](#description) * [VERSION](#version) * [FEATURES](#features) * [MODULINOS](#modulinos) * [Why Modulinos?](#why-modulinos) * [The Bash Wrapper](#the-bash-wrapper) * [create-modulino](#create-modulino) * [MODULINO\_WRAPPER](#modulino\wrapper) * [QUICK START](#quick-start) * [Single-Module Application](#single-module-application) * [Role-Based Application](#role-based-application) * [ROLE-BASED ARCHITECTURE](#role-based-architecture) * [The YAML Manifest](#the-yaml-manifest) * [Command Values](#command-values) * [`roles:` vs `commands:`](#roles-vs-commands) * [When to Use Each Approach](#when-to-use-each-approach) * [Sharing Methods Between Commands](#sharing-methods-between-commands) * [Roles With No Commands](#roles-with-no-commands) * [Activating Role-Based Architecture](#activating-role-based-architecture) * [The Inherited main()](#the-inherited-main) * [Distributing the Manifest](#distributing-the-manifest) * [PHILOSOPHY AND DESIGN PRINCIPLES](#philosophy-and-design-principles) * [Not a Framework](#not-a-framework) * [Validation, Defaults, and Configuration](#validation-defaults-and-configuration) * [When to Use](#when-to-use) * [The init-run Lifecycle](#the-init-run-lifecycle) * [Opt-in Default Command](#opt-in-default-command) * [`$AUTO_HELP` and `$AUTO_DEFAULT`](#$autohelp-and-$autodefault) * [CONSTANTS](#constants) * [ADDING USAGE TO YOUR SCRIPTS](#adding-usage-to-your-scripts) * [Customizing Help Output](#customizing-help-output) * [Custom help() Method](#custom-help-method) * [`help_sections`](#helpsections) * [INTERNAL COMMANDS](#internal-commands) * [-generate-completion](#-generate-completion) * [-dump-spec](#-dump-spec) * [-scaffold](#-scaffold) * [-migrate](#-migrate) * [METHODS AND SUBROUTINES](#methods-and-subroutines) * [new](#new) * [command](#command) * [command\_args](#command\args) * [commands](#commands) * [main](#main) * [run](#run) * [get\_args](#get\args) * [With names](#with-names) * [With no names](#with-no-names) * [init](#init) * [USING PACKAGE VARIABLES](#using-package-variables) * [COMMAND LINE OPTIONS](#command-line-options) * [Getopt::Long Configuration](#getoptlong-configuration) * [COMMAND ARGUMENTS](#command-arguments) * [CUSTOM ERROR HANDLER](#custom-error-handler) * [SETTING DEFAULT VALUES FOR OPTIONS](#setting-default-values-for-options) * [ADDING ADDITIONAL ACCESSORS](#adding-additional-accessors) * [LOGGING](#logging) * [Colored Output](#colored-output) * [Per Command Log Levels](#per-command-log-levels) * [FAQ](#faq) * [ALIASING OPTIONS AND COMMANDS](#aliasing-options-and-commands) * [How option aliases work](#how-option-aliases-work) * [How command aliases work](#how-command-aliases-work) * [Usage examples](#usage-examples) * [Recommendations](#recommendations) * [ERRORS/EXIT CODES](#errorsexit-codes) * [Exit Codes](#exit-codes) * [LICENSE AND COPYRIGHT](#license-and-copyright) * [SEE ALSO](#see-also) * [AUTHOR](#author) # NAME CLI::Simple - a minimalist object oriented base class for CLI applications # SYNOPSIS #!/usr/bin/env perl package MyScript; use strict; use warnings; use CLI::Simple::Constants qw(:booleans :chars); use CLI::Simple qw($AUTO_HELP $AUTO_DEFAULT); use parent qw(CLI::Simple); caller or exit __PACKAGE__->main(); sub execute { my ($self) = @_; # retrieve a CLI option my $file = $self->get_file; ... } sub list { my ($self) = @_ # retrieve a command argument my ($file) = $self->get_args(); ... } sub main { # Disable auto-default for single commands, enable auto-help $AUTO_DEFAULT = 0; $AUTO_HELP = 1; my $cli = MyScript->new( option_specs => [ qw( help format=s file=s) ], default_options => { format => 'json' }, # set some defaults extra_options => [ qw( content ) ], # non-option, setter/getter commands => { execute => \&execute, list => \&list, } alias => { options => { fmt => 'format' }, commands => { ls => 'list' } }, ); return $cli->run(); } 1; \# role-based CLI Application (2.0.0) \# create a YAML manifest `my-script.yml` in your project root: --- roles: frobnicate: My::Script::Role::Frobnicate list: My::Script::Role::List options: - help|h - verbose|v - output|o=s \# create a main module package My::Script; use CLI::Simple qw(:roles); use parent qw(CLI::Simple); our $VERSION = '1.0.0'; caller or exit __PACKAGE__->main; 1; \# create implementation roles package My::Script::Role::Frobnicate; use Role::Tiny; use CLI::Simple::Constants qw(:booleans); sub cmd_frobnicate { my ($self) = @_; ... return $SUCCESS; } 1; # DESCRIPTION [![CLI-Simple](https://github.com/rlauer6/CLI-Simple/actions/workflows/build.yml/badge.svg)](https://github.com/rlauer6/CLI-Simple/actions/workflows/build.yml) Tired of writing the same 'ol boilerplate code for command line scripts? Want a standard, simple way to create a Perl script that takes options and commands? `CLI::Simple` makes it easy to create scripts that take _options_, _commands_ and _arguments_. `CLI::Simple` is designed around the _modulino_ pattern - Perl modules that can be executed directly as scripts. See ["MODULINOS"](#modulinos). For common constant values (like `$TRUE`, `$DASH`, or `$SUCCESS`), see [CLI::Simple::Constants](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AConstants), which pairs naturally with this module. Version 2.0.0 introduces optional role-based architecture for applications that have outgrown a single module. Declare your commands and options in a YAML manifest, implement each command in a dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) role, and `CLI::Simple` handles composition, dispatch, and lifecycle automatically. Your main module shrinks to a single line: caller or exit __PACKAGE__->main; Not ready for a full refactor? Start smaller. The built-in `-dump-spec` command introspects your existing module and writes a YAML manifest that makes your configuration data-driven without moving a single line of implementation code. Adopt roles incrementally, one command at a time. When you are ready to scaffold a full role-based project, `-scaffold` generates role stubs, a slimmed main module, and inter-module dependencies from your manifest. Feed the resulting tarball to [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) and you have a complete, buildable CPAN distribution in one step. Version 2.3.0 adds selective role composition. Commands declared with the `roles` manifest key compose only the roles required by the selected command. The original `commands` form remains supported for backward compatibility and retains its original behavior of composing the complete set of command roles. # VERSION This documentation refers to version 2.3.1. # FEATURES - accept command line arguments ala [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong) - supports commands and command arguments - automatically add a logger - global or custom log levels per command - easily add usage notes - automatically create setter/getters for your script - low dependency profile - selective role composition through YAML command manifests - built-in scaffolding tools for migrating legacy scripts to roles - bash completion script generation for modulino wrappers - optional pager support for help output via [IO::Pager](https://metacpan.org/pod/IO%3A%3APager) - customizable help sections via `help_sections` # MODULINOS A _modulino_ is a Perl module that can also be run directly as a script. The term was coined by Brian D. Foy and the pattern is simple: caller or exit __PACKAGE__->main(); When the file is `require`d or `use`d by another module, `caller` returns the calling package and the expression short-circuits - `main()` is never called. When the file is executed directly by Perl, `caller` returns false and `main()` runs. The same file serves as both a reusable module and an executable script. `CLI::Simple` is designed around this pattern. Every `CLI::Simple` application is expected to be a modulino. The framework's lifecycle, internal commands, bash completion, and scaffolding tools all assume this dual-use design. ## Why Modulinos? The modulino pattern offers several advantages over a traditional script: - **Testable** - your script logic lives in a proper Perl module that can be `use`d in test files without executing `main()` - **Reusable** - other scripts and modules can `use` your modulino and call its methods directly - **Introspectable** - tools like `-dump-spec` and `-generate-completion` can load your modulino and inspect its live state without running it as a script - **Installable** - modulinos distribute cleanly as CPAN modules with full man page support via [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) ## The Bash Wrapper Perl modulinos are invoked via a thin bash wrapper script that locates the installed module file and passes all arguments through to Perl: #!/usr/bin/env bash #-*- mode: sh; -*- MODULINO_WRAPPER=my-script MODULE_NAME=My::Script MODULE_PATH=$(MODULE_PATH="${MODULE_NAME//:://}.pm" \ perl -M$MODULE_NAME -e 'print $INC{$ENV{MODULE_PATH}};') MODULINO_WRAPPER=$MODULINO_WRAPPER perl $MODULE_PATH "$@" The wrapper locates the installed `.pm` file via `%INC` and sets `MODULINO_WRAPPER` in the environment so `CLI::Simple` knows the name of the script the user actually typed. This is used by `-generate-completion` to name the bash completion function correctly and by [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) to create man page symlinks. ## create-modulino `CLI::Simple` ships with a `create-modulino` tool that generates the bash wrapper for any `CLI::Simple` modulino: # create wrapper using module name convention (My::Script -> my-script) create-modulino -m My::Script # install to a specific directory create-modulino -m My::Script -i /usr/local/bin # use a custom wrapper name create-modulino -m My::Script -a my-alias -i /usr/local/bin `create-modulino` is itself a modulino - an example of the pattern it creates. The bash wrapper template lives in its `__DATA__` section, keeping the tool entirely self-contained. If you are building a CPAN distribution, [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) integrates `create-modulino` into the `make modulino` target, generating and installing the wrapper as part of the build process. ## MODULINO\_WRAPPER The `MODULINO_WRAPPER` environment variable tells `CLI::Simple` the name of the wrapper script that invoked the modulino. It is set by the wrapper and used by: - `-generate-completion` - to name the bash completion function and `complete` target correctly - Man page symlinks via [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) - so `man my-script` resolves to the module's man page If `MODULINO_WRAPPER` is not set, `CLI::Simple` infers the script name from the module name by convention - `My::Script` becomes `my-script`. Set it explicitly when the wrapper name does not follow this convention. # QUICK START ## Single-Module Application The simplest way to use `CLI::Simple` is to subclass it and define your commands as methods in the same module: package My::Script; use strict; use warnings; use CLI::Simple::Constants qw(:booleans); use parent qw(CLI::Simple); caller or exit __PACKAGE__->main; sub cmd_frobnicate { my ($self) = @_; my $output = $self->get_output; ... return $SUCCESS; } sub main { __PACKAGE__->new( option_specs => [ qw( help|h verbose|v output|o=s ) ], commands => { frobnicate => \&cmd_frobnicate }, )->run; } 1; ## Role-Based Application For larger applications, declare your commands and options in a YAML manifest and implement each command in a dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) role. Your main module becomes a single declaration: package My::Script; use strict; use warnings; use CLI::Simple qw(:roles); use parent qw(CLI::Simple); our $VERSION = '1.0.0'; caller or exit __PACKAGE__->main; 1; **Naming convention:** The YAML manifest filename is derived from your module name - `My::Script` looks for `my-script.yml` in the distribution share directory. You must package the spec file with your distribution. The manifest maps commands to roles: --- roles: frobnicate: My::Script::Role::Frobnicate list: My::Script::Role::List options: - help|h - verbose|v - output|o=s Each role implements one or more commands: package My::Script::Role::Frobnicate; use Role::Tiny; use CLI::Simple::Constants qw(:booleans); sub cmd_frobnicate { my ($self) = @_; ... return $SUCCESS; } 1; To easily generate the directory structure, role stubs, and build files for this architecture, `CLI::Simple` provides a built-in `-scaffold` tool. See ["-scaffold"](#scaffold) for detailed instructions on generating a role-based project tarball from a monolithic script or a YAML manifest. For a comprehensive guide on transitioning your application, see ["ROLE-BASED ARCHITECTURE"](#role-based-architecture). # ROLE-BASED ARCHITECTURE `CLI::Simple` 2.0.0 introduced an optional role-based architecture for applications that have grown beyond a single module. Commands may be implemented in dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) roles and declared in a YAML manifest, allowing `CLI::Simple` to build the dispatch table and provide an inherited `main()` - potentially reducing your main module to a single declaration. Version 2.3.0 adds selective role composition through the `roles:` manifest key. When `roles:` is used, only the role or roles required by the selected command are composed. The original `commands:` manifest form remains supported for backward compatibility and retains its original behavior of composing the complete set of command roles. ## The YAML Manifest The manifest is a YAML file that declares your commands, options, and defaults. By convention the filename is derived from your module name: My::Script -> my-script.yml CPAN::Maker::Bootstrapper -> cpan-maker-bootstrapper.yml `CLI::Simple` locates the manifest via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir) using the distribution name derived from the module name. The manifest must be installed as part of the distribution - it cannot be loaded from an arbitrary location. _Security note: The manifest is loaded exclusively from the distribution share directory via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir). A manifest that was not installed as part of the distribution cannot be loaded. This provides the same security model as Perl module loading itself._ _Note: Version 2.3.0 introduces `roles:` for selective role composition. The original `commands:` form remains supported for backward compatibility. See ["roles: vs commands:"](#roles-vs-commands)._ A minimal manifest: --- roles: frobnicate: My::Script::Role::Frobnicate list: My::Script::Role::List options: - help|h - verbose|v - output|o=s A complete manifest with all supported keys: --- roles: frobnicate: My::Script::Role::Frobnicate list: My::Script::Role::List options: - help|h - verbose|v - output|o=s default_options: verbose: 0 extra_options: - dbh - config_data ## Command Values Entries beneath `roles:` map a command name to either a single role or a list of roles required by that command. For example: roles: frobnicate: My::Script::Role::Frobnicate publish: - My::Script::Role::Publish - My::Script::Role::Packages When `frobnicate` is selected, only `My::Script::Role::Frobnicate` is composed into the application. When `publish` is selected, both `My::Script::Role::Publish` and `My::Script::Role::Packages` are composed. The selected role set must provide the command method corresponding to the command name. Hyphens are converted to underscores when resolving the method name, so: code-review resolves to: cmd_code_review The original `commands:` form remains supported for backward compatibility. Its values may be role class names or method names and retain the pre-2.3.0 behavior described in ["roles: vs commands:"](#roles-vs-commands). ## `roles:` vs `commands:` Version 2.3.0 introduces `roles:` for selective role composition. New applications should use `roles:`. The original `commands:` manifest key remains supported for backward compatibility. With `commands:`, selecting a command causes all roles referenced by the manifest's command definitions to be composed into the application, regardless of which command is being executed. This makes methods from every composed role available throughout the application. However, it can also load modules that are not needed by the selected command, increasing startup time. For applications invoked repeatedly, such as utilities called from `make` recipes, this additional startup overhead can become significant. With `roles:`, only the roles associated with the selected command are composed. A command may require one role or several roles. Selective composition reduces unnecessary dependencies and makes the role requirements of each command explicit. ## When to Use Each Approach `CLI::Simple` supports several approaches to organizing command-line applications. The appropriate choice depends on the size of the application and how its commands share functionality. - Single-module application For small utilities with a limited number of commands, defining command handlers in a single module is often the simplest approach. Commands are registered directly through the `commands` constructor parameter. No YAML manifest or role composition is required. - Legacy `commands:` manifest Existing applications using a YAML manifest with `commands:` retain the original behavior of composing all command roles together. This approach may be appropriate for applications whose commands depend on methods supplied by other command roles. However, every role class referenced by the manifest's command definitions is composed regardless of which command is selected. - Selective `roles:` manifest For new or growing applications, `roles:` provides a more modular approach. Each command declares the roles it requires, and only those roles are composed when the command is selected. This is particularly useful when commands have different dependencies or when minimizing startup time is important. Applications can migrate from `commands:` to `roles:` incrementally, provided each migrated command declares the roles it requires. ## Sharing Methods Between Commands With legacy `commands:` manifests, all command roles are composed into the application. Methods provided by one command role are therefore available to other commands. With selective `roles:` composition, a command cannot assume that roles associated with other commands have been composed. Functionality shared by multiple commands can be placed in a separate role and included in each command's role list: roles: publish: - My::Script::Role::Publish - My::Script::Role::Common deploy: - My::Script::Role::Deploy - My::Script::Role::Common Alternatively, functionality required by every command can be composed directly into the main application class using [Role::Tiny::With](https://metacpan.org/pod/Role%3A%3ATiny%3A%3AWith). Shared functionality can also be implemented in ordinary Perl modules without using roles. See ["Roles With No Commands"](#roles-with-no-commands) for an example of composing an application-wide role. ## Roles With No Commands Some roles provide application-wide behavior rather than implementing a command. For example, a role may provide an `init()` method for startup validation or other functionality required by every command. Because these roles are not associated with a command beneath `roles:`, they must be composed explicitly into the main module: package My::Script; use CLI::Simple qw(:roles); use Role::Tiny::With; use parent qw(CLI::Simple); with 'My::Script::Role::Init'; caller or exit __PACKAGE__->main; 1; Roles composed this way are always available to the application, regardless of which command is selected. ## Activating Role-Based Architecture Add `:roles` to your `use CLI::Simple` statement: use CLI::Simple qw(:roles); This causes `CLI::Simple` to load the YAML manifest and retain its command, role, option, alias, and abbreviation metadata for the application. Role composition does not occur while the manifest is being loaded. When the application is started, `CLI::Simple` first resolves the selected command, including aliases and abbreviations. It then composes the role or roles required by that command. For commands declared beneath `roles:`, only the associated role set is composed. For legacy commands declared beneath `commands:`, the original all-role composition behavior is retained for backward compatibility. ## The Inherited main() When using `:roles`, your class inherits `main()` from `CLI::Simple`: caller or exit __PACKAGE__->main; The inherited `main()` uses the manifest metadata to resolve the requested command, composes the role or roles required for that command, constructs the application object, and calls `run()`. Aliases and command abbreviations are resolved before selective role composition, so they select the same role set as the canonical command. Override `main()` in your subclass only if you need application startup behavior that cannot be expressed through the manifest, `init()`, or explicitly composed application roles. ## Distributing the Manifest The YAML manifest is part of the application's runtime configuration and must be installed with the distribution. `CPAN::Maker` users can add it to `extra-files` in `buildspec.yml` so it is installed into the distribution's share directory: extra-files: - share: - my-script.yml During development the manifest is found via `%INC`. After installation it is found via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir). No code changes are required between the two environments. # PHILOSOPHY AND DESIGN PRINCIPLES `CLI::Simple` is intentionally minimalist. It provides just enough structure to build command-line tools with subcommands, option parsing, and help handling -- but without enforcing any particular framework or lifecycle. ## Not a Framework This module is not [App::Cmd](https://metacpan.org/pod/App%3A%3ACmd), [MooseX::Getopt](https://metacpan.org/pod/MooseX%3A%3AGetopt), or a full application toolkit. Instead, it offers: - An object-oriented base class with a clean `run()` dispatcher - Command-line parsing via `Getopt::Long` - Built-in logging via `Log::Log4perl` - Subclass hooks like `init()` for setup and validation - Optional role-based architecture via YAML manifest for larger applications The philosophy is: provide just enough infrastructure, then get out of your way. ## Validation, Defaults, and Configuration `CLI::Simple` does not impose a validation model. You may: - Use `Getopt::Long` option specifications for argument types and `default_options` to supply default values - Write your own validation logic in `init()` - Throw exceptions, emit usage, or exit early at any point The lifecycle is explicit and under your control. You decide how much structure you want to add on top of it. ## When to Use `CLI::Simple` is ideal for: - Internal tools and admin scripts - Bootstrapped CLIs where you don't want a framework - Users who want to subclass a clean, minimal interface - Applications that have grown beyond a single module and benefit from role-based command composition For interactive CLI handling or complex command trees, consider [App::Cmd](https://metacpan.org/pod/App%3A%3ACmd) or [CLI::Framework](https://metacpan.org/pod/CLI%3A%3AFramework). ## The init-run Lifecycle - **Phase 0: Manifest Loading** For role-based applications using `use CLI::Simple qw(:roles)`, the YAML manifest is loaded during `import` and its command, role, option, alias, and abbreviation metadata is retained for the application. Roles are not composed during manifest loading. The selected command is resolved later, during application startup. For commands declared beneath `roles:`, only the role or roles required by that command are composed. Legacy `commands:` manifests retain the original all-role composition behavior. Single-module applications skip this phase entirely. - **Phase 1: Internal Commands** Before anything else, `CLI::Simple` checks `@ARGV` for internal commands prefixed with `-`. If one is found it executes immediately and exits. See ["INTERNAL COMMANDS"](#internal-commands). - **Phase 2: Initialization (`new` =** `init`)> The constructor parses command-line arguments via `Getopt::Long`, creates accessors for all options, and calls your `init()` method. Inside `init()`, your application has full access to the parsed options and arguments. This phase is the ideal hook for all final setup tasks, such as: - Validating command-line arguments. - Loading configuration files based on a `--config` option. - Dynamically overriding the command (e.g, `$self->command('new_default')`). - Performing any setup required **before** a command is run. - **Phase 3: Execution (`run`)** Dispatches to the command method determined during initialization. ## Opt-in Default Command By design, `CLI::Simple` **does not impose a default command**. This provides total flexibility for the application author: - **You Can Set a Default:** If your application needs a default command, define a `default` entry in the `commands` hash passed to the constructor, or set the command during `init()` using `command()`. Alternatively, enable `$AUTO_HELP` to display help when no command is supplied. - **You Can Have No Default:** If you do **not** set a default, `run()` will simply do nothing and return cleanly if no command is provided on the command line. This "no default by default" behavior is what enables a powerful "setup-only" execution mode. A user can run your script _without_ specifying a command. This will: - 1. Run the entire `new()` / `init()` phase, performing all setup. - 2. Call `run()`, which will find no command and exit cleanly. This provides an ideal hook for applications that need to perform "on-demand initialization" (e.g., seeding a database, authenticating) by checking for a specific flag inside `init()`, without also triggering an unwanted command. In role-based applications using a YAML manifest, a `default` command that aliases another command should map to the sub name directly rather than a role class: commands: default: cmd_install install: My::Module::Role::Installer ## `$AUTO_HELP` and `$AUTO_DEFAULT` The following package variables control automatic command selection, help behavior, and output paging. By default, the framework provides no default command as explained in the sections above. Some scripters may want default behaviors that assume a command or provide usage if no command is provided. - `$AUTO_HELP` Set the package variable `$AUTO_HELP` to a true value if you want `CLI::Simple` to provide help when no command is provided. default: false - `$AUTO_DEFAULT` Set the package variable `$AUTO_DEFAULT` to a true value if you want `CLI::Simple` to automatically select a command if you have only 1 command defined and no command is provided on the command line. When true, it will prepend the single command name to the argument list, allowing any subsequent arguments to be correctly parsed as args for that command. default: false - `$PAGER` Set the package variable `$PAGER` to a true value to route help output through [IO::Pager](https://metacpan.org/pod/IO%3A%3APager) when `--help` is invoked. When enabled, `IO::Pager` selects an appropriate pager (`less`, `more`, etc.) based on the `PAGER` environment variable, falling back to a sensible default. Set to false to suppress pager use and write help directly to STDOUT. use CLI::Simple qw($PAGER); $PAGER = 0; # disable pager default: true Note: [IO::Pager](https://metacpan.org/pod/IO%3A%3APager) must be installed for pager support. If it is not available, help output is written directly to STDOUT regardless of the value of `$PAGER`. # CONSTANTS `CLI::Simple::Constants` provides a collection of exportable constants commonly used in command-line applications. These include: - Boolean flags like `$TRUE`, `$FALSE`, `$SUCCESS`, and `$FAILURE` - Common character tokens such as `$COLON`, `$DASH`, `$EQUALS_SIGN`, etc. - Log level names compatible with [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) To use them in your script: use CLI::Simple::Constants qw(:all); # ADDING USAGE TO YOUR SCRIPTS To provide built-in usage/help output, include a `=head1 SYNOPSIS` section in your script's POD: =head1 SYNOPSIS ``` usage: myscript [options] command args Options ------- --help, -h Display help ... ``` If the user supplies the command `help`, or the `--help` option, `CLI::Simple` displays the configured help sections using [Pod::Usage](https://metacpan.org/pod/Pod%3A%3AUsage). For backward compatibility, `USAGE` is also supported. If a `USAGE` section is present, it is used as the usage section. If no `USAGE` section is present, `SYNOPSIS` is used instead. When both `SYNOPSIS` and `USAGE` are present, `USAGE` is used by default. Applications that explicitly configure `help_sections` may select the desired section. ## Customizing Help Output ### Custom help() Method If you need full control over the help output, you can define a custom `help` method and assign it as a command: commands => { help => &help, ... }; This is useful if your module follows the modulino pattern and you want to present help information that differs from the embedded POD. ### `help_sections` By default `CLI::Simple` renders the following POD sections when present, subject to the usage section selection described above: SYNOPSIS DESCRIPTION/Commands DESCRIPTION/Options OPTIONS You can override the default selection by passing an array reference of section names during construction: my $cli = CLI::Simple->new( help_sections => [qw(SYNOPSIS COMMANDS OPTIONS)], ... ); When `help_sections` is supplied explicitly, `CLI::Simple` honors the list exactly as provided. For example, an application may request both `SYNOPSIS` and `USAGE`: help_sections => [qw(SYNOPSIS USAGE)] Section names follow [Pod::Usage](https://metacpan.org/pod/Pod%3A%3AUsage) conventions. Subsections are specified with a `/` separator; for example, `DESCRIPTION/Commands` renders only the `Commands` subsection under `DESCRIPTION`. # INTERNAL COMMANDS `CLI::Simple` reserves command names beginning with `-` for its own use. These commands are intercepted before option parsing begins and execute immediately, bypassing the normal lifecycle entirely. See ["The init-run Lifecycle"](#the-init-run-lifecycle). Internal commands are dispatched via the `%INTERNAL_COMMANDS` package variable: our %INTERNAL_COMMANDS = ( '-generate-completion' => \&_cmd_generate_completion, '-dump-spec' => \&_cmd_dump_spec, '-scaffold' => \&_cmd_scaffold, '-migrate' => \&_cmd_migrate, ); Subclasses can add their own internal commands by extending the hash before `new()` is called: our %INTERNAL_COMMANDS = ( %CLI::Simple::INTERNAL_COMMANDS, '-my-command' => \&_cmd_my_command, ); ## -generate-completion Generates a bash completion script for the script's commands and options, derived from the live object state. Bash completions are a feature that allows the shell to automatically finish commands, file paths, and options when you press the Tab key. my-script -generate-completion > \ ~/.local/share/bash-completion/completions/my-script After generating the bash completion script, source it in your current shell to test: source ~/.local/share/bash-completion/completions/my-script Test by typing your script name followed by a space and pressing Tab. You should see the available commands. To verify option completion, type your script name followed by a space and `--` and press Tab. To make completions permanent, most systems automatically source files placed in `~/.local/share/bash-completion/completions/` when `bash-completion` 2.x is installed. If your system does not pick them up automatically, add the following to your `~/.bashrc`: source ~/.local/share/bash-completion/completions/my-script Alternatively, place the generated file in the system-wide completion directory (requires root): my-script -generate-completion > \ /etc/bash_completion.d/my-script The script name is taken from the first argument if provided, then `MODULINO_WRAPPER` if set, then inferred from the module name. If the inferred name cannot be found in `PATH`, a warning is issued but the completion script is still generated. _Note: If you created the modulino with the supplied `create-modulino` tool `MODULINO_WRAPPER` is already set inside the bash script that invokes the modulino._ - Case 1: Your modulino wrapper and module name are aligned The modulino script `my-modulino` refers to My::Modulino my-modulino -generate-completion - Case 2: Your modulino wrapper was created using `create-modulino` The modulino script `my-alias` refers to My::Modulino. Although the wrapper name differs from the module name, `MODULINO_WRAPPER` is set by the generated bash wrapper. my-alias -generate-completion - Case 3: Your modulino is an alias not created by `create-modulino` Without `MODULINO_WRAPPER`, the generated completion script may use the path to the Perl module rather than the wrapper's command name. The `-generate-completion` script called by your custom wrapper most likely only resolves the program name as the path to your Perl module: path-to-modules/My/Module.pm ...in this case you need to supply the alias name or set `MODULINO_WRAPPER` in the environment. my-alias -generate-completion my-alias ## -dump-spec Introspects the running modulino and writes a YAML manifest to the current directory. The filename is derived from the module name by convention. my-script -dump-spec # sub names - baby step toward roles my-script -dump-spec roles # role class names - full commitment Without the `roles` argument, commands map to their existing sub names so the manifest can be used immediately without moving any code. With `roles`, commands map to derived role class names suitable for use with `-scaffold`. Alias commands - those whose coderef resolves to a sub name that does not match the command key - are always written as sub names regardless of mode. ## -scaffold Generates a role-based project tarball from the running modulino or from an explicit spec file. The `-scaffold` command can take a monolithic application or a YAML file like the one above and create the project hierarchy for a role based application. The command will create a tarball that contains role stubs, a slimmed main module with extracted POD (if your monolith contained any), a `project.mk` with inter-module dependencies, and the YAML manifest. If you've turned your monolith's package into a modulino: my-script -scaffold # introspect live module ...or use `cli-simple` if you have a .yml file. cli-simple -scaffold my-script.yml # scaffold from spec file The tarball will be named `my-script-roles.tar.gz` by convention (the lower case snake cased version of the class name). The name is used to infer the class name. If your filename is different than the classes you want to scaffold, you will need to edit the files. Extract the content to a directory and start editing. If you feed the tarball to [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) via the `import-scaffold` command you can produce a complete buildable CPAN distribution. ## -migrate Combines `-dump-spec roles` and `-scaffold` in a single step. my-script -migrate Writes the YAML manifest then generates the role-based tarball. Use this when you are ready for a full migration and do not need to inspect or edit the manifest first. If you want to review or adjust the manifest before scaffolding, run `-dump-spec` and `-scaffold` separately. # METHODS AND SUBROUTINES ## new new( args ) Instantiates a new `CLI::Simple` instance, parses options, optionally initializes logging, and makes options available via dynamically generated accessors. _Note: The `new()` constructor uses [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong)'s `GetOptions`, which directly modifies `@ARGV` by removing any recognized options. The remaining elements of `@ARGV` are treated as the command name and its arguments._ `args` is a hash or hash reference containing the following keys: - abbreviations A boolean that determines whether abbreviated command names are allowed. When true, the `run()` method will treat the provided command as a prefix and compare it to the keys in the command hash. If exactly one match is found, it will be used. If more than one match is found, or if no match is found, `run()` will throw an exception. This allows for convenient shorthand like: mytool disable-sched # expands to 'disable-scheduled-task' default: false - commands (required) A hash mapping command names to either a subroutine reference or an array reference. If an array reference is used, the first element must be a subroutine reference and the second should be a valid log level. (See ["Per Command Log Levels"](#per-command-log-levels).) Example: { send => \&send_message, receive => \&receive_message, list_messages => [ \&list_messages, 'error' ], } If your script does not use command names, you may set a `default` key to the subroutine or method to run: { default => \&main } If no default is provided, the behavior is controlled by the `$AUTO_DEFAULT` and `$AUTO_HELP` package variables. Setting `$AUTO_DEFAULT` to true when your `commands` hash contains only a single command, will cause that command to be run automatically when no command name is given on the command line. This allows you to treat the program like a single-command tool, where arguments can be passed directly without explicitly naming the command. - default\_options (optional) A hash reference providing default values for options. These values apply if the corresponding option is not given on the command line. - extra\_options (optional) An array reference of names for additional accessors you want to create, even if they are not part of `option_specs`. Example: extra_options => [ qw(foo bar baz) ] - option\_specs (optional) An array reference of option specifications, as accepted by [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong). These define the command-line options your program recognizes. - validate\_command By default, `CLI::Simple` validates the selected command against the registered commands. Set `validate_command` to a false value to disable this validation. Typically you might use this to allow a script to assume a default command and allow arguments. For example suppose you have a script `foo` with a command "get" with arguments: foo get something ...but want to allow users to also do: foo something To do this you should follow this recipe: sub init { my ($self) = @_; my @args = $self->get_args; if ( ! @args ) { $self->command_args($self->command()); # set the args to the command $self->command('get'); # set the command to your default } else { die "ERROR: unknown command\n" if !$self->commands->{$self->command}; # validate the command } ... return; } _NOTE: This only works if your commands have a deterministic number of arguments. For example you might always require at least 1 argument. If you have no arguments as in the above recipe you would assume command is the argument to your default command._ ## command command command(command) Gets or sets the command to execute. Usually this is the first argument on the command line after all options have been parsed. There are times when you might want to override the argument. You can pass a new command that will be executed when you call the `run()` method. ## command\_args my $args = $self->command_args(); Gets or sets the argument list. Similar to `get_args` when no arguments are passed except it returns an array reference. To replace or add to the argument list, pass an array or list. my $args = $self->command_args; $self->command_args(@{$args}, 'foo'); ## commands commands commands(command, handler) Returns the command dispatch hash supplied to the constructor. When called with a command name and handler, adds the command to the dispatch hash. `handler` must be a code reference. commands(foo => sub { return 'foo' }); ## main __PACKAGE__->main; For role-based applications, `main` is inherited from `CLI::Simple` and reads the YAML manifest loaded during `import`. It constructs the object with the manifest's options, default options, extra options, and dispatch table, then calls `run()`. In a role-based modulino, the entire `main` sub reduces to: caller or exit __PACKAGE__->main; For single-module applications, override `main` in your subclass as usual. ## run Executes the selected command using the parsed options and arguments. The `run` method dispatches control to the corresponding command subroutine. Command subroutines should return `0` for success and a non-zero value for failure. The return value is used as the script's exit status. ## get\_args Return the arguments that follow the command. get_args(NAME, ... ) # with names get_args() # raw positional args ### With names - In scalar context, returns a hash reference mapping each NAME to the corresponding positional argument. - In list context, returns a flat list of `(name =` value)> pairs. Example: sub send_message { my ($self) = @_; my %args = $self->get_args(qw(message email)); _send_message($args{message}, $args{email}); } When you call `get_args` with a list of names, values are assigned in order: the first name gets the first argument, the second name gets the second argument, and so on. If you only want specific positions, you may use `undef` as a placeholder: my %args = $self->get_args('message', undef, 'cc'); # skip argument 2 If there are fewer positional arguments than names, the remaining names are set to `undef`. Extra positional arguments (beyond the provided names) are ignored. ### With no names - In scalar context returns an array reference containing the command's positional arguments. - In list context returns a list containing the command's positional arguments. ## init If defined, `init()` is invoked during application initialization, after command-line options and arguments have been processed and before command dispatch. Use this method to perform application-specific initialization and validation. # USING PACKAGE VARIABLES Constructor arguments may also be defined using package variables. This provides a declarative alternative to passing configuration directly to `new()`. Package variable names correspond to constructor argument names, converted to uppercase. our $OPTION_SPECS = [ qw( help|h log-level=s|L debug|d ) ]; our $COMMANDS = { foo => \&foo, bar => \&bar, }; # COMMAND LINE OPTIONS Command-line options are defined using [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong)-style specifications. You pass these into the constructor via the `option_specs` parameter: my $cli = CLI::Simple->new( option_specs => [ qw( help|h foo-bar=s log-level=s ) ] ); Option values are accessible through automatically generated getter methods: $cli->get_foo(); $cli->get_log_level(); Option names that contain dashes (`-`) are automatically converted to snake\_case for the accessor methods. For example: option_specs => [ 'foo-bar=s' ] ...results in: $cli->get_foo_bar(); ## Getopt::Long Configuration `CLI::Simple` uses [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong) to parse command-line options, with the `no_ignore_case` configuration enabled. Consequently: - Option names are case-sensitive. - Automatic option abbreviation is enabled. An option name may be abbreviated to any unambiguous prefix. - Multiple option names may be declared using Getopt::Long's `|` syntax, such as `config|c=s`. - When multiple spellings of the same option are supplied, the last occurrence determines its value. All other Getopt::Long configuration settings retain their defaults. # COMMAND ARGUMENTS If your commands accept positional arguments, you can retrieve them using the `get_args` method. You may optionally provide a list of argument names, in which case the arguments will be returned as a hash (or hashref in scalar context) with named values. Example: sub send_message { my ($self) = @_; my %args = $self->get_args(qw(phone_number message)); send_sms_message($args{phone_number}, $args{message}); } If you call `get_args()` without any argument names, it simply returns all remaining arguments as a list: my ($phone_number, $message) = $self->get_args; _Note: When called with names, `get_args` returns a hash in list context and a hash reference in scalar context._ =head2 set\_args Resets the positional arguments. $self->set_args(qw(foo 1)); This method overrides the positional arguments originally passed to the script. You can achieve the same behavior by calling the `get_args` in scalar context and modifying the reference. my $args = $self->get_args; $args->[1] = '2'; Use this technique when you want to modify individual arguments without replacing the entire argument list. # CUSTOM ERROR HANDLER By default, `CLI::Simple` exits if `Getopt::Long::GetOptions` returns a false value, indicating an error while parsing options. - Set `$CLI::Simple::GETOPT_EXIT_ON_ERROR` to a false value. This disables automatic exiting and lets your program decide what to do after an option-parsing failure. - Provide an `error_handler` callback in the constructor. my $cli = CLI::Simple->new( commands => \%commands, default_options => \%default_options, extra_options => \@extra_options, option_specs => \@option_specs, abbreviations => $TRUE, error_handler => sub { my ($msg) = @_; print {*STDERR} $msg; return $TRUE; # continue processing }, ); The error handler is called with the error message from `GetOptions`. It must return a boolean: a true value allows processing to continue, while a false value causes `CLI::Simple` to exit immediately. # SETTING DEFAULT VALUES FOR OPTIONS To assign default values to your options, pass a hash reference as the `default_options` argument to the constructor. These values will be used unless explicitly overridden by the user on the command line. Example: my $cli = CLI::Simple->new( default_options => { foo => 'bar' }, option_specs => [ qw(foo=s bar=s) ], commands => { foo => \&foo, bar => \&bar, }, ); Defaulted options are accessible through their corresponding getter methods, just like options set via the command line. # ADDING ADDITIONAL ACCESSORS All command-line options are automatically available through getter methods named `get_*`. If you need to create additional accessors (getters and setters) for values that are not derived from the command line, use the `extra_options` parameter. This is useful for passing runtime configuration or computed values throughout your application. Example: my $cli = CLI::Simple->new( default_options => { foo => 'bar' }, option_specs => [ qw(foo=s bar=s) ], extra_options => [ qw(biz buz baz) ], commands => { foo => \&foo, bar => \&bar, }, ); This will generate `get_biz`, `set_biz`, `get_buz`, etc., for internal use. # LOGGING `CLI::Simple` integrates with [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) to provide structured logging for your scripts. `CLI::Simple` provides convenient initialization of [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) through `use_log4perl()`. __PACKAGE__->use_log4perl( level => 'info', config => $log4perl_config_string ); If you do not explicitly include a `log-level` option in your `option_specs`, CLI::Simple will automatically add one for you. Once enabled, you can access the logger instance via: my $logger = $self->get_logger; This logger supports the standard Log4perl methods like `info`, `debug`, `warn`, etc. _Note: Because it is opt-in, `CLI::Simple` does not itself depend on [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl). **If your application calls `use_log4perl`, it owns that dependency** and must declare it in its own `requires`/`cpanfile`. Static dependency scanners cannot see it -- the module is loaded dynamically inside the method call -- so you must add it by hand._ ## Colored Output Pass `color => 1` to `use_log4perl()` to default your script to colorized log output using a built-in appender, instead of supplying your own `config`: __PACKAGE__->use_log4perl( level => 'info', color => 1, ); Colorizing requires [Term::ANSIColor](https://metacpan.org/pod/Term%3A%3AANSIColor). If it isn't installed, `CLI::Simple` quietly falls back to uncolored output rather than failing - `color => 1` is a request, not a hard dependency. If you'd like the person running your script to be able to override that default from the command line, add `color!` to your `option_specs`: my @option_specs = qw( color! ... ); This gives you `--color` and `--no-color` for free. Whichever way `use_log4perl()` set the default, an explicit flag on the command line always wins; if neither `--color` nor `--no-color` is passed, your `use_log4perl()` setting is left alone. Declaring `color!` is therefore safe to add at any time - it only changes behavior for scripts whose users actually pass the flag. _Note: `color` and `config` are mutually exclusive - `use_log4perl()` dies if you pass both. `color` is specifically for using `CLI::Simple`'s own built-in colorized appender; if you need a custom config, write it to include coloring yourself rather than passing `color => 1`._ ## Per Command Log Levels Some commands may require more verbose logging than others. For example, certain commands might perform complex actions that benefit from detailed logs, while others are designed solely to produce clean, structured output. To assign a custom log level to a command, use an array reference as the value for that command in the commands hash passed to the constructor. The first two elements of the array reference are: - A code reference to the command subroutine - A log level string: one of 'trace', 'debug', 'info', 'warn', 'error', or 'fatal' Example: CLI::Simple->new( option_specs => [qw( help format=s )], default_options => { format => 'json' }, # set some defaults extra_options => [qw( content )], # non-option, setter/getter commands => { execute => \&execute, list => [ \&list, 'error' ], } )->run; _TIP: add other elements to the array for your command to process._ _Note: Per-command log levels are not currently supported in the YAML manifest. Define them programmatically by overriding `main()` if needed._ # FAQ - How do I execute startup code before my command runs? Implement an `init()` method in your class. The `new()` constructor will invoke this method before returning and before `run()` is executed. Your `init()` method will have access to all options and arguments. Logging will also be initialized, so you can use `get_logger()` to emit messages. - Do I need to implement commands? No. If your script performs a single operation, you can register a default command: commands => { default => \&main } - Must I subclass `CLI::Simple`? No. You can instantiate `CLI::Simple` directly and supply the `commands` and other configuration through the constructor. Subclassing is useful when you want to provide application-specific methods such as `init()`. - How do I turn my class into a script? Use the modulino pattern: create a class that checks whether it is being invoked directly: package MyScript; caller or exit __PACKAGE__->main(); sub main { ... } This lets the file be used as both a module and an executable script. - How do I migrate an existing script to role-based architecture? Run the built-in `-dump-spec` command to generate a YAML manifest from your existing script, then `-scaffold` to generate role stubs: my-script -dump-spec # generates my-script.yml my-script -scaffold # generates my-script-roles.tar.gz See ["ROLE-BASED ARCHITECTURE"](#role-based-architecture) for the full migration workflow. - How do I start a new role-based project from scratch? Write a YAML manifest and use the `cli-simple` wrapper to scaffold it: cli-simple -scaffold my-script.yml See ["ROLE-BASED ARCHITECTURE"](#role-based-architecture) for the manifest format. - How do I enable bash completion for my script? Your script must be invoked via a bash modulino wrapper with `MODULINO_WRAPPER` set. Then run: my-script -generate-completion > \ ~/.local/share/bash-completion/completions/my-script Wrappers generated by [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) set `MODULINO_WRAPPER` automatically. - How do I add my own internal commands? Add entries to `%INTERNAL_COMMANDS` before calling `new()`: our %INTERNAL_COMMANDS = ( %CLI::Simple::INTERNAL_COMMANDS, '-my-command' => \&_cmd_my_command, ); - My application dies with "use\_log4perl() requires Log::Log4perl..." Since not all scripts require logging, `Log::Log4perl` is an _optional dependency_ of `CLI::Simple`. If your application calls `use_log4perl()`, `Log::Log4perl` must be installed. # ALIASING OPTIONS AND COMMANDS `CLI::Simple` lets you define short, human-friendly aliases for both option names and command names. Use the `alias` parameter to `new():` my $app = CLI::Simple->new( option_specs => [ qw(config=s verbose!) ], commands => { list => \&list, execute => \&execute }, alias => { options => { cfg => 'config', v => 'verbose' }, commands => { ls => 'list' } }, ); ## How option aliases work - Spec tail is copied automatically You only name the canonical option in `option_specs`. For each alias, `CLI::Simple` finds the canonical option's spec tail (for example `=s`, `:i`, `!`, `+`) and appends it to the alias. In the example above, `cfg` behaves as if you had written `cfg=s`, and `v` behaves as if you had written `v!`. - Accessors are created for both names Accessors are generated from all option names (canonical and aliases), with '-' normalized to '\_'. In the example, both `get_config()` and `get_cfg()` are available. - Values are mirrored after parsing After option parsing and normalization, values are mirrored so either name can be used consistently. When both the canonical option and an alias are supplied the canonical name wins. - No duplicate injection If the alias already exists in `option_specs`, it will not be injected again; value mirroring still occurs. - Errors are explicit If an alias points at a canonical option that does not exist, `CLI::Simple` croaks with a clear error. - Case sensitivity `Getopt::Long` is used with `:config no_ignore_case`, so option names (and therefore aliases) are case sensitive by default. ## How command aliases work - Simple mapping Provide `alias =` { commands => { alias => canonical } }> to map an alias to an existing command. In the example, `ls` dispatches to the `list` command. - Applied before abbreviations Aliases are installed before command abbreviation resolution. If you enable abbreviations, they apply to the full set of command names, including any aliases. - Errors are explicit If an alias points at a command that does not exist, `CLI::Simple` croaks with a clear error. ## Usage examples # Using an option alias script.pl --cfg app.json execute # Using a command alias script.pl ls After parsing, both `get_config()` and `get_cfg()` will return the same value. If the user passes both `--config` and `--cfg`, the value from `--config` (the canonical version) is used. _Note: In role-based applications using a YAML manifest, command aliases are expressed by mapping the alias command directly to the target sub name rather than a role class. See ["ROLE-BASED ARCHITECTURE"](#role-based-architecture)._ ## Recommendations - Keep the canonical spec single-named Define a single canonical name in `option_specs` and add other spellings via `alias`. Avoid multi-name specs like `config|cfg=s`; use `alias` instead. - Document your precedence If you prefer the alias name to win when both are supplied, enforce that in your application or adjust the mirroring order. By default, the canonical name wins. # ERRORS/EXIT CODES The `run()` method dispatches the selected command and returns its exit status. Command handlers should return `0` for success or a non-zero value to indicate failure. exit CLI::Simple->new(commands => { foo => \&cmd_foo })->run(); ## Exit Codes `CLI::Simple` uses conventional exit codes so that calling scripts can distinguish between normal completion and error conditions. - '0' Successful completion of a command (`SUCCESS`). - '1' General usage error, `--help` display via `pod2usage`, an invalid command line (`FAILURE`) or option parsing errors. - Any other code A command handler may return an application-specific numeric exit code, which `run()` passes through to the caller. A handler that calls `exit()` terminates the process directly. # LICENSE AND COPYRIGHT This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See [https://dev.perl.org/licenses/](https://dev.perl.org/licenses/) for more information. # SEE ALSO [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong), [CLI::Simple::Constants](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AConstants), [CLI::Simple::Utils](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AUtils), [Pod::Usage](https://metacpan.org/pod/Pod%3A%3AUsage), [App::Cmd](https://metacpan.org/pod/App%3A%3ACmd), [CLI::Framework](https://metacpan.org/pod/CLI%3A%3AFramework), [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny), [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) # AUTHOR Rob Lauer -