{"id":26956287,"url":"https://github.com/l3nz/cli-matic","last_synced_at":"2025-05-15T20:06:11.861Z","repository":{"id":31993196,"uuid":"127525682","full_name":"l3nz/cli-matic","owner":"l3nz","description":"Compact, hands-free [sub]command line parsing library for Clojure.","archived":false,"fork":false,"pushed_at":"2023-08-11T06:12:43.000Z","size":403,"stargazers_count":372,"open_issues_count":35,"forks_count":29,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-05-10T07:48:32.115Z","etag":null,"topics":["argv-parser","clojure","command-line","command-line-parser","graalvm","planck","subcommands"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/l3nz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","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}},"created_at":"2018-03-31T11:20:30.000Z","updated_at":"2025-04-01T14:56:34.000Z","dependencies_parsed_at":"2024-06-18T18:35:56.168Z","dependency_job_id":"2d33a804-839f-4ce8-a35f-2c47beac56a5","html_url":"https://github.com/l3nz/cli-matic","commit_stats":{"total_commits":281,"total_committers":16,"mean_commits":17.5625,"dds":0.5480427046263345,"last_synced_commit":"d80e7db1bae2962c3b291dbdee14e67bfdfd288f"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l3nz%2Fcli-matic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l3nz%2Fcli-matic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l3nz%2Fcli-matic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/l3nz%2Fcli-matic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/l3nz","download_url":"https://codeload.github.com/l3nz/cli-matic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414499,"owners_count":22067272,"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":["argv-parser","clojure","command-line","command-line-parser","graalvm","planck","subcommands"],"created_at":"2025-04-03T03:21:09.629Z","updated_at":"2025-05-15T20:06:06.818Z","avatar_url":"https://github.com/l3nz.png","language":"Clojure","readme":"# CLI-matic \n\n\n[![Clojars Project](https://img.shields.io/clojars/v/cli-matic.svg)](https://clojars.org/cli-matic)\n[![](https://cljdoc.xyz/badge/cli-matic)](https://cljdoc.xyz/jump/release/cli-matic)\n![ClojarsDownloads](https://img.shields.io/clojars/dt/cli-matic)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --\u003e\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)\n\u003c!-- ALL-CONTRIBUTORS-BADGE:END --\u003e\n\nCompact [sub]command line parsing library, for Clojure. Perfect for scripting (who said\nClojure is not good for scripting?).\n\n**Especially when scripting, you should write interesting code, not boilerplate.** Command line apps are usually so tiny that there is absolutely no reason why your code should not be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.\n\nCLI-matic works with GraalVM, giving unbeatable performance for stand-alone command-line apps that do not even need a Java installation - see [Command-line apps with Clojure and GraalVM: 300x better start-up times](https://www.astrecipes.net/blog/2018/07/20/cmd-line-apps-with-clojure-and-graalvm/).\n\nCLI-matic also works with Planck REPL for very quick CLJS scripting - see [Using with Planck](https://github.com/l3nz/cli-matic/blob/master/examples/cljs-planck/README.md) and - last but not least - is compatible with [Babashka](#babashka), that happens to be the gold standard of Clojure scripting.\n\nAnd if there is no such thing as too much of a good thing, then [BabashkaBins](#babashkabins) lets you write a script using `bb` and then compile it to a Graal binary automagically.\n\n\n## Using\n\nThe library is available on Clojars: https://clojars.org/cli-matic\n\nOr the library can be easily referenced through Github when using `deps` (make sure you change the commit-id):\n\n\t{:deps\n\t {cli-matic\n\t  {:git/url \"https://github.com/l3nz/cli-matic.git\"\n\t   :sha \"374b2ad71843c07b9d2ddfc1d4439bd7f8ebafab\"}}}\n\n\n## Features\n\n\n* Create **all-in-one scripts with subcommands and help**, in a way more compact than the excellent - but lower level - `tools.cli`.\n* **Avoid common pre-processing.** Parsing dates, integers, reading small files, downloading a JSON URL.... it should just happen. The more you declare, the less time you waste.\n* **Validate with Spec.** Modern Clojure uses Spec, so validation should be spec-based as well. Validation should happen at the parameter level, and across all parameters of the subcommand at once, and emit sane error messages. Again, the more you have in declarative code, the less room for mistakes.  \n* **Read environment variables.** Passing environment variables is a handy way to inject passwords, etc. This should just happen and be declarative.\n* **Capture unnamed parameters** as if they were named parameters, with casting, validation, etc.\n* **Babashka-compatible**. Read [here](#babashka) for more info.\n\nWhile targeted at scripting, CLI-matic of course works with any program receiving CLI arguments.\n\n\n## Rationale\n\nSay we want to create a short script, in Clojure, where we want\nto run a very simple calculator that either sums A to B or subtracts B from A:\n\n\n\t$ clj -m calc add -a 40 -b 2\n\t42\n\t$ clj -m calc sub -a 10 -b 2\n\t8\n\t$ clj -m calc --base 16 add -a 30 -b 2\n\t20\n\nWe also want it to display its help:\n\n\t$clj -m calc -?\n\tNAME:\n\t toycalc - A command-line toy calculator\n\n\tUSAGE:\n\t toycalc [global-options] command [command options] [arguments...]\n\n\tVERSION:\n\t 0.0.1\n\n\tCOMMANDS:\n\t   add, a   Adds two numbers together\n\t   sub, s   Subtracts parameter B from A\n\n\tGLOBAL OPTIONS:\n\t       --base N  10  The number base for output\n\t   -?, --help\n\n\nAnd help for sub-commands:\n\n\t$clj -m calc add -?\n\tNAME:\n\t toycalc add - Adds two numbers together\n\n\tUSAGE:\n\t toycalc [add|a] [command options] [arguments...]\n\n\tOPTIONS:\n\t   -a, --a1 N  0  Addendum 1\n\t   -b, --a2 N  0  Addendum 2\n\t   -?, --help\n\nBut while we are coding this, we do not really want to waste time writing any parsing logic.\nWhat we care about implementing are the functions `add-numbers` and `sub-numbers` where we do actual work; the rest should be declared externally and/or \"just happen\".\n\nFrom the point of view of us programmers, we'd like to have a couple of functions like:\n\n\t(defn add-number\n\t\t\"Sums A and B together, and prints it in base `base`\"\n\t\t[{:keys [a b base]}]\n\t\t(Integer/toString (+ a b) base))\n\nAnd nothing more; **the fact that both parameters exist, are of the right type, have the right defaults, print\nthe correct help screen, etc., should ideally not be a concern.**\n\n\nSo we define a configuration:\n```clojure\n\n(def CONFIGURATION\n  {:command     \"toycalc\"\n   :description \"A command-line toy calculator\"\n   :version     \"0.0.1\"\n   :opts        [{:as      \"The number base for output\"\n                  :default 10\n                  :option  \"base\"\n                  :type    :int}]\n   :subcommands [{:command     \"add\"\n                  :description \"Adds two numbers together\"\n                  :examples    [\"First example\" \"Second example\"]\n                  :opts        [{:as     \"Addendum 1\"\n                                 :option \"a\"\n                                 :type   :int}\n                                {:as      \"Addendum 2\"\n                                 :default 0\n                                 :option  \"b\"\n                                 :type    :int}]\n                  :runs        add_numbers}\n                 {:command     \"subc\"\n                  :description \"Subtracts parameter B from A\"\n                  :opts        [{:as      \"Parameter q\"\n                                 :default 0\n                                 :option  \"q\"\n                                 :type    :int}]\n                  :subcommands [{:command     \"sub\"\n                                 :description \"Subtracts\"\n                                 :opts        [{:as      \"Parameter A\"\n                                                :default 0\n                                                :option  \"a\"\n                                                :type    :int}\n                                               {:as      \"Parameter B\"\n                                                :default 0\n                                                :option  \"b\"\n                                                :type    :int}]\n                                 :runs        subtract_numbers}]}]} ]\n```\n\n\nIt contains:\n\n* Information on the app itself (name, version)\n* The list of global parameters as `:opts`, i.e. the ones that apply to all subcommands (may be empty, or you may skip it at all)\n* A list of sub-commands, each with its own parameters in `:opts`, and a function to be called in `:runs`, or more `:subcommands`. You can optionally validate the full parameter-map that is received by the function implementing the subcommand at once by passing a Spec into `:spec`.\n\nAnd...that's it!\n\n\n### Handling multiple layers of sub-commands\n\nAs the configuration is recursive (what you have in `:subcommands` can contain more subcommands)  you can have multiple layers of subcommands, each with their own \"global\" options; or you can have no subbcommands at all by simply defining a `:runs` function at the main level.\n\n* If within the subcommand you add a 0-arity function to `:on-shutdown`, it will be called when the JVM terminates. This is mostly useful for long running servers, or to do some clean-up. Note that the hook is always called - whether the shutdown is forced by pressing (say) Ctrl+C or just by the JVM exiting. See the examples. \n* When printing a version number, the most-specific wins; that is, you could have a different version string per subcommand. If not found, the most-specific ancestor found is used.\n* The same goes for help generation; you can have it customized per sub-command if needed.  \n* Each subcommand can have an optional `:examples` key, that can contain a string or\n  a sequence of strings, that will be printed out under EXAMPLES.\n\n\n\n### Current pre-sets\n\nThe following pre-sets (`:type`) are available:\n\n* `:int` - an integer number\n* `:int-0` - an integer number, with defaults to zero\n* `:float` - a float number\n* `:float-0` - a float number, with defaults to zero\n* `:string` - a string\n* `:keyword` - a string representation of a keyword, leading colon is optional, if no namespace is specified. ::foo will be converted to :user/foo, otherwise it will work as expected.\n* `:with-flag` - a boolean flag that generates a pair of --foo/--no-foo flags. --foo sets 'foo' to true and --no-foo sets 'foo' to false.\n* `:flag` - a boolean flag that recognizes \"Y\", \"Yes\", \"On\", \"T\", \"True\", and \"1\" as true values and \"N\", \"No\", \"Off\", \"F\", \"False\", and \"0\" as false values.\n* `:json` - a JSON literal value, that will be decoded and returned as a Clojure structure.\n* `:yaml` - a YAML literal value, that will be decoded and returned as a Clojure structure.\n* `:edn` - an EDN literal value, that will be decoded and returned.\n* `:yyyy-mm-dd` - a Date object, expressed as \"yyyy-mm-dd\" in the local time zone\n* `:slurp` - Receives a file name - reads is as text and returns it as a single string. Handles URIs correctly. Handles \"-\" as stdin.\n* `:slurplines` - Receives a file name - reads is as text and returns it as a seq of strings. Handles URIs correctly.\n* `:ednfile` - a file (or URL) containing EDN, that will be decoded and returned as a Clojure structure.\n* `:jsonfile` - a file (or URL) containing JSON, that will be decoded and returned as a Clojure structure.\n* `:yamlfile` - a file (or URL) containing YAML, that will be decoded and returned as a Clojure structure.\n\nYou may also specify a set of allowed values in `:type`, like `:type #{:one :two}`. It must be a set made of keywords or strings, and the\nparameter will be matched to allowed values in a case-insensitive way. Keywords do not need (but are allowed) a \ntrailing colon.  Sets print their allowed values on help and, on mismatches, suggest possible correct values.\n\nFor all options, you can then add:\n\n* `:default` the default value, as expected after conversion\n  * If no default, the value will be passed only if present\n  * If you set `:default :present` this means that CLI-matic will abort if that option is not present;\n    it is effectively a “required option” (and it appears with a trailing asterisk in the help)\n* `:as` is the description that appears in help. It can be a multi-line array, or\n  a single string.\n* `:multiple` if true, the values for all options with the same name are stored in an array\n* `:short`: a shortened name for the command (if a string), or a positional argument if integer (see below).\n* `:env` if set, the default is read from the current value of an env variable you specify. For capture to happen, either the option must be missing, or its value must be invalid. If an option has an `:env` value specified to FOO, its description in the help shows `[$FOO]`.\n* `:spec`: a Spec that will be used to validate the the parameter, after any coercion/transformation.\n\n\n### Return values\n\nThe function called can return an integer; if it does, it is used as an exit code for the shell process.\n\nIf you return a future, or a promise, or a core.async channel, then CLI-matic will wait until it is fulfilled, or there is a value on the channel, and will use that as a return code (at the moment, only works on the JVM).\n\nErrors and exceptions return an exit code of -1; while normal executions (including invocations\nof help) return 0.\n\n### Positional arguments\n\nIf there are values that are not options in your command line, CLI-matic will usually return them in an array of unparsed entries, as strings.\nBut  - if you use the positional syntax for `short`:\n\n\t{:option \"a1\" :short 0 :as \"First addendum\" :type :int :default 23}\n\nYou 'bind' the \toption 'a1' to the first unparsed element; this means that\nyou can apply all presets/defaults/validation rules as if it was a named option.\n\nSo you could call your script as:\n\n\tclj -m calc add --a2 3 5\n\nAnd CLI-matic would set 'a2' to 3 and have \"5\" as an unparsed argument; and then bind it to \"a1\", so it will be cast to an integer. You function will be called with:\n\n\t{:a1 5, :a2 3}\n\nThat is what you wanted from the start.\n\nAt the same time, the named option remains, so you can use either version. Bound entries are not removed from the unparsed command line entries.\n\n### Validation with Spec (and Expound)\n\nCLI-matic can optionally validate any parameter, and the set of parameters you use to call the subcommand function, with Spec, and uses the excellent Expound https://github.com/bhb/expound to produce sane error messages. An example is under `examples/clj` as `toycalc-spec.clj` - see https://github.com/l3nz/cli-matic/blob/master/examples/clj/toycalc-spec.clj\n\nBy using and including Expound as a depencency, you can add error descriptions where the raw Spec would be hard to read, and use a nice set of\npre-built specs with readable descriptions that come with Expound - see https://github.com/bhb/expound/blob/master/src/expound/specs.cljc\n\n\n### Help text generation\n\nCLI-matic comes with pre-packaged help text generators for global and sub-command help.\nThese generators can be overridden by supplying one or more of your own functions in the `:app` section of the configuration:\n\n\n\t(defn my-command-help [setup subcmd]\n\t  \" ... \")\n\t\n\t(defn gen-sub-command-help [setup subcmd]\n\t  \" ... \")\n\t\n\t{:command \"toycalc\"\n\t :global-help my-command-help\n\t :subcmd-help gen-sub-command-help}}\n\nBoth functions receive the the configuration and the sub-command it was called with, and  return a string (or an array of strings) that CLI-matic prints verbatim to the user as the full help text.\n\nSee example in `helpgen.clj`.\n\n## Babashka\n\nThis library is compatible with [Babashka](https://github.com/babashka/babashka) - a native Clojure interpreter for scripting with fast startup. Its main goal is to leverage Clojure in places where you would be using bash otherwise.\n\nIn addition to this library, you need to include babashka's [fork of\nclojure.spec.alpha](https://github.com/babashka/spec.alpha) in your\n`bb.edn`. Also see this project's `bb.edn` for how this project's tests are run\nwith babashka.\n\nSee [Scripting with Babashka](https://github.com/l3nz/cli-matic/blob/master/examples/bb/README.md).\n\n\n## Old (non-recursive) configuration\n\nThe following configuration, that forced you to use exactly one layer, is still supported and translated automagically.\n\n\n\t(def CONFIGURATION\n\t  {:app         {:command     \"toycalc\"\n\t                 :description \"A command-line toy calculator\"\n\t                 :version     \"0.0.1\"}\n\n\t   :global-opts [{:option  \"base\"\n\t                  :as      \"The number base for output\"\n\t                  :type    :int\n\t                  :default 10}]\n\n\t   :commands    [{:command     \"add\"\n\t                  :description \"Adds two numbers together\"\n\t                  :opts        [{:option \"a\" :as \"Addendum 1\" :type :int}\n\t                                {:option \"b\" :as \"Addendum 2\" :type :int :default 0}]              \n\t                  :runs        add_numbers}\n\n\t                 {:command     \"sub\"\n\t                  :description \"Subtracts parameter B from A\"\n\t                  :opts        [{:option \"a\" :as \"Parameter A\" :type :int :default 0}\n\t                                {:option \"b\" :as \"Parameter B\" :type :int :default 0}]\n\t                  :runs        subtract_numbers}\n\t                 ]})\n\nNote that custom help-text generators are not translated, as their arity changed in v0.4.0+\n\n\n\n### Transitive dependencies\n\nCLI-matic currently depends on:\n\n* org.clojure/clojure\n* org.clojure/spec.alpha\n* org.clojure/tools.cli\n* expound\n\n#### Optional dependencies\n\nTo use **JSON decoding**, you need Cheshire `cheshire/cheshire` to be on the classpath; otherwise it will break.\nIf you do not need JSON parsing, you can do without.\n\nTo use **Yaml decoding**, you need `clj-commons/clj-yaml` on your classpath; otherwise it will break.\nIf you do not need YAML parsing, you can do without. Note that up to version 0.4 of cli-matic we used to rely on `io.forward/yaml`,\nbut it used reflection, and so was incompatible with GraalVM native images.\n\nIf Orchestra `orchestra` is present on the classpath, loading most namespaces triggers\nan instrumentation. As we already have Expound, we get easy-to-read messages\nfor free.\n\n\n## Tips \u0026 tricks\n\n### Reducing startup time with skip-macros\n\nIf you run your script with the property `clojure.spec.skip-macros=true` you get significant \nsavings:\n\n\t\ttime clj -J-Dclojure.spec.skip-macros=true -m recap sv\n\t\treal\t0m2.587s - user\t0m6.997 - sys\t0m0.332s\n\nVersus the default:\n\n\t\ttime clj -J-Dclojure.spec.skip-macros=false -m recap sv\n\t\treal\t0m3.141s - user\t0m8.707s - sys\t0m0.391s\n\nSo that's like half a second for free on my machine.\n\n\n### Capturing current version\n\nIf you would like to capture the build environment at compile time (e.g. the exact GIT revision, or when/where \nthe program was built, or the version of your project as defined in `project.clj`) so you can print\nmeaningful version numbers without manual intervention, you may want to include https://github.com/l3nz/say-cheez and \nuse it to provide everything to you.\n\n\n### Writing a stand-alone script with no external deps.edn\n\nEric Normand has a [nice tip](https://gist.github.com/ericnormand/6bb4562c4bc578ef223182e3bb1e72c5) for writing stand-alone scripts that all live in one file:\n\n```\n#!/bin/sh\n#_(\n   #_DEPS is same format as deps.edn. Multiline is okay.\n   DEPS='\n   {:deps \n   \t{cli-matic {:mvn/version \"0.3.3\"}}}\n   '\n\n   #_You can put other options here\n   OPTS='\n   -J-Xms256m -J-Xmx256m \n   -J-client\n   -J-Dclojure.spec.skip-macros=true\n   '\nexec clojure $OPTS -Sdeps \"$DEPS\" \"$0\" \"$@\"\n)\n\n(println \"It works!\")\n\n```\n\nAnd so you have a nice place not to forget to set `skip-macros`!\n\n\n### BabashkaBins\n\n`bbb`\n lets you take a standard Clojure project layout, run it under both JVM Clojure and babashka, and then automates \n the compilation of your project into a static binary with GraalVM for you when it’s time to distribute it.\n\n- https://nikvdp.com/post/bbb/\n- https://github.com/nikvdp/bbb\n\n## Contributing\n\nBefore submitting a bug or pull request, make sure you read [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Similar projects / inspiration\n\n* OCLIF (JavaScript/Node) @ https://github.com/oclif/oclif\n* Cobra (Golang) @ https://github.com/spf13/cobra\n* PicoCLI (Java) @ https://picocli.info/\n\n## License\n\nThe use and distribution terms for this software are covered by the\nEclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)\nwhich can be found in the file epl.html at the root of this distribution.\nBy using this software in any fashion, you are agreeing to be bound by\nthe terms of this license.\n\nYou must not remove this notice, or any other, from this software.\n\t\n## Contributors ✨\n\nThanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):\n\n\u003c!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --\u003e\n\u003c!-- prettier-ignore-start --\u003e\n\u003c!-- markdownlint-disable --\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/jwhitlark\"\u003e\u003cimg src=\"https://avatars0.githubusercontent.com/u/59580?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eJason Whitlark\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=jwhitlark\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/ty-i3\"\u003e\u003cimg src=\"https://avatars3.githubusercontent.com/u/38514663?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003ety-i3\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=ty-i3\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://jeiwan.net/\"\u003e\u003cimg src=\"https://avatars0.githubusercontent.com/u/8029346?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eIvan Kuznetsov\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=Jeiwan\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://cortys.de\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/1737630?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eClemens Damke\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=Cortys\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://choomnuan.com\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/4092543?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eBurin Choomnuan\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=agilecreativity\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"https://github.com/lread\"\u003e\u003cimg src=\"https://avatars2.githubusercontent.com/u/967328?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eLee Read\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"https://github.com/l3nz/cli-matic/commits?author=lread\" title=\"Code\"\u003e💻\u003c/a\u003e\u003c/td\u003e\n    \u003ctd align=\"center\"\u003e\u003ca href=\"http://blog.fikesfarm.com\"\u003e\u003cimg src=\"https://avatars1.githubusercontent.com/u/1723464?v=4\" width=\"100px;\" alt=\"\"/\u003e\u003cbr /\u003e\u003csub\u003e\u003cb\u003eMike Fikes\u003c/b\u003e\u003c/sub\u003e\u003c/a\u003e\u003cbr /\u003e\u003ca href=\"#question-mfikes\" title=\"Answering Questions\"\u003e💬\u003c/a\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\u003c!-- markdownlint-enable --\u003e\n\u003c!-- prettier-ignore-end --\u003e\n\u003c!-- ALL-CONTRIBUTORS-LIST:END --\u003e\n\nThis project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl3nz%2Fcli-matic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fl3nz%2Fcli-matic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fl3nz%2Fcli-matic/lists"}