{"id":29097382,"url":"https://github.com/lambdaisland/cli","last_synced_at":"2025-10-17T08:21:00.432Z","repository":{"id":220819552,"uuid":"752608140","full_name":"lambdaisland/cli","owner":"lambdaisland","description":"Opinionated command line argument handling, with excellent support for subcommands","archived":false,"fork":false,"pushed_at":"2025-04-05T10:01:08.000Z","size":183,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-06-19T16:29:46.559Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambdaisland.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2024-02-04T10:54:32.000Z","updated_at":"2025-04-30T03:54:46.000Z","dependencies_parsed_at":"2024-02-04T15:38:49.257Z","dependency_job_id":"4a344f49-2bcc-458c-a602-2c81ed6274d7","html_url":"https://github.com/lambdaisland/cli","commit_stats":{"total_commits":76,"total_committers":2,"mean_commits":38.0,"dds":0.02631578947368418,"last_synced_commit":"694e5299a7a780ff465423387103628d611e6483"},"previous_names":["lambdaisland/cli"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/lambdaisland/cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fcli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fcli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fcli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fcli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambdaisland","download_url":"https://codeload.github.com/lambdaisland/cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fcli/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261596066,"owners_count":23182300,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":"2025-06-28T13:42:04.819Z","updated_at":"2025-10-17T08:21:00.424Z","avatar_url":"https://github.com/lambdaisland.png","language":"Clojure","funding_links":["https://opencollective.com/lambda-island","http://opencollective.com/lambda-island"],"categories":[],"sub_categories":[],"readme":"# cli\n\n\u003c!-- badges --\u003e\n[![cljdoc badge](https://cljdoc.org/badge/com.lambdaisland/cli)](https://cljdoc.org/d/com.lambdaisland/cli) [![Clojars Project](https://img.shields.io/clojars/v/com.lambdaisland/cli.svg)](https://clojars.org/com.lambdaisland/cli) ![](https://img.shields.io/clojars/dt/com.lambdaisland%2Fcli?style=flat-square)\n\u003c!-- /badges --\u003e\n\nCommand line parser with good subcommand and help handling\n\n## Features\n\n\u003c!-- installation --\u003e\n## Installation\n\nTo use the latest release, add the following to your `deps.edn` ([Clojure CLI](https://clojure.org/guides/deps_and_cli))\n\n```clj\ncom.lambdaisland/cli {:mvn/version \"1.25.107\"}\n```\n\nor add the following to your `project.clj` ([Leiningen](https://leiningen.org/))\n\n```clj\n[com.lambdaisland/cli \"1.25.107\"]\n```\n\u003c!-- /installation --\u003e\n\n## Rationale\n\nThis is an opinionated CLI argument handling library. It is meant for command\nline tools with subcommands (for example git, which has `git commit`, `git log`\nand so forth). It tries to sticks to common conventions (in particular the\nprominent GNU conventions), uses a syntax that should feel natural to Clojure\nprogrammers (it's all just functions and data), and provides your tool with\nbuilt-in help facilities automagically.\n\nIt is Babashka compatible, and in fact pairs really nicely with `bb` for making\nhome-grown or general purpose tools.\n\nThis library helps you write [Well-behaved Command Line\nTools](https://lambdaisland.com/blog/2020-07-28-well-behaved-command-line-tools)\n\n## Getting Started\n\nWe'll use `com.lambdaisland/cli` to create a simple script that can print out\nthe options it receives and handle basic help.\n\nThis assumes [Babashka](https://babashka.org/) is installed, so `bb` should be\non your shell's `PATH`. To use lambdaisland/cli with plain Clojure instead, use\n`deps.edn` instead of `bb.edn`, and `clojure` instead of `bb`.\n\n```\n$ bb --version\n$ clojure --version\n```\n\n### Step 1: Create the Basic Command\n\nStart by creating a `bb.edn` file (or `deps.edn` when using `clojure`):\n\n```clj\n{:deps {com.lambdaisland/cli {:mvn/version \"1.25.107\"}}}\n```\n\nCreate a file (e.g., `cli-test`):\n\n```clj\n#!/usr/bin/env bb\n\n;; alternatively: #!/usr/bin/env clojure\n\n(require \n '[lambdaisland.cli :as cli]\n '[clojure.pprint :as pprint])\n\n;; 1. Define your main function.\n(defn cli-test \n  \"Ground breaking tool breaks new ground.\"\n  [opts]\n  (pprint/pprint opts))\n\n;; 2. Dispatch the function (using its var).\n(cli/dispatch #'cli-test)\n```\n\nMake it executable:\n\n```shell\nchmod +x cli-test\n```\n\nRun it:\n\n```shell\n$ ./cli-test --help\nNAME\n  cli-test  ——  Ground breaking tool breaks new ground.\n\nSYNOPSIS\n  cli-test [\u003cargs\u003e...]\n```\n\nBy passing a function var (`#'cli-test`), the library automatically infers the\nname and docstring for help output.\n\n### Step 2: Pass Arguments and Flags\n\nRun it with some input\n\n```clj\n$ bb cli-test.bb --abc -xxy hello --format=txt world\n{:lambdaisland.cli/argv [\"hello\" \"world\"]\n :abc 1\n :x 2\n :y 1\n :format \"txt\"}\n```\n\nYour command function receives all parsed input as a single map argument.\nArguments starting with `-` or `--` are parsed as flags with some default\nconventions (you can change how they are handled by explicitly defining them).\n\n- `--abc` — the value in the map is the number of times the flag, can be treated as a count or simply as a boolean\n- `-x` — also a count, but multiple one-letter flags can be passed at once when using a single dash, so `-xxy` is the same as `-x -x -y`\n- `--format=txt` — key-value, if the value looks like a number it is parsed as such\n- remaining values are collected in `:lambdaisland.cli/argv`.\n\n### Step 3: Define Custom Flags\n\nTo gain control over how flags are parsed and to provide richer help text, we\nwrap the command var in a configuration map. Note that `:flags` uses vector\nsyntax, so you can specify the order they show up in the help, but it functions\nmore like a map.\n\nModify your `cli-test.bb` to include a `:flags` configuration:\n\n```clj\n(cli/dispatch\n {:name \"cli-test\"\n  :command #'cli-test ; The function to call\n  :flags   [\"-v, --verbose\" \"Increases verbosity\"\n            \"--input FILE\"  \"Specify the input file\"]})\n```\n\nRun the updated help:\n\n```shell\n$ bb cli-test.bb --help                        \nNAME\n  cli-test  ——  Ground breaking tool breaks new ground.\n\nSYNOPSIS\n  cli-test [-v | --verbose] [--input FILE] [\u003cargs\u003e...]\n\nFLAGS\n  -v, --verbose      Increases verbosity      \n      --input FILE   Specify the input file   \n```\n\nRun the tool with the new flags:\n\n```clj\n$ bb cli-test.bb -vvv --input=world.txt\n{:lambdaisland.cli/argv [],\n :verbose 3,\n :input \"world.txt\"}\n```\n\n### Step 4. Flag options and subcommands\n\nTo give a hint of the full power of this library, we'll add a few subcommands,\nand change how the flags behave.\n\nFor this we replace the single `:command` with a set of `:commands`, using a\n`tool noun verb` structure that is a common convention.\n\nInstead of just a string, we use a map to describe each flag, so we can set a\ndefault, change how it is parsed, or set the key that will show up in the\noptions map passed to the command function.\n\n```clj\n(defn ls-widgets\n  [opts]\n  (println \"Listing widgets\")\n  (pprint/pprint opts))\n\n(cli/dispatch\n {:name     \"cli-test\"\n  :commands [\"widget\"\n             {:doc      \"Manipulate widgets\"\n              :commands\n              [\"ls\"\n               {:doc     \"List widgets\"\n                :command ls-widgets}]}]\n  :flags    [\"-v, --verbose\" {:doc \"Increases verbosity\"\n                              :key :verbosity\n                              :default 0}\n             \"--format \u003cfmt\u003e\"  {:doc \"Specify the format\"\n                                :default \"txt\"\n                                :parse keyword}]})\n```\n\nAnd try it out:\n\n```\n$ cli-test widget ls --format=xxx -vvv\nListing widgets\n{:verbosity 3\n :format :xxx\n :lambdaisland.cli/command [\"widget\" \"ls\"]\n :lambdaisland.cli/argv []}\n```\n\nBut note that this example can also be written using vars and docstrings, which\nmakes it really neat. It also lets you put command specific flags in var\nmetadata, like this:\n\n```clj\n(defn ls-widgets\n  \"List widgets\"\n  {:flags [\"--[no-]disabled\" \"Include disabled widgets in the list\"]}\n  [opts]\n  (println \"Listing widgets\")\n  (pprint/pprint opts))\n\n(def widget-cmds\n  \"Manipulate widgets\"\n  {:commands [\"ls\" #'ls-widgets]})\n\n(def flags\n  [\"-v, --verbose\" {:doc \"Increases verbosity\"\n                    :key :verbosity\n                    :default 0}\n   \"--format \u003cfmt\u003e\"  {:doc \"Specify the format\"\n                      :default \"txt\"\n                      :parse keyword}])\n\n(cli/dispatch\n {:name     \"cli-test\"\n  :commands [\"widget\" #'widget-cmds]\n  :flags    flags})\n```\n\n### Summary\n\nWe've shown how you can get started immediately, relying on the automatic\nhandling of flags and positional arguments. How you can improve how flags and\narguments are handled, and get better help text, by defining them explicitly,\nand how var references provide a concise way to define complex setups.\n\nThis just scratches the surface, we haven't shown commands with named positional\narguments, middleware, flag handling functions, passing through a map of initial\nvalues, or shell tab-completion.\n\n## How-to Guides\n\nWe can add extra things to the dispatch configuration map, notably `:flags` and\n`:commands`. We'll explain flags first.\n\n### How to Configure Flags for Type Conversion and Defaults\n\nBecause you haven't told lambdaisland/cli about the flags your script\nunderstands, it has to guess how to handle them. If you have arguments like\n`--input world.txt`, then without extra information we don't know if `world.txt`\nis a positional argument to the script itself, or to `--input`. In this case\n`cli` assumes that it's a separate positional argument. If you use the\n`--input=world.txt` then it does know that this is the argument to `--input`,\nbut ideally these two (with or without the `=`) should behave the same.\n\n`:flags` takes a vector or map of \"flag specification\" + \"flag options\" pairs,\nwhere the options can be simply a string if you only need to set a documentation\nstring. The reason we also support a vector is so you can be explicit about the\norder that flags should be shown in the help text. (The same is true for\n`:commands`).\n\n```clj\n(cli/dispatch\n {:command #'cli-test\n  :flags [\"-v, --verbose\" \"Increases verbosity\"\n          \"--input FILE\" \"Specify the input file\"\n          \"--env=\u003cdev|prod|staging\u003e\" {:doc \"Select an environment\"}] })\n```\n\nThis is what the help text looks like now:\n\n```shell\n$ cli-test --help\nUsage: cli-test [-v | --verbose] [--input FILE] [--env=\u003cdev|prod|staging\u003e] [\u003cargs\u003e]\n\nGround breaking tool breaks new ground.\n\n  -v, --verbose                  Increases verbosity\n      --input FILE               Specify the input file\n      --env=\u003cdev|prod|staging\u003e   Select an environment \n```\n\nAnd if we invoke it\n\n```\n$ cli-test -vvv --input=world.txt --env prod --cool=123\n{:verbose 3,\n :input \"world.txt\",\n :env \"prod\",\n :cool \"123\",\n :lambdaisland.cli/argv []}\n```\n\nSo your `:command` function receives a map, which contains the parsed flags.\nPositional argument are also added to the map, if they are given a name (which\nwe'll show below), but they are also gathered under the special key\n`::cli/argv`.\n\nNote that this map is also bound to the dynamic var `cli/*opts*`, so it's not\nnecessary to pass it around everywhere. This is especially useful for flags that\ninfluence global behavior, like `--verbose`, `--silent`, or `--dry-run`.\n\nAt this point a few things are worth calling out.\n\n- To specifiy arguments to flags or subcommands we support both the all-caps\n  version or the angle brackets, version, so `--flag ARG` or `--flag \u003carg\u003e`.\n- You can optionally add an equal sign in between when specifying the argument,\n  e.g. `--flag=\u003carg\u003e` .\n- When invoking the command, both the version with or without `=` are\n  understood, regardless of how the flag was specified.\n- If a flag is not predefined then only the `--flag=\u003carg\u003e` version is able to\n  pass along an argument, with `--flag arg` we assume that flag does not take\n  any arguments, and arg is instead treated as a positional argument to the\n  (sub-)command.\n- For flags without arguments, the default behavior is to count the number of\n  flags. This is useful for things like `--verbose` that can be specified\n  multiple times. For other cases you can simply treat it as a boolean.\n- The key that is used in the options map is based on the long-form.\n  (double-dash) form. So `-v, --verbose` means you'll get a `:verbose` key, for\n  `-v` or `--verbose`.\n- You can use the `--[no-]foo` syntax for adding both a `--foo` and a `--no-foo`\n  flag, in this case the resulting value in the opts map will be `:foo true` or\n  `:foo false`.\n- You can add a `:default` to a flag, like `[\"--port PORT\" {:default 8080}]`.\n- You can set a `:parse` function which will be used to parse/coerce the\n  argument. The default will parse numbers (basic longs and doubles, no special\n  formats), and nothing else.\n- If you have a `:default` which is a string, and you have a `:parse` function,\n  then the default will be run through parse as well. It's generally best to set\n  the default to a string or a number, this will look better in the help text,\n  where we show the default.\n- A single dash (`-`) is considered a positional argument, conventially\n  indicating stdin/stdout.\n- To pass a positional argument that starts with a dash, prefix it with a\n  backslash. lambdaisland/cli will remove the backslash, and treat the remainder\n  as a positional argument rather than a flag. Note that the shell does its own\n  backspace (character escape) handling, so in practice this means prefixing\n  with two backslashes, e.g. `\\\\--foo`.\n- For flags that are expected to be passed multiple times, e.g. `-u foo -u bar`,\n  set `:coll? true`, in that case you will always receive them as a vector, even\n  if there is only one.\n\nYou can also explicitly set which key to use with `:key`, as well as setting a\nspecific `:value`, for instance:\n\n```clj\n(cli/dispatch\n {:command #'cli-test\n  :flags\n  [\"--prod\" {:doc \"Use the production environment\"\n             :key :env\n             :value :prod}\n   \"--staging\" {:doc \"Use the staging environment\"\n                :key :env\n                :value :staging}]})\n```\n\n```shell\n$ cli-test --staging\n{:env :staging, :lambdaisland.cli/argv []}\n```\n\nAlternatively you can set a handler function, which determines what happens when\nthis flag is used. It is passed the options map, and any arguments to the flag,\nbut you could also use it for instance to change global state.\n\n```clj\n(cli/dispatch\n {:command #'cli-test\n  :flags\n  [\"--prod\" {:doc \"Use the production environment\"\n             :handler (fn [opts] (assoc opts :env :prod))}\n   \"--input FILE\" {:doc \"Use the staging environment\"\n                   :handler (fn [opts file] (assoc opts :input-file file))}]})\n```\n\n```shell\n$ cli-test --input foo\n{:input-file \"foo\", :lambdaisland.cli/argv []}\n```\n\nNote that if you have a `:default` and a `:handler`, then the handler will be\ncalled with the default first, always, and possibly later again with based on\nany CLI flags.\n\n\u003c!-- (Note that a flag CAN have multiple positional arguments (e.g. `\"--foo A B\"`), --\u003e\n\u003c!-- but this is discouraged since it goes contrary to expectations of command line --\u003e\n\u003c!-- utilities, and it can not be used with the `=` syntax.) --\u003e\n\nIf you are explicit about which flags you accept, then you may prefer not to let\n`lambdaisland/cli` play loosey goosey and simply accept anything. In this case\nyou can set `:strict? true`. In this mode only explicitly configured flags are\naccepted, others throw an error.\n\nAnother possibility is to set `:middleware` for a flag, this is a function or\nlist of functions that get wrapped around the final command.\n\n```clj\n(cli/dispatch\n {:commands\n  [\"ls\" {:command #'list-widgets\n         :flags\n         [\"-l, --long\"\n          {:doc        \"Use long format\"\n           :middleware [(fn [cmd]\n                          (fn [opts]\n                            (binding [*format* :long]\n                              (cmd opts))))]}]}]})\n```\n\nFinally, it's possible to set `:required`, to indicate for users that a flag\nmust always be passed:\n\n```clj\n(cli/dispatch\n {:command #'cli-test\n  :flags [\"-v, --verbose\" \"Increases verbosity\"\n          \"--input FILE\" \"Specify the input file\"\n          \"--env=\u003cdev|prod|staging\u003e\" {:doc \"Select an environment\"\n                                      :required true}] })\n```\n\n#### Flag option reference\n\n- `:doc` docstring\n- `:default` default value\n- `:value` value to be associated with the key (for flags that don't take arguments)\n- `:handler` handler function, `(fn [opts \u0026 flag-args] ,,,)` \n- `:middleware` function(s) which wrap the final command handler (function or sequence of functions)\n- `:coll?` flag can be specified  multiple times, will result in a vector \n- `:parse` function used to coerce the flag argument\n\n### How to Build a Multi-Command CLI Tool\n\n`lambdaisland/cli` is specifically meant for CLI tools with multiple subcommands\n(and sub-sub-commands, and so forth). In this way it forms an appealing\nalternative to bb tasks, for people who prefer conventional CLI ergonomics, as\nwell as keeping everything in a single script.\n\nSpecifying commands is similar to specifying flags, you provide a vector of map\nwith pairs, where the first element in the pair is a string that specifies the\ncommand, and any arguments, and the second element is a map specifying how that\ncommand should be used. Instead of a map you can also provide a var, in which\ncase the docstring (`:doc`) and command (`:command`) are taken from the var.\n\nThe way this works is we take the var metadata, and add `:command` pointing at\nthe var itself. If you provide a map with a var `:command`, then we merge the\nmetadata and whatever values you provided. So you can also specify additional\nkeys through var metadata.\n\n```clj\n(cli/dispatch\n {:name    \"cli-test\"\n  :doc     \"This is my cool CLI tool. Use it well.\"\n  :strict? true\n  :commands\n  [\"ls\"       #'list-widgets\n   \"add \u003cid\u003e\" #'add-widget\n   \"auth\" {:doc      \"Auth commands\"\n           :commands [\"login\"  #'auth-login\n                      \"logout\" #'auth-logout]}]\n  :flags\n  [\"-v,--verbose\" \"Increase verbosity\"\n   \"--input=\u003cfoo\u003e\" \"Input file\"]})\n```\n\n```shell\n$ cli-test auth login x y z\n{:lambdaisland.cli/command [\"auth\" \"login\"],\n :lambdaisland.cli/argv [\"x\" \"y\" \"z\"]}\n```\n\nNotice now CLI has split the positional argument into a \"command\" and \"argv\"\n(argument vector), and made both available in the map passed to the command (and\nbound to `cli/*opts*`).\n\nHere you start seeing the benefits of this stuff managed for you, things like\nrequesting the help information for a subcommand \"just works\".\n\n```shell\n$ cli-test --help   \nUsage: cli-test [-v | --verbose] [--input=\u003cfoo\u003e] [ls | auth | add] [\u003cargs\u003e...]\n\nThis is my cool CLI tool. Use it well.\n\n  -v, --verbose       Increase verbosity\n      --input=\u003cfoo\u003e   Input file        \n\n  ls                    List widgets \n  auth \u003clogin|logout\u003e   Auth commands\n  add \u003cid\u003e              Add a widget \n```\n\n```shell\n$ cli-test auth --help\nUsage: cli-test auth [-v | --verbose] [--input=\u003cfoo\u003e] [login | logout] [\u003cargs\u003e...]\n\nAuth commands\n\n  -v, --verbose       Increase verbosity\n      --input=\u003cfoo\u003e   Input file        \n\n  login    Login with your account\n  logout   Log out of your account\n```\n\nCommands can specify additional flags, which will be available for that command\nor any subcommands.\n\n```clj\n(cli/dispatch\n {:name    \"cli-test\"\n  :doc     \"This is my cool CLI tool. Use it well.\"\n  :commands\n  [\"ls\"       {:command #'list-widgets\n               :flags   [\"-l, --long\" \"Use long format\"]}\n   \"add \u003cid\u003e\" #'add-widget]\n  :flags\n  [\"-v,--verbose\" \"Increase verbosity\"]})\n```\n\nIn this case the `--long` option is only available for `cli-test ls`.\n\n```shell\n$ cli-test --help     \nUsage: cli-test [-v | --verbose] [ls | add] [\u003cargs\u003e...]\n\nThis is my cool CLI tool. Use it well.\n\n  -v, --verbose   Increase verbosity\n\n  ls                     \n  add \u003cid\u003e   Add a widget\n```\n\n```shell\n$ cli-test ls --help\nUsage: cli-test ls [-v | --verbose] [-l | --long] [\u003cargs\u003e...]\n\nThis is my cool CLI tool. Use it well.\n\n  -v, --verbose   Increase verbosity\n  -l, --long      Use long format   \n```\n\n#### Command Middleware\n\nCommands, too, can receive middleware, including in the top level cmdspec. If\nyou imagine middleware as getting wrapped in layers around the final command\nhandler function, then the outer layers are the middleware for flags, and the\ninner layers are for command middleware, with subcommand middleware sitting\ninside their parent middleware.\n\nFlags that occur first on the command line get their middleware wrapped around\nthe middleware for flags that occur later, so that later flags may override the\nbehavior or earlier flags.\n\nWhat this means effectively is that is flags manipiulate the options, that this\nis visible to command middleware, and that if parent commands manipulate the\noptions map, that this is visible to child commands. Similarly a parent could\nbind a dynamic var, and have it be visible to its children.\n\n#### Command option reference\n\n- `:name` (top-level only) Name of the command, used in the help text\n- `:doc` docstring, first line should be a short description, followed by a\n  blank line, and a longer description\n- `:command` handler function, `(fn [opts] ,,,)`, or var\n- `:commands` subcommands, mutually exclusive with `:command`\n- `:flags` flags, see the section of flags\n- `:middleware` function(s) which wrap the final command handler (function or sequence of functions)\n- `:init` Initial options maps, which gets threaded through middleware and flag\n  handlers, and passed to the final command handler\n\n### Processing Order\n\nMost of what lambdaisland/cli does is combine the command and flag descriptions\nwith the incoming command line arguments to build up a map, which then gets\npassed to the command handler. This map gets built up in multiple steps.\n\nThe `:init` configuration flag, if present, provides the starting point. It can\nbe a map, or a zero-arity function returning a map.\n\nThen we add `:default` values for top-level `:flags`. Normally these are simply\nassoc'ed into the map provided by `:init`. If the flag also has a `:handler`,\nthen the opts map we have so far is passed to the handler, which can manipulate\nit and return an updated version.\n\nThen we actually start processing command line arguments, splitting them into\nflags (start with a dash), or positional argument (does not start with a dash,\nor a single dash). Flag arguments are processed as we encounter them,\npotentially calling their handler, with the opts map we have so far, from\n`:init`, defaults, and earlier flags and flag handlers. The positional arguments\nare then used to determine which (sub-)command to invoke.\n\nDuring any handler execution `cli/*opts*` is bound to the intermediate opts map\nthat we have so far, so any utility functions that are called can assume this is\navailable.\n\nSub-commands can add additional flag specifications, if we encounter those then\ntheir defaults are added to the opts map, either directly or through their\ndefined handler. This does mean that these kind of flags can only be used\n*after* the command. This is an unfortunate necessity, since doing it otherwise,\nby reparsing with the additional flags added, can lead to ambiguity between flag\narguments and positional arguments. Note that in non-strict mode flags that\nprecede the subcommand that defines them will use the default flag processing,\nwithout any regard for the flagspec defined on the subcommand (`:parse`,\n`:default`, `:handler`, `:middleware`, etc are all ignored).\n\nFinally the opts map gets bound to `cli/*opts*`, middleware gets invoked (so\n`*opts*` can be accessed during middleware execution).\n\nPay attention to the fact that the opts map is built up in multiple steps, and\nso flag handlers will only see a partial `opts`/`*opts*`. Because of this we\nrecommend mainly using handlers to manipulate the opts map, and not much else.\nAny behavior should be reserved for the main command handler, and for\nmiddleware, which are guaranteed to see all possible flags reflected in the opts\nmap they receive.\n\n## Shell completion\n\nThere is experimental support for shell tab-completion for zsh and bash. It is\nbare bones, it can complete subcommands and flags, and is aware of nested\nsubcommands, but don't expect it to be perfect. There is no smart completion of\nflag values or positional arguments yet, and it's not yet pluggable (specify\ntype/completions of values), but it's already helpful.\n\nTo get started run the hidden `__licli install-completions` subcommand. This\nwill detect your shell, and install the necessary bits, and associate our\ndynamic completion with your specific command. You can add `--zsh` or `--bash`\nto explictly choose which shell to configure.\n\ne.g.\n\n```\nbin/my_command __licli install-completions\n```\n\nWhen calling from Clojure (not Babashka), we can't derive the script name\nexplicitly. Pass the full path to the executable as an argument. You can easily\nuse `realpath` for that.\n\n```\nbin/my_command __licli install-completions `realpath bin/my_command`\n```\n\nOther shells and smarter completions may come in the future. The current\nimplementation is so we have a starting point that can be improved.\nContributions to the shell completion are welcome!\n\n\u003c!-- opencollective --\u003e\n## Lambda Island Open Source\n\nThank you! cli is made possible thanks to our generous backers. [Become a\nbacker on OpenCollective](https://opencollective.com/lambda-island) so that we\ncan continue to make cli better.\n\n\u003ca href=\"https://opencollective.com/lambda-island\"\u003e\n\u003cimg src=\"https://opencollective.com/lambda-island/organizations.svg?avatarHeight=46\u0026width=800\u0026button=false\"\u003e\n\u003cimg src=\"https://opencollective.com/lambda-island/individuals.svg?avatarHeight=46\u0026width=800\u0026button=false\"\u003e\n\u003c/a\u003e\n\u003cimg align=\"left\" src=\"https://github.com/lambdaisland/open-source/raw/master/artwork/lighthouse_readme.png\"\u003e\n\n\u0026nbsp;\n\ncli is part of a growing collection of quality Clojure libraries created and maintained\nby the fine folks at [Gaiwan](https://gaiwan.co).\n\nPay it forward by [becoming a backer on our OpenCollective](http://opencollective.com/lambda-island),\nso that we continue to enjoy a thriving Clojure ecosystem.\n\nYou can find an overview of all our different projects at [lambdaisland/open-source](https://github.com/lambdaisland/open-source).\n\n\u0026nbsp;\n\n\u0026nbsp;\n\u003c!-- /opencollective --\u003e\n\n\u003c!-- contributing --\u003e\n## Contributing\n\nWe warmly welcome patches to cli. Please keep in mind the following:\n\n- adhere to the [LambdaIsland Clojure Style Guide](https://nextjournal.com/lambdaisland/clojure-style-guide)\n- write patches that solve a problem \n- start by stating the problem, then supply a minimal solution `*`\n- by contributing you agree to license your contributions as MPL 2.0\n- don't break the contract with downstream consumers `**`\n- don't break the tests\n\nWe would very much appreciate it if you also\n\n- update the CHANGELOG and README\n- add tests for new functionality\n\nWe recommend opening an issue first, before opening a pull request. That way we\ncan make sure we agree what the problem is, and discuss how best to solve it.\nThis is especially true if you add new dependencies, or significantly increase\nthe API surface. In cases like these we need to decide if these changes are in\nline with the project's goals.\n\n`*` This goes for features too, a feature needs to solve a problem. State the problem it solves first, only then move on to solving it.\n\n`**` Projects that have a version that starts with `0.` may still see breaking changes, although we also consider the level of community adoption. The more widespread a project is, the less likely we're willing to introduce breakage. See [LambdaIsland-flavored Versioning](https://github.com/lambdaisland/open-source#lambdaisland-flavored-versioning) for more info.\n\u003c!-- /contributing --\u003e\n\n\u003c!-- license --\u003e\n## License\n\nCopyright \u0026copy; 2024-2025 Arne Brasseur and Contributors\n\nLicensed under the term of the Mozilla Public License 2.0, see LICENSE.\n\u003c!-- /license --\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fcli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambdaisland%2Fcli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fcli/lists"}