{"id":49340997,"url":"https://github.com/rlauer6/cli-simple","last_synced_at":"2026-04-27T04:02:25.933Z","repository":{"id":207388227,"uuid":"719123146","full_name":"rlauer6/CLI-Simple","owner":"rlauer6","description":"Accelerator for creating command line scripts in Perl","archived":false,"fork":false,"pushed_at":"2026-04-26T12:49:36.000Z","size":209,"stargazers_count":0,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-26T13:23:30.041Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Makefile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rlauer6.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":"COPYRIGHT","agents":null,"dco":null,"cla":null}},"created_at":"2023-11-15T13:59:42.000Z","updated_at":"2026-04-26T12:49:41.000Z","dependencies_parsed_at":"2024-02-09T22:28:37.726Z","dependency_job_id":"c979e65d-91b2-4d80-8f81-fe2d31a8b206","html_url":"https://github.com/rlauer6/CLI-Simple","commit_stats":null,"previous_names":["rlauer6/perl-cli-simple","rlauer6/cli-simple"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/rlauer6/CLI-Simple","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rlauer6%2FCLI-Simple","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rlauer6%2FCLI-Simple/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rlauer6%2FCLI-Simple/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rlauer6%2FCLI-Simple/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rlauer6","download_url":"https://codeload.github.com/rlauer6/CLI-Simple/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rlauer6%2FCLI-Simple/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32321940,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-27T04:02:24.333Z","updated_at":"2026-04-27T04:02:25.923Z","avatar_url":"https://github.com/rlauer6.png","language":"Makefile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Table of Contents\n\n* [NAME](#name)\n* [SYNOPSIS](#synopsis)\n* [DESCRIPTION](#description)\n* [VERSION](#version)\n* [FEATURES](#features)\n* [MODULINOS](#modulinos)\n  * [Why Modulinos?](#why-modulinos)\n  * [The Bash Wrapper](#the-bash-wrapper)\n  * [create-modulino](#create-modulino)\n  * [MODULINO\\_WRAPPER](#modulino\\wrapper)\n* [QUICK START](#quick-start)\n  * [Single-Module Application](#single-module-application)\n  * [Role-Based Application](#role-based-application)\n* [ROLE-BASED ARCHITECTURE](#role-based-architecture)\n  * [The YAML Manifest](#the-yaml-manifest)\n  * [Command Values](#command-values)\n  * [Roles With No Commands](#roles-with-no-commands)\n  * [Activating Role-Based Architecture](#activating-role-based-architecture)\n  * [The Inherited main()](#the-inherited-main)\n  * [Distributing the Manifest](#distributing-the-manifest)\n  * [Not a Framework](#not-a-framework)\n  * [Validation, Defaults, and Configuration](#validation-defaults-and-configuration)\n  * [When to Use](#when-to-use)\n  * [The init-run Lifecycle](#the-init-run-lifecycle)\n  * [\"opt-in\" Default Command](#\"opt-in\"-default-command)\n  * [`$AUTO_HELP` and `$AUTO_DEFAULT`](#$autohelp-and-$autodefault)\n* [CONSTANTS](#constants)\n* [ADDITIONAL NOTES](#additional-notes)\n* [INTERNAL COMMANDS](#internal-commands)\n  * [-generate-completion](#-generate-completion)\n  * [-dump-spec](#-dump-spec)\n  * [-scaffold](#-scaffold)\n  * [-migrate](#-migrate)\n* [METHODS AND SUBROUTINES](#methods-and-subroutines)\n  * [new](#new)\n  * [command](#command)\n  * [commands (required)](#commands-required)\n  * [main](#main)\n  * [run](#run)\n  * [get\\_args](#get\\args)\n    * [With names](#with-names)\n    * [With no names](#with-no-names)\n  * [init](#init)\n* [USING PACKAGE VARIABLES](#using-package-variables)\n* [COMMAND LINE OPTIONS](#command-line-options)\n  * [set\\_args](#set\\args)\n* [COMMAND ARGUMENTS](#command-arguments)\n* [CUSTOM ERROR HANDLER](#custom-error-handler)\n* [SETTING DEFAULT VALUES FOR OPTIONS](#setting-default-values-for-options)\n* [ADDING USAGE TO YOUR SCRIPTS](#adding-usage-to-your-scripts)\n  * [Custom help() Method](#custom-help-method)\n* [ADDING ADDITIONAL SETTERS](#adding-additional-setters)\n* [LOGGING](#logging)\n  * [Per Command Log Levels](#per-command-log-levels)\n* [FAQ](#faq)\n* [ALIASING OPTIONS AND COMMANDS](#aliasing-options-and-commands)\n  * [How option aliases work](#how-option-aliases-work)\n  * [How command aliases work](#how-command-aliases-work)\n  * [Usage examples](#usage-examples)\n  * [Recommendations](#recommendations)\n* [ERRORS/EXIT CODES](#errorsexit-codes)\n  * [Exit Codes](#exit-codes)\n* [EXAMPLE](#example)\n* [LICENSE AND COPYRIGHT](#license-and-copyright)\n* [SEE ALSO](#see-also)\n* [AUTHOR](#author)\n# NAME\n\nCLI::Simple - a minimalist object oriented base class for CLI applications\n\n# SYNOPSIS\n\n    #!/usr/bin/env perl\n\n    package MyScript;\n\n    use strict;\n    use warnings;\n\n    use CLI::Simple::Constants qw(:booleans :chars);\n    use CLI::Simple qw($AUTO_HELP $AUTO_DEFAULT);\n\n    use parent qw(CLI::Simple);\n\n    caller or __PACKAGE__-\u003emain();\n\n    sub execute {\n      my ($self) = @_;\n\n      # retrieve a CLI option   \n      my $file = $self-\u003eget_file;\n      ...\n    }\n\n    sub list { \n      my ($self) = @_\n\n      # retrieve a command argument\n      my ($file) = $self-\u003eget_args();\n      ...\n    }\n\n    sub main {\n\n      # Disable auto-default for single commands, enable auto-help\n      $AUTO_DEFAULT = 0;\n      $AUTO_HELP = 1;\n\n      my $cli = MyScript-\u003enew(\n       option_specs    =\u003e [ qw( help format=s file=s) ],\n       default_options =\u003e { format =\u003e 'json' }, # set some defaults\n       extra_options   =\u003e [ qw( content ) ], # non-option, setter/getter\n       commands        =\u003e { execute =\u003e \\\u0026execute, list =\u003e \\\u0026list,  }\n       alias           =\u003e { options =\u003e { fmt =\u003e 'format' }, commands =\u003e { ls =\u003e 'list' } },\n      );\n\n      return $cli-\u003erun();\n    }\n\n    1;\n\n\\# role-based CLI Application (2.0.0)\n\n\\# create a YAML manifest `my-script.yml` in your project root:\n\n    ---\n    commands:\n      frobnicate: My::Script::Role::Frobnicate\n      list:       My::Script::Role::List\n    options:\n      - help|h\n      - verbose|v\n      - output|o=s\n\n\\# create a main module\n\n    package My::Script;\n\n    use CLI::Simple qw(:roles);\n    use parent qw(CLI::Simple);\n\n    our $VERSION = '1.0.0';\n\n    caller or exit __PACKAGE__-\u003emain;\n\n    1;\n\n\\# create implementation roles\n\n    package My::Script::Role::Frobnicate;\n\n    use Role::Tiny;\n    use CLI::Simple::Constants qw(:booleans);\n\n    sub cmd_frobnicate {\n      my ($self) = @_;\n      ...\n      return $SUCCESS;\n    }\n\n    1;\n\n# DESCRIPTION\n\nTired of writing the same 'ol boilerplate code for command line\nscripts? Want a standard, simple way to create a Perl script that\ntakes options and commands?  `CLI::Simple` makes it easy to create\nscripts that take _options_, _commands_ and _arguments_.\n\n`CLI::Simple` is designed around the _modulino_ pattern - Perl\nmodules that can be executed directly as scripts. See [\"MODULINOS\"](#modulinos).\n\nFor common constant values (like `$TRUE`, `$DASH`, or `$SUCCESS`), see\n[CLI::Simple::Constants](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AConstants), which pairs naturally with this module.\n\nVersion 2.0.0 introduces optional role-based architecture for applications\nthat have outgrown a single module. Declare your commands and options in a\nYAML manifest, implement each command in a dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) role, and\n`CLI::Simple` handles composition, dispatch, and lifecycle automatically.\nYour main module shrinks to a single line:\n\n    caller or exit __PACKAGE__-\u003emain;\n\nNot ready for a full refactor? Start smaller. The built-in `-dump-spec`\ncommand introspects your existing module and writes a YAML manifest that\nmakes your configuration data-driven without moving a single line of\nimplementation code. Adopt roles incrementally, one command at a time.\n\nWhen you are ready to scaffold a full role-based project, `-scaffold`\ngenerates role stubs, a slimmed main module, and inter-module dependencies\nfrom your manifest. Feed the resulting tarball to\n[CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) and you have a complete, buildable CPAN\ndistribution in one step.\n\n# VERSION\n\nThis documentation refers to version 2.0.0.\n\n# FEATURES\n\n- accept command line arguments ala [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong)\n- supports commands and command arguments\n- automatically add a logger\n- global or custom log levels per command\n- easily add usage notes\n- automatically create setter/getters for your script\n- low dependency profile\n- optional role-based architecture via YAML manifest\n- built-in scaffolding tools for migrating legacy scripts to roles\n- bash completion script generation for modulino wrappers\n\n# MODULINOS\n\nA _modulino_ is a Perl module that can also be run directly as a\nscript. The term was coined by Brian D. Foy and the pattern is simple:\n\n    caller or __PACKAGE__-\u003emain();\n\nWhen the file is `require`d or `use`d by another module, `caller`\nreturns the calling package and the expression short-circuits -\n`main()` is never called. When the file is executed directly by Perl,\n`caller` returns false and `main()` runs. The same file serves as\nboth a reusable module and an executable script.\n\n`CLI::Simple` is designed around this pattern. Every `CLI::Simple`\napplication is expected to be a modulino. The framework's lifecycle,\ninternal commands, bash completion, and scaffolding tools all assume\nthis dual-use design.\n\n## Why Modulinos?\n\nThe modulino pattern offers several advantages over a traditional\nscript:\n\n- **Testable** - your script logic lives in a proper Perl module\nthat can be `use`d in test files without executing `main()`\n- **Reusable** - other scripts and modules can `use` your\nmodulino and call its methods directly\n- **Introspectable** - tools like `-dump-spec` and\n`-generate-completion` can load your modulino and inspect its live\nstate without running it as a script\n- **Installable** - modulinos distribute cleanly as CPAN modules\nwith full man page support via [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper)\n\n## The Bash Wrapper\n\nPerl modulinos are invoked via a thin bash wrapper script that locates\nthe installed module file and passes all arguments through to Perl:\n\n    #!/usr/bin/env bash\n    #-*- mode: sh; -*-\n\n    MODULINO_WRAPPER=my-script\n    MODULE_NAME=My::Script\n    MODULE_PATH=$(MODULE_PATH=\"${MODULE_NAME//:://}.pm\" \\\n      perl -M$MODULE_NAME -e 'print $INC{$ENV{MODULE_PATH}};')\n\n    MODULINO_WRAPPER=$MODULINO_WRAPPER perl $MODULE_PATH \"$@\"\n\nThe wrapper locates the installed `.pm` file via `%INC` and sets\n`MODULINO_WRAPPER` in the environment so `CLI::Simple` knows the\nname of the script the user actually typed. This is used by\n`-generate-completion` to name the bash completion function correctly\nand by [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) to create man page symlinks.\n\n## create-modulino\n\n`CLI::Simple` ships with a `create-modulino` tool that generates the\nbash wrapper for any `CLI::Simple` modulino:\n\n    # create wrapper using module name convention (My::Script -\u003e my-script)\n    create-modulino -m My::Script\n\n    # install to a specific directory\n    create-modulino -m My::Script -i /usr/local/bin\n\n    # use a custom wrapper name\n    create-modulino -m My::Script -a my-alias -i /usr/local/bin\n\n`create-modulino` is itself a modulino - an example of the pattern it\ncreates. The bash wrapper template lives in its `__DATA__` section,\nkeeping the tool entirely self-contained.\n\nIf you are building a CPAN distribution, [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper)\nintegrates `create-modulino` into the `make modulino` target,\ngenerating and installing the wrapper as part of the build process.\n\n## MODULINO\\_WRAPPER\n\nThe `MODULINO_WRAPPER` environment variable tells `CLI::Simple` the\nname of the wrapper script that invoked the modulino. It is set by the\nwrapper and used by:\n\n- `-generate-completion` - to name the bash completion function\nand `complete` target correctly\n- Man page symlinks via [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) - so\n`man my-script` resolves to the module's man page\n\nIf `MODULINO_WRAPPER` is not set, `CLI::Simple` infers the script\nname from the module name by convention - `My::Script` becomes\n`my-script`. Set it explicitly when the wrapper name does not follow\nthis convention.\n\n# QUICK START\n\n## Single-Module Application\n\nThe simplest way to use `CLI::Simple` is to subclass it and define\nyour commands as methods in the same module:\n\n    package My::Script;\n\n    use strict;\n    use warnings;\n\n    use CLI::Simple::Constants qw(:booleans);\n\n    use parent qw(CLI::Simple);\n\n    caller or __PACKAGE__-\u003emain;\n\n    sub cmd_frobnicate {\n      my ($self) = @_;\n      my $output = $self-\u003eget_output;\n      ...\n      return $SUCCESS;\n    }\n\n    sub main {\n      __PACKAGE__-\u003enew(\n        option_specs =\u003e [ qw( help|h verbose|v output|o=s ) ],\n        commands     =\u003e { frobnicate =\u003e \\\u0026cmd_frobnicate },\n      )-\u003erun;\n    }\n\n    1;\n\n## Role-Based Application\n\nFor larger applications, declare your commands and options in a YAML\nmanifest and implement each command in a dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) role.\nYour main module becomes a single declaration:\n\n    package My::Script;\n\n    use strict;\n    use warnings;\n\n    use CLI::Simple qw(:roles);\n    use parent qw(CLI::Simple);\n\n    our $VERSION = '1.0.0';\n\n    caller or exit __PACKAGE__-\u003emain;\n\n    1;\n\n**Naming convention:** The YAML manifest filename is derived from your\nmodule name - `My::Script` looks for `my-script.yml` in the\ndistribution share directory. You must package the spec file with your\ndistribution.\n\nThe manifest maps commands to roles:\n\n    ---\n    commands:\n      frobnicate: My::Script::Role::Frobnicate\n      list:       My::Script::Role::List\n    options:\n      - help|h\n      - verbose|v\n      - output|o=s\n\nEach role implements one or more commands:\n\n    package My::Script::Role::Frobnicate;\n\n    use Role::Tiny;\n    use CLI::Simple::Constants qw(:booleans);\n\n    sub cmd_frobnicate {\n      my ($self) = @_;\n      ...\n      return $SUCCESS;\n    }\n\n    1;\n\nTo scaffold role stubs from an existing modulino, run the built-in\n`-scaffold` command:\n\n    my-script -scaffold\n\nTo scaffold from an existing manifest - including a new one written by hand\nor generated by `-dump-spec` - pass the spec file path:\n\n    cli-simple -scaffold my-script.yml\n\nOr let `CLI::Simple` generate the manifest and scaffold from an\nexisting modulino in one step:\n\n    my-script -migrate\n\nSee [\"ROLE-BASED ARCHITECTURE\"](#role-based-architecture) for the complete workflow including\nthe baby-step migration path.\n\n# ROLE-BASED ARCHITECTURE\n\n`CLI::Simple` 2.0.0 introduces an optional role-based architecture\nfor applications that have grown beyond a single module. Commands are\nimplemented in dedicated [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny) roles and declared in a YAML\nmanifest. `CLI::Simple` composes the roles, builds the dispatch\ntable, and provides an inherited `main()` - potentially reducing your\nmain module to a single declaration.\n\n## The YAML Manifest\n\nThe manifest is a YAML file that declares your commands, options, and\ndefaults. By convention the filename is derived from your module name:\n\n    My::Script        -\u003e  my-script.yml\n    CPAN::Maker::Bootstrapper  -\u003e  cpan-maker-bootstrapper.yml\n\n`CLI::Simple` locates the manifest via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir) using the\ndistribution name derived from the module name. The manifest must be\ninstalled as part of the distribution - it cannot be loaded from an\narbitrary location.\n\n_Security note: The manifest is loaded exclusively from the\ndistribution share directory via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir). A manifest that\nwas not installed as part of the distribution cannot be loaded. This\nprovides the same security model as Perl module loading itself._\n\nA minimal manifest:\n\n    ---\n    commands:\n      frobnicate: My::Script::Role::Frobnicate\n      list:       My::Script::Role::List\n    options:\n      - help|h\n      - verbose|v\n      - output|o=s\n\nA complete manifest with all supported keys:\n\n    ---\n    commands:\n      frobnicate: My::Script::Role::Frobnicate\n      list:       My::Script::Role::List\n      default:    cmd_frobnicate\n    options:\n      - help|h\n      - verbose|v\n      - output|o=s\n    default_options:\n      verbose: 0\n    extra_options:\n      - dbh\n      - config_data\n\n## Command Values\n\nEach command in the manifest maps to either a role class name or a\nsub name:\n\n- **Role class name** (contains `::`) - the role is composed\ninto your main module and the method `cmd__command_` is resolved\nfrom the role. `code-review` resolves to `cmd_code_review`.\n- **Sub name** - resolved directly via `can()` on your class.\nUse this for alias commands that point to an existing method:\n\n        default: cmd_frobnicate\n\n## Roles With No Commands\n\nSome roles provide framework behavior rather than commands - for\nexample an `init()` method for startup validation. Since these roles\nhave no command entry in the manifest they must be composed manually\nin your main module:\n\n    package My::Script;\n\n    use CLI::Simple qw(:roles);\n    use Role::Tiny::With;\n    use parent qw(CLI::Simple);\n\n    with 'My::Script::Role::Init';\n\n    caller or exit __PACKAGE__-\u003emain;\n\n    1;\n\n_Note: A future version of `CLI::Simple` will support an\n`extra_roles` key in the manifest to handle this automatically._\n\n## Activating Role-Based Architecture\n\nAdd `:roles` to your `use CLI::Simple` statement:\n\n    use CLI::Simple qw(:roles);\n\nThis triggers manifest loading at compile time. The manifest is\nlocated using the fallback chain described above. Roles are composed\ninto your class and the dispatch table is built before `new()` is\ncalled.\n\n## The Inherited main()\n\nWhen using `:roles`, your class inherits `main()` from\n`CLI::Simple`. It reads the manifest, constructs the object with the\nmanifest's options and dispatch table, and calls `run()`:\n\n    caller or exit __PACKAGE__-\u003emain;\n\nOverride `main()` in your subclass only if you need to add behaviour\nthat cannot be expressed in the manifest or `init()`.\n\n## Distributing the Manifest\n\nAdd the manifest to your distribution's share\ndirectory. `CPAN::Maker` users can add it `extra-files` in\n`buildspec.yml` so it is installed into the share directory:\n\n    extra-files:\n      - share:\n        - my-script.yml\n\nDuring development the manifest is found via `%INC`. After\ninstallation it is found via [File::ShareDir](https://metacpan.org/pod/File%3A%3AShareDir). No code changes\nrequired between the two environments.\n\u0026#x3d;head1 PHILOSOPHY AND DESIGN PRINCIPLES\n\n`CLI::Simple` is intentionally minimalist. It provides just enough\nstructure to build command-line tools with subcommands, option\nparsing, and help handling -- but without enforcing any particular\nframework or lifecycle.\n\n## Not a Framework\n\nThis module is not [App::Cmd](https://metacpan.org/pod/App%3A%3ACmd), [MooseX::Getopt](https://metacpan.org/pod/MooseX%3A%3AGetopt), or a full\napplication toolkit.  Instead, it offers:\n\n- An object-oriented base class with a clean `run()` dispatcher\n- Command-line parsing via `Getopt::Long`\n- Built-in logging via `Log::Log4perl`\n- Subclass hooks like `init()` for setup and validation\n- Optional role-based architecture via YAML manifest for larger applications\n\nThe philosophy is: provide just enough infrastructure, then get out of your way.\n\n## Validation, Defaults, and Configuration\n\n`CLI::Simple` does not impose a validation model. You may:\n\n- Use `Getopt::Long` features (e.g., type constraints, default values)\n- Write your own validation logic in `init()`\n- Throw exceptions, emit usage, or exit early at any point\n\nThe lifecycle is explicit and under your control. You decide how much structure\nyou want to add on top of it.\n\n## When to Use\n\n`CLI::Simple` is ideal for:\n\n- Internal tools and admin scripts\n- Bootstrapped CLIs where you don't want a framework\n- Users who want to subclass a clean, minimal interface\n- Applications that have grown beyond a single module and benefit from\nrole-based command composition\n\nFor interactive CLI handling or complex command trees, consider\n[App::Cmd](https://metacpan.org/pod/App%3A%3ACmd) or [CLI::Framework](https://metacpan.org/pod/CLI%3A%3AFramework).\n\n## The init-run Lifecycle\n\n- **Phase 0: Internal Commands**\n\n    Before anything else, `CLI::Simple` checks `@ARGV` for internal\n    commands prefixed with `-`. If one is found it executes immediately\n    and exits. See [\"INTERNAL COMMANDS\"](#internal-commands).\n\n- **Phase 1: Manifest Loading**\n\n    For role-based applications using `use CLI::Simple qw(:roles)`, the\n    YAML manifest is loaded at compile time during `import`. Roles are\n    composed into the calling class and the dispatch table is built before\n    `new()` is ever called. Single-module applications skip this phase\n    entirely.\n\n- **Phase 2: Initialization (`new` =** `init`)\u003e\n\n    The constructor parses command-line arguments via `Getopt::Long`,\n    creates accessors for all options, and calls your `init()` method.\n    Inside `init()`, your application has full access to the parsed options \n    and arguments. This phase is the ideal hook for all final setup tasks, \n    such as:\n\n    - Validating command-line arguments.\n    - Loading configuration files based on a `--config` option.\n    - Dynamically overriding the command (e..g, `$self-\u003ecommand('new_default')`).\n    - Performing any setup required **before** a command is run.\n\n- **Phase 3: Execution (`run`)**\n\n    Dispatches to the command method determined during initialization.\n\n## \"opt-in\" Default Command\n\nBy design, `CLI::Simple` **does not impose a default command**.\nThis provides total flexibility for the application author:\n\n- **You Can Set a Default:** If your application needs a default\ncommand (e.g., to run `help` when no command is given), you can set\n`$AUTO_HELP`, explicitly set the `default` command in the `command`\nhash you pass to the constructor or use `command()` to set one\ninside the `init()` method.\n- **You Can Have No Default:** If you do **not** set a default,\n`run()` will simply do nothing and return cleanly if no command\nis provided on the command line.\n\nThis \"no default by default\" behavior is what enables a powerful \n\"setup-only\" execution mode. A user can run your script _without_\nspecifying a command. This will:\n\n- 1. Run the entire `new()` / `init()` phase, performing all setup.\n- 2. Call `run()`, which will find no command and exit cleanly.\n\nThis provides an ideal hook for applications that need to perform\n\"on-demand initialization\" (e.g., seeding a database, authenticating)\nby checking for a specific flag inside `init()`, without also\ntriggering an unwanted command.\n\nIn role-based applications using a YAML manifest, a `default` command\nthat aliases another command should map to the sub name directly rather\nthan a role class:\n\n    commands:\n      default: cmd_install\n      install: My::Module::Role::Installer\n\n## `$AUTO_HELP` and `$AUTO_DEFAULT`\n\nTwo package variables can be used to further control the lifecycle. By\ndefault, the framework provides no default command as explained in the\nsections above. Some scripters may want default behaviors that assume\na command or provide usage if no command is provided.\n\n- `$AUTO_HELP`\n\n    Set the package variable `$AUTO_HELP` to a true value if you want\n    `CLI::Simple` to provide help when no command is provided.\n\n    default: false\n\n- `$AUTO_DEFAULT`\n\n    Set the package variable `$AUTO_DEFAULT` to a true value if you want\n    `CLI::Simple` to automatically select a command if you have only 1\n    command defined and no command is provided on the command line. When\n    true, it will prepend the single command name to the argument list,\n    allowing any subsequent arguments to be correctly parsed as args for\n    that command.\n\n    default: false\n\n# CONSTANTS\n\n`CLI::Simple` does not define its own constants directly, but it is often used\nin conjunction with [CLI::Simple::Constants](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AConstants), which provides a collection of\nexportable values commonly needed in command-line scripts.\n\nThese include:\n\n- Boolean flags like `$TRUE`, `$FALSE`, `$SUCCESS`, and `$FAILURE`\n- Common character tokens such as `$COLON`, `$DASH`, `$EQUALS_SIGN`, etc.\n- Log level names compatible with [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl)\n\nTo use them in your script:\n\n    use CLI::Simple::Constants qw(:all);\n\n# ADDITIONAL NOTES\n\n- All options are case insensitive\n- See [CLI::Simple::Utils](https://metacpan.org/pod/CLI%3A%3ASimple%3A%3AUtils) to learn about additional utilities\nuseful when writing scripts, including `choose`, `slurp`, and `dmp`.\n- `%INTERNAL_COMMANDS` is a package variable - subclasses can\nadd their own internal commands by pushing entries into the hash before\ncalling `new()`.\n\n# INTERNAL COMMANDS\n\n`CLI::Simple` reserves command names beginning with `-` for its own\nuse. These commands are intercepted before option parsing begins and\nexecute immediately, bypassing the normal lifecycle entirely. See\n[\"The init-run Lifecycle\"](#the-init-run-lifecycle).\n\nInternal commands are dispatched via the `%INTERNAL_COMMANDS` package\nvariable:\n\n    our %INTERNAL_COMMANDS = (\n      '-generate-completion' =\u003e \\\u0026_cmd_generate_completion,\n      '-dump-spec'           =\u003e \\\u0026_cmd_dump_spec,\n      '-scaffold'            =\u003e \\\u0026_cmd_scaffold,\n      '-migrate'             =\u003e \\\u0026_cmd_migrate,\n    );\n\nSubclasses can add their own internal commands by extending the hash\nbefore `new()` is called:\n\n    our %INTERNAL_COMMANDS = (\n      %CLI::Simple::INTERNAL_COMMANDS,\n      '-my-command' =\u003e \\\u0026_cmd_my_command,\n    );\n\n## -generate-completion\n\nGenerates a bash completion script for the script's commands and\noptions, derived from the live object state. Bash completions are a\nfeature that allows the shell to automatically finish commands, file\npaths, and options when you press the Tab key.\n\n    my-script -generate-completion \u003e \\\n      ~/.local/share/bash-completion/completions/my-script\n\nAfter generating the bash completion script, source it in your current\nshell to test:\n\n    source ~/.local/share/bash-completion/completions/my-script\n\nTest by typing your script name followed by a space and pressing Tab.\nYou should see the available commands. To verify option completion,\ntype `--` and press Tab.\n\nTo make completions permanent, most systems automatically source files\nplaced in `~/.local/share/bash-completion/completions/` when\n`bash-completion` 2.x is installed. If your system does not pick\nthem up automatically, add the following to your `~/.bashrc`:\n\n    source ~/.local/share/bash-completion/completions/my-script\n\nAlternatively, place the generated file in the system-wide completion\ndirectory (requires root):\n\n    my-script -generate-completion \u003e \\\n      /etc/bash_completion.d/my-script\n\nThe script name is taken from the first argument if provided, then\n`MODULINO_WRAPPER` if set, then inferred from the module name. If the\ninferred name cannot be found in `PATH`, a warning is issued but the\ncompletion script is still generated.\n\n_Note: If you created the modulino with the supplied\n`create-modulino` tool `MODULINO_WRAPPER` is already set inside the\nbash script that invokes the modulino._\n\n- Case 1: Your modulino wrapper and module name are aligned \n\n    The modulino script `my-modulino` refers to My::Modulino\n\n        my-modulino -generate-completion\n\n- Case 2: Your modulino wrapper was created using `create-modulino`\n\n    The modulino script `my-alias` refers to My::Modulino. They are not\n    aligned however `MODULINO_WRAPPER` is set by the bash wrapper.\n\n        my-alias -generate-completion\n\n- Case 3: Your modulino is an alias not created by `create-modulino`\n\n    The script name `my-alias` is not aligned with your module name\n    `My::Module` and your modulino wrapper does not set\n    `MODULINO_WRAPPER`. The `-generate-completion` script called by \n    your custom wrapper most likely only resolves the program name as the path to\n    your Perl module:\n\n        path-to-modules/My/Module.pm\n\n    ...in this case you need to supply the alias name or set\n    `MODULINO_WRAPPER` in the environment.\n\n        my-alias -generate-completion my-alias\n\n## -dump-spec\n\nIntrospects the running modulino and writes a YAML manifest to the\ncurrent directory. The filename is derived from the module name by\nconvention.\n\n    my-script -dump-spec           # sub names - baby step toward roles\n    my-script -dump-spec roles     # role class names - full commitment\n\nWithout the `roles` argument, commands map to their existing sub\nnames so the manifest can be used immediately without moving any\ncode. With `roles`, commands map to derived role class names suitable\nfor use with `-scaffold`.\n\nAlias commands - those whose coderef resolves to a sub name that does\nnot match the command key - are always written as sub names regardless\nof mode.\n\n## -scaffold\n\nGenerates a role-based project tarball from the running modulino or\nfrom an explicit spec file. The tarball contains role stubs, a slimmed\nmain module with extracted POD, a `project.mk` with inter-module\ndependencies, and the YAML manifest.\n\n    my-script -scaffold                        # introspect live module\n    cli-simple -scaffold my-script.yml         # scaffold from spec file\n\nThe tarball is named `my-script-roles.tar.gz` by convention (the\nlower case snake cased version of the class name). The name is used to\ninfer the class name. If your filename is different than the\nclasses you want to scaffold, you will need to edit the files. \n\nFeed the tarball to [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) via the\n`import-scaffold` command to produce a complete buildable CPAN\ndistribution.\n\n## -migrate\n\nCombines `-dump-spec roles` and `-scaffold` in a single step.\n\n    my-script -migrate\n\nWrites the YAML manifest then generates the role-based tarball. Use\nthis when you are ready for a full migration and do not need to inspect\nor edit the manifest first. If you want to review or adjust the\nmanifest before scaffolding, run `-dump-spec` and `-scaffold`\nseparately.\n\n# METHODS AND SUBROUTINES\n\n## new\n\n    new( args )\n\nInstantiates a new `CLI::Simple` instance, parses options, optionally\ninitializes logging, and makes options available via dynamically\ngenerated accessors.\n\n_Note: The `new()` constructor uses [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong)'s `GetOptions`,\nwhich directly modifies `@ARGV` by removing any recognized\noptions. The remaining elements of `@ARGV` are treated as the command\nname and its arguments._\n\n`args` is a hash or hash reference containing the following keys:\n\n- abbreviations\n\n    A boolean that determines whether abbreviated command names are allowed.\n\n    When true, the `run()` method will treat the provided command as a prefix\n    and compare it to the keys in the command hash. If exactly one match is\n    found, it will be used. If more than one match is found, or if no match is\n    found, `run()` will throw an exception.\n\n    This allows for convenient shorthand like:\n\n        mytool disable-sched    # expands to 'disable-scheduled-task'\n\n    default: false\n\n- commands (required)\n\n    A hash mapping command names to either a subroutine reference or an\n    array reference.\n\n    If an array reference is used, the first element must be a subroutine\n    reference and the second should be a valid log level. (See\n    [\"Per Command Log Levels\"](#per-command-log-levels).)\n\n    Example:\n\n        {\n          send          =\u003e \\\u0026send_message,\n          receive       =\u003e \\\u0026receive_message,\n          list_messages =\u003e [ \\\u0026list_messages, 'error' ],\n        }\n\n    If your script does not use command names, you may set a `default` key\n    to the subroutine or method to run:\n\n        { default =\u003e \\\u0026main }\n\n    If no default is provided, the behavior is controlled by the\n    `$AUTO_DEFAULT` and `$AUTO_HELP` package variables.\n\n    Setting `$AUTO_DEFAULT` to true when your `commands` hash\n    contains only a single command, will cause that command to be run\n    automatically when no command name is given on the command line. This\n    allows you to treat the program like a single-command tool, where\n    arguments can be passed directly without explicitly naming the\n    command.\n\n- default\\_options (optional)\n\n    A hash reference providing default values for options. These values\n    apply if the corresponding option is not given on the command line.\n\n- extra\\_options (optional)\n\n    An array reference of names for additional accessors you want to create,\n    even if they are not part of `option_specs`.\n\n    Example:\n\n        extra_options =\u003e [ qw(foo bar baz) ]\n\n- option\\_specs (optional)\n\n    An array reference of option specifications, as accepted by\n    [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong). These define the command-line options your program\n    recognizes.\n\n## command\n\n    command\n    command(command)\n\nGet or sets the command to execute. Usually this is the first argument\non the command line after all options have been parsed. There are\ntimes when you might want to override the argument. You can pass a new\ncommand that will be executed when you call the `run()` method.\n\n## commands (required)\n\n    commands\n    commands(command, handler)\n\nReturns the hash you passed in the constructor as `commands` or can\nbe used to insert a new command into the `commands` hash. `handler`\nshould be a code reference.\n\n    commands(foo =\u003e sub { return 'foo' });\n\n## main\n\n    __PACKAGE__-\u003emain;\n\nFor role-based applications, `main` is inherited from `CLI::Simple`\nand reads the YAML manifest loaded during `import`. It constructs the\nobject with the manifest's options, default options, extra options, and\ndispatch table, then calls `run()`.\n\nIn a role-based modulino the entire `main` sub reduces to:\n\n    caller or exit __PACKAGE__-\u003emain;\n\nFor single-module applications, override `main` in your subclass as\nusual.\n\n## run\n\nExecute the script with the given options, commands and arguments. The\n`run` method interprets the command line and passes control to your\ncommand subroutines. Your subroutines should return a 0 for success\nand a non-zero value for failure.  This error code is passed to the\nshell as the script return code.\n\n## get\\_args\n\nReturn the arguments that follow the command.\n\n    get_args(NAME, ... )     # with names\n    get_args()               # raw positional args\n\n### With names\n\n- In scalar context, returns a hash reference mapping each NAME to\nthe corresponding positional argument.\n- In list context, returns a flat list of `(name =` value)\u003e pairs.\n\nExample:\n\n    sub send_message {\n      my ($self) = @_;\n\n      my %args = $self-\u003eget_args(qw(message email));\n\n      _send_message($args{message}, $args{email});\n    }\n\nWhen you call `get_args` with a list of names, values are assigned in\norder: the first name gets the first argument, the second name gets the\nsecond argument, and so on. If you only want specific positions, you may\nuse `undef` as a placeholder:\n\n    my %args = $self-\u003eget_args('message', undef, 'cc');  # args 1 and 3\n\nIf there are fewer positional arguments than names, the remaining names\nare set to `undef`. Extra positional arguments (beyond the provided\nnames) are ignored.\n\n### With no names\n\n- In scalar context returns an array reference containing the\ncommand's positional arguments.\n- In list context returns a list containing the command's\npositional arguments.\n\n## init\n\nIf you define your own `init()` method, it will be called by the\nconstructor. Use this method to perform any actions you require before\nyou execute the `run()` method.\n\n# USING PACKAGE VARIABLES\n\nYou can pass the necessary parameter required to implement your\ncommand line scripts in the constructor or some people prefer to see\nthem clearly defined in the code. Accordingly, you can use package\nvariables with the same name as the constructor arguments (in upper\ncase).\n\n    our $OPTION_SPECS = [\n      qw(\n        help|h\n        log-level=s|L\n        debug|d\n      )\n    ];\n\n    our $COMMANDS = {\n      foo =\u003e \\\u0026foo,\n      bar =\u003e \\\u0026bar,\n    };\n\nSubclasses can also extend the built-in internal commands by adding\nentries to `%INTERNAL_COMMANDS`:\n\n    our %INTERNAL_COMMANDS = (\n      %CLI::Simple::INTERNAL_COMMANDS,\n      '-my-command' =\u003e \\\u0026_cmd_my_command,\n    );\n\n# COMMAND LINE OPTIONS\n\nCommand-line options are defined using [Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong)-style\nspecifications. You pass these into the constructor via the\n`option_specs` parameter:\n\n    my $cli = CLI::Simple-\u003enew(\n      option_specs =\u003e [ qw( help|h foo-bar=s log-level=s ) ]\n    );\n\nIn your command subroutines, you can access these values using\nautomatically generated getter methods:\n\n    $cli-\u003eget_foo();\n    $cli-\u003eget_log_level();\n\nOption names that contain dashes (`-`) are automatically converted to\nsnake\\_case for the accessor methods. For example:\n\n    option_specs =\u003e [ 'foo-bar=s' ]\n\n...results in:\n\n    $cli-\u003eget_foo_bar();\n\n## set\\_args\n\nResets the positional arguments.\n\n    $self-\u003eset_args(qw(foo 1));\n\nThis method overrides the positional arguments originally passed to\nthe script. You can achieve the same behavior by calling the\n`get_args` in scalar context and modifying the reference.\n\n    my $args = $self-\u003eget_args;\n    $args-\u003e[1] = '2';\n\nUse this technique when you want don't want to alter the entire set of\narguments.\n\n# COMMAND ARGUMENTS\n\nIf your commands accept positional arguments, you can retrieve them\nusing the `get_args` method.\n\nYou may optionally provide a list of argument names, in which case the\narguments will be returned as a hash (or hashref in scalar context)\nwith named values.\n\nExample:\n\n    sub send_message {\n      my ($self) = @_;\n\n      my %args = $self-\u003eget_args(qw(phone_number message));\n\n      send_sms_message($args{phone_number}, $args{message});\n    }\n\nIf you call `get_args()` without any argument names, it simply\nreturns all remaining arguments as a list:\n\n    my ($phone_number, $message) = $self-\u003eget_args;\n\n_Note: When called with names, `get_args` returns a hash in list\ncontext and a hash reference in scalar context._\n\n# CUSTOM ERROR HANDLER\n\nBy default, `CLI::Simple` will exit if `GetOptions` returns a false\nvalue, indicating an error while parsing options. You can override this\nbehavior in one of two ways:\n\n- Set `$CLI::Simple::GETOPT_EXIT_ON_ERROR` to a false value.\n\n    This disables automatic exiting and lets your program decide what to do\n    after an option-parsing failure.\n\n- Provide an `error_handler` callback in the constructor.\n\n        my $cli = CLI::Simple-\u003enew(\n          commands        =\u003e \\%commands,\n          default_options =\u003e \\%default_options,\n          extra_options   =\u003e \\@extra_options,\n          option_specs    =\u003e \\@option_specs,\n          abbreviations   =\u003e $TRUE,\n          error_handler   =\u003e sub {\n            my ($msg) = @_;\n            print {*STDERR} $msg;\n            return $TRUE;   # continue processing\n          },\n        );\n\n    The error handler is called with the error message from `GetOptions`.\n    It must return a boolean: a true value allows processing to continue,\n    while a false value causes `CLI::Simple` to exit immediately.\n\n# SETTING DEFAULT VALUES FOR OPTIONS\n\nTo assign default values to your options, pass a hash reference as the\n`default_options` argument to the constructor. These values will be\nused unless explicitly overridden by the user on the command line.\n\nExample:\n\n    my $cli = CLI::Simple-\u003enew(\n      default_options =\u003e { foo =\u003e 'bar' },\n      option_specs    =\u003e [ qw(foo=s bar=s) ],\n      commands        =\u003e {\n        foo =\u003e \\\u0026foo,\n        bar =\u003e \\\u0026bar,\n      },\n    );\n\nDefaulted options are accessible through their corresponding getter\nmethods, just like options set via the command line.\n\n# ADDING USAGE TO YOUR SCRIPTS\n\nTo provide built-in usage/help output, include a `=head1 USAGE`\nsection in your script's POD:\n\n    =head1 USAGE\n\n      usage: myscript [options] command args\n\n      Options\n      -------\n      --help, -h      Display help\n      ...\n\nIf the user supplies the command `help`, or the `--help` option,\n`CLI::Simple` will display this section automatically:\n\n    perl myscript.pm --help\n    perl myscript.pm help\n\n## Custom help() Method\n\nIf you need full control over the help output, you can define a custom\n`help` method and assign it as a command:\n\n    commands =\u003e {\n      help =\u003e \\\u0026help,\n      ...\n    }\n\nThis is useful if your module follows the modulino pattern and you\nwant to present usage information that differs from the embedded\nPOD. Without a custom handler, `CLI::Simple` defaults to displaying the\n`USAGE` POD section.\n\n# ADDING ADDITIONAL SETTERS\n\nAll command-line options are automatically available through getter\nmethods named `get_*`.\n\nIf you need to create additional accessors (getters and setters) for\nvalues that are not derived from the command line, use the\n`extra_options` parameter.\n\nThis is useful for passing runtime configuration or computed values\nthroughout your application.\n\nExample:\n\n    my $cli = CLI::Simple-\u003enew(\n      default_options =\u003e { foo =\u003e 'bar' },\n      option_specs    =\u003e [ qw(foo=s bar=s) ],\n      extra_options   =\u003e [ qw(biz buz baz) ],\n      commands        =\u003e {\n        foo =\u003e \\\u0026foo,\n        bar =\u003e \\\u0026bar,\n      },\n    );\n\nThis will generate `get_biz`, `set_biz`, `get_buz`, etc., for\ninternal use.\n\n# LOGGING\n\n`CLI::Simple` integrates with [Log::Log4perl](https://metacpan.org/pod/Log%3A%3ALog4perl) to provide structured\nlogging for your scripts.\n\nTo enable logging, call the class method `use_log4perl()` in your\nmodule or script:\n\n    __PACKAGE__-\u003euse_log4perl(\n      level  =\u003e 'info',\n      config =\u003e $log4perl_config_string\n    );\n\nIf you do not explicitly include a `log-level` option in your\n`option_specs`, CLI::Simple will automatically add one for you.\n\nOnce enabled, you can access the logger instance via:\n\n    my $logger = $self-\u003eget_logger;\n\nThis logger supports the standard Log4perl methods like `info`,\n`debug`, `warn`, etc.\n\n## Per Command Log Levels\n\nSome commands may require more verbose logging than others. For\nexample, certain commands might perform complex actions that benefit\nfrom detailed logs, while others are designed solely to produce clean,\nstructured output.\n\nTo assign a custom log level to a command, use an array reference as\nthe value for that command in the commands hash passed to the\nconstructor.\n\nThe array reference should contain at least two elements:\n\n- A code reference to the command subroutine\n- A log level string: one of 'trace', 'debug', 'info', 'warn',\n'error', or 'fatal'\n\nExample:\n\n    CLI::Simple-\u003enew(\n      option_specs    =\u003e [qw( help format=s )],\n      default_options =\u003e { format =\u003e 'json' },  # set some defaults\n      extra_options   =\u003e [qw( content )],       # non-option, setter/getter\n      commands        =\u003e {\n        execute =\u003e \\\u0026execute,\n        list    =\u003e [ \\\u0026list, 'error' ],\n      }\n    )-\u003erun;\n\n_TIP: add other elements to the array for your command to process._\n\n_Note: Per-command log levels are not currently supported in the YAML\nmanifest. Define them programmatically by overriding `main()` if needed._\n\n# FAQ\n\n- How do I execute startup code before my command runs?\n\n    Implement an `init()` method in your class. The `new()` constructor\n    will invoke this method before returning and before `run()` is\n    executed.\n\n    Your `init()` method will have access to all options and\n    arguments. Logging will also be initialized, so you can use\n    `get_logger()` to emit messages.\n\n- Do I need to implement commands?\n\n    No. If your script doesn't support multiple commands, you can specify\n    a `default` key instead:\n\n        commands =\u003e { default =\u003e \\\u0026main }\n\n- Must I subclass `CLI::Simple`?\n\n    No. You can use it procedurally or functionally.\n\n- How do I turn my class into a script?\n\n    Use the modulino pattern: create a class that checks whether it is\n    being invoked directly:\n\n        package MyScript;\n\n        caller or __PACKAGE__-\u003emain();\n\n        sub main {\n          ...\n        }\n\n    This lets the file be used as both a module and an executable script.\n\n- How do I migrate an existing script to role-based architecture?\n\n    Run the built-in `-dump-spec` command to generate a YAML manifest from\n    your existing script, then `-scaffold` to generate role stubs:\n\n        my-script -dump-spec        # generates my-script.yml\n        my-script -scaffold         # generates my-script-roles.tar.gz\n\n    See [\"ROLE-BASED ARCHITECTURE\"](#role-based-architecture) for the full migration workflow.\n\n- How do I start a new role-based project from scratch?\n\n    Write a YAML manifest and use the `cli-simple` wrapper to scaffold it:\n\n        cli-simple -scaffold my-script.yml\n\n    See [\"ROLE-BASED ARCHITECTURE\"](#role-based-architecture) for the manifest format.\n\n- How do I enable bash completion for my script?\n\n    Your script must be invoked via a bash modulino wrapper with\n    `MODULINO_WRAPPER` set. Then run:\n\n        my-script -generate-completion \u003e \\\n          ~/.local/share/bash-completion/completions/my-script\n\n    Wrappers generated by [CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper) set\n    `MODULINO_WRAPPER` automatically.\n\n- How do I add my own internal commands?\n\n    Add entries to `%INTERNAL_COMMANDS` before calling `new()`:\n\n        our %INTERNAL_COMMANDS = (\n          %CLI::Simple::INTERNAL_COMMANDS,\n          '-my-command' =\u003e \\\u0026_cmd_my_command,\n        );\n\n# ALIASING OPTIONS AND COMMANDS\n\n`CLI::Simple` lets you define short, human-friendly aliases for both\noption names and command names. Use the `alias` parameter to `new():`\n\n    my $app = CLI::Simple-\u003enew(\n      option_specs    =\u003e [ qw(config=s verbose!) ],\n      commands        =\u003e { list =\u003e \\\u0026list, execute =\u003e \\\u0026execute },\n      alias =\u003e {\n        options  =\u003e { cfg =\u003e 'config', v =\u003e 'verbose' },\n        commands =\u003e { ls  =\u003e 'list'   }\n      },\n    );\n\n## How option aliases work\n\n- Spec tail is copied automatically\n\n    You only name the canonical option in `option_specs`. For each alias,\n    `CLI::Simple` finds the canonical option's spec tail (for example\n    `=s`, `:i`, `!`, `+`) and appends it to the alias. In the example\n    above, `cfg` behaves as if you had written `cfg=s`, and `v` behaves\n    as if you had written `v!`.\n\n    _Note: If your option includes a one-letter short-cut and the alias\n    does not start with the same letter it will not be automatically\n    enabled as a short-cut._\n\n- Accessors are created for both names\n\n    Accessors are generated from all option names (canonical and aliases),\n    with '-' normalized to '\\_'. In the example, both `get_config()` and\n    `get_cfg()` are available.\n\n- Values are mirrored after parsing\n\n    After option parsing and normalization, values are mirrored so either\n    name can be used consistently. If both the canonical name and its alias\n    are provided on the command line, the alias wins and becomes the final\n    value for both names.\n\n- No duplicate injection\n\n    If the alias already exists in `option_specs`, it will not be injected\n    again; value mirroring still occurs.\n\n- Errors are explicit\n\n    If an alias points at a canonical option that does not exist,\n    `CLI::Simple` croaks with a clear error.\n\n- Case sensitivity\n\n    `Getopt::Long` is used with `:config no_ignore_case`, so option names\n    (and therefore aliases) are case sensitive by default.\n\n## How command aliases work\n\n- Simple mapping\n\n    Provide `alias =` { commands =\u003e { alias =\u003e canonical } }\u003e to map an alias\n    to an existing command. In the example, `ls` dispatches to the `list`\n    command.\n\n- Applied before abbreviations\n\n    Aliases are installed before command abbreviation resolution. If you\n    enable abbreviations, they apply to the full set of command names,\n    including any aliases.\n\n- Errors are explicit\n\n    If an alias points at a command that does not exist, `CLI::Simple` croaks\n    with a clear error.\n\n## Usage examples\n\n    # Using an option alias\n    script.pl --cfg app.json execute\n\n    # Using a command alias\n    script.pl ls\n\nAfter parsing, both `get_config()` and `get_cfg()` will return the\nsame value. If the user passes both `--config` and `--cfg`, the value\nfrom `--cfg` (the alias) is used.\n\n_Note: In role-based applications using a YAML manifest, command\naliases are expressed by mapping the alias command directly to the\ntarget sub name rather than a role class. See [\"ROLE-BASED ARCHITECTURE\"](#role-based-architecture)._\n\n## Recommendations\n\n- Keep the canonical spec single-named\n\n    Define a single canonical name in `option_specs` and add other spellings\n    via `alias`. Avoid multi-name specs like `config|cfg=s`; use `alias`\n    instead.\n\n- Document your precedence\n\n    If you prefer the alias name to win when both are supplied, enforce\n    that in your application or adjust the mirroring order. By default, the\n    canonical name wins.\n\n# ERRORS/EXIT CODES\n\nWhen you execute the `run()` method it passes control to the method\nthat implements the command specified on the command line. Your method\nis expected to return 0 for success or an error code that you can pass\nto the shell on exit.\n\n    exit CLI::Simple-\u003enew(commands =\u003e { foo =\u003e \\\u0026cmd_foo })-\u003erun();\n\n## Exit Codes\n\n`CLI::Simple` uses conventional exit codes so that calling scripts\ncan distinguish between normal completion and error conditions.\n\n- '0'\n\n    Successful completion of a command (`SUCCESS`).\n\n- '1'\n\n    General usage error, such as `--help` display via `pod2usage`, or an\n    invalid command line (`FAILURE`).\n\n- '2'\n\n    Option parsing failure, such as an unrecognized option or invalid\n    argument (also reported as `FAILURE`).\n\n- Any other code\n\n    If a user-supplied command callback explicitly calls `exit()` or\n    returns a numeric value other than 0 - 2, that code is passed through\n    unchanged to the shell. This allows application-specific exit codes.\n\n# EXAMPLE\n\nRun the shell script that comes with the distribution to output a\nworking example:\n\n    cli-simple-example \u003e example.pl\n\nFor a role-based example, see [\"QUICK START\"](#quick-start).\n\n# LICENSE AND COPYRIGHT\n\nThis module is free software; you can redistribute it and/or modify it\nunder the same terms as Perl itself.  See\n[https://dev.perl.org/licenses/](https://dev.perl.org/licenses/) for more information.\n\n# SEE ALSO\n\n[Getopt::Long](https://metacpan.org/pod/Getopt%3A%3ALong), [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),\n[CLI::Framework](https://metacpan.org/pod/CLI%3A%3AFramework), [Role::Tiny](https://metacpan.org/pod/Role%3A%3ATiny),\n[CPAN::Maker::Bootstrapper](https://metacpan.org/pod/CPAN%3A%3AMaker%3A%3ABootstrapper)\n\n# AUTHOR\n\nRob Lauer - \u003crlauer@treasurersbriefcase.com\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frlauer6%2Fcli-simple","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frlauer6%2Fcli-simple","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frlauer6%2Fcli-simple/lists"}