{"id":15011205,"url":"https://github.com/diegozoracky/cliss","last_synced_at":"2025-04-09T19:04:31.018Z","repository":{"id":49598160,"uuid":"109509975","full_name":"DiegoZoracKy/cliss","owner":"DiegoZoracKy","description":"CLI Simple, Stupid. Automatic discovery of parameters names. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand. Support to sync / async. Support to subcommands down to N levels.","archived":false,"fork":false,"pushed_at":"2021-06-12T15:53:07.000Z","size":59,"stargazers_count":10,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-23T21:01:38.228Z","etag":null,"topics":["cli","command-line","command-line-interface","nodejs"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DiegoZoracKy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-04T16:13:27.000Z","updated_at":"2023-10-16T05:55:13.000Z","dependencies_parsed_at":"2022-09-16T15:01:14.634Z","dependency_job_id":null,"html_url":"https://github.com/DiegoZoracKy/cliss","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoZoracKy%2Fcliss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoZoracKy%2Fcliss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoZoracKy%2Fcliss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DiegoZoracKy%2Fcliss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DiegoZoracKy","download_url":"https://codeload.github.com/DiegoZoracKy/cliss/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248094988,"owners_count":21046770,"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":["cli","command-line","command-line-interface","nodejs"],"created_at":"2024-09-24T19:39:41.605Z","updated_at":"2025-04-09T19:04:30.999Z","avatar_url":"https://github.com/DiegoZoracKy.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CLIss\n\n[![Build Status](https://api.travis-ci.org/DiegoZoracKy/cliss.svg)](https://travis-ci.org/DiegoZoracKy/cliss) [![npm](https://img.shields.io/npm/v/cliss.svg)]() [![npm](https://img.shields.io/npm/l/cliss.svg)]()\n\nCLI Simple, Stupid. Automatic discovery of parameters names and support to subcommands down to N levels. Provides an easy and minimal setup by passing in only a function reference without the need of declaring all expected options names or create a help section by hand.\n\nSide note: It is worth taking a look at [MagiCLI](https://github.com/DiegoZoracKy/magicli), which is a module capable to create a CLI interface automatically for a module, instead of creating one by hand.\n\n## Goals\n\n * Simple and easy API\n * Easy minimal setup, extracting options names from functions parameters\n * Out of the box support to sync or async (Promise) functions\n * Subcommands down to N levels\n * Automatic Help section generation, that can be improved only when needed\n\n## Installation\n\n```bash\n$ npm install cliss\n```\n\n## Usage\n\nThrough this section we'll be going from the most minimal usage of the module, where options names are extracted from functions parameters:\n```javascript\nconst func = (param1, param2) =\u003e `${param1}_${param2}`;\ncliss(func);\n```\n\nto a version using all the possible options it provides:\n\n```javascript\nconst cliSpec = {\n\tname,\n\tdescription,\n\tversion,\n\toptions: [{\n\t\tname,\n\t\tdescription,\n\t\trequired,\n\t\ttype\n\t}],\n\tpipe: {\n\t\tstdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) =\u003e {},\n\t\tbefore: (args, positionalArgs, argsAfterEndOfOptions) =\u003e {},\n\t\tafter: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) =\u003e {}\n\t},\n\taction: () =\u003e {},\n\tcommands: [{}]\n};\n\nconst clissOptions = {\n\tcommand: {\n\t\tsubcommandsDelimiter\n\t},\n\toptions: {\n\t\tvalidateRequiredParameters\n\t},\n\tversion: {\n\t\toption\n\t},\n\thelp: {\n\t\toption,\n\t\tstripAnsi\n\t},\n\tpipe: {\n\t\tstdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) =\u003e {},\n\t\tbefore: (args, positionalArgs, argsAfterEndOfOptions) =\u003e {},\n\t\tafter: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) =\u003e {}\n\t}\n};\n\ncliss(cliSpec, clissOptions);\n```\n\n### A CLI for a function (the most simple and minimal use case)\n`cliss(functionReference)`\n\nCreating a CLI for a function by doing nothing more than passing it as a parameter to cliss. The options names will be the same as the parameters expected by the function.\n\n```javascript\n'use strict';\nconst cliss = require('cliss');\n\nconst aFunctionWithWeirdParametersDefinition = (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) =\u003e {\n\tlet result = `param1: ${param1} \\n`;\n\tresult += `param2: ${param2} \\n`;\n\tresult += `param3: ${param3} \\n`;\n\tresult += `args: ${args.join(',')}`;\n\n\treturn result;Run the program passing with the following options:\n\n\n};\n\ncliss(aFunctionWithWeirdParametersDefinition);\n```\nCalling it via CLI with `--help` will give you:\n\n```bash\nOptions:\n\n  --param1\n  --param2\n  --param3\n  --args\n```\n\nPassing in the options:\n`node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 --args=a --args=r --args=g --args=s`\n\nOr passing options + arguments (arguments for the \"...args\" parameter in this case):\n`node cli.js --param2=PARAM2 --param1=PARAM1 --param3=PARAM3 a r g s`\n\nWill result in:\n\n```bash\nparam1: PARAM1\nparam2: PARAM2\nparam3: PARAM3\nargs: a,r,g,s\n```\nNote that the order of the options doesn't need to match the order of the parameters.\n\n### Improving the help section\n`cliss(cliSpec)`\n\nGreat, but probably one would like to improve a bit the `--help` section of the module, by providing to the end user the **name** (the command's name for calling it via CLI), **description** and **version** of the module. In this case a *Object Literal* will be used instead of just a function reference.\n\n```javascript\n'use strict';\nconst cliss = require('../');\n\ncliss({\n\tname: 'some-command',\n\tdescription: 'Just an example that will do nothing but concat all the parameters.',\n\tversion: '1.0.0',\n\taction: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) =\u003e {\n\t\tlet result = `param1: ${param1} \\n`;\n\t\tresult += `param2: ${param2} \\n`;\n\t\tresult += `param3: ${param3} \\n`;\n\t\tresult += `args: ${args.join(',')}`;\n\n\t\treturn result;\n\t}\n});\n```\n\nNow, when calling it with `--help`, a better help section will be shown:\n\n```bash\nDescription:\n\n  Just an example that will do nothing but concat all the parameters.\n\nUsage:\n\n  $ some-command  [options] [args...]\n\nOptions:\n\n  --param1\n  --param2\n  --param3\n  --args\n```\n\n### Providing more information about the expected options\n`cliss(cliSpec)`\n\nThe options were effortlessly extracted from the parameters names, but **cliss** provides a way for one to provide more information about each of them. The *Object Literal* passed in the *cliSpec* parameter can have a property named **options**, which expects an *Array* of objects, containing the **name** of the option plus some of the following properties:\n\n* **required**\nTo tell if the parameter is required.\n\n* **description**\nTo give hints or explain what the option is about.\n\n* **type**\nTo define how the parser should treat the option (Array, Object, String, Number, etc.). Check [yargs-parser](https://github.com/yargs/yargs-parser) for instructions about *type*, as it is the engine being used to parse the options.\n\n* **alias**\nTo define an alias for the option.\n\nFollowing the last example, let's improve it to:\n * give more information about **param1**\n * check **args** as required\n\n```javascript\ncliss({\n\tname: 'some-command',\n\tdescription: 'Just an example that will do nothing but concat all the parameters.',\n\tversion: '1.0.0',\n\toptions: [{\n\t\tname: 'param1',\n\t\tdescription: 'This param is the base value to compute everything else.',\n\t\trequired: true,\n\t\ttype: 'String'\n\t}, {\n\t\tname: 'args',\n\t\trequired: true\n\t}],\n\taction: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) =\u003e {\n\t\tlet result = `param1: ${param1} \\n`;\n\t\tresult += `param2: ${param2} \\n`;\n\t\tresult += `param3: ${param3} \\n`;\n\t\tresult += `args: ${args.join(',')}`;\n\n\t\treturn result;\n\t}\n});\n```\n\nCall `--help`, and note that the *Usage* section will also be affected. Now *[options] [args...]* will be shown as *\u003coptions\u003e \u003cargs...\u003e*, because both of them are required.\n\n```bash\nDescription:\n\n  Just an example that will do nothing but logging all the parameters.\n\nUsage:\n\n  $ some-command  \u003coptions\u003e \u003cargs...\u003e\n\nOptions:\n\n  --param1 String   Required - This param is the base value to compute\n                    everything else.\n  --param2\n  --param3\n  --args            Required\n```\n\nRun the program with the following options:\n`node cli.js --param1=001 --param2=002 --param3=PARAM3 a r g s`\n\nAnd check the result to see how *param1* was indeed treated as a string, while *param2* was parsed as a number:\n\n```bash\nparam1: 001\nparam2: 2\nparam3: PARAM3\nargs: a,r,g,s\n```\n\n### Pipe: STDIN, Before and After\n`cliss(cliSpec)`\n\nA property named **pipe** can also be defined on *cliSpec* in order to handle **stdin** and also, some steps of the execution flow (**before** and **after**). To define a single handle for all the commands, the **pipe** option can be defined on [Cliss options](#cliss-options) as will be shown later on the documentation. The pipeline execution of a command is:\n\n**stdin** *(command.pipe.stdin || clissOptions.pipe.stdin)* =\u003e\n\n**clissOptions.pipe.before** =\u003e\n\n**command.pipe.before** =\u003e\n\n**command.action** =\u003e\n\n**command.pipe.after** =\u003e\n\n**clissOptions.pipe.after** =\u003e\n\n**stdout**\n\nWhere each of these steps can be handled if needed.\n\nThe properties expected by **pipe** are:\n\n* **stdin**\n`(stdinValue, args, positionalArgs, argsAfterEndOfOptions)`\n\n* **before**\n`(args, positionalArgs, argsAfterEndOfOptions)`\nTo transform the data being input, before it is passed in to the main command action.\n\n* **after**\n`(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)`\nTo transform the *output* (for example, to JSON.stringify an *Object Literal*)\n\nNote: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline.\n\nTo better explain with an example, let's modify the previous one to:\n\n* get *param3* from **stdin**\n* use **before** to reverse *...args* array\n* use **after** to decorate the output\n\nCheck the *pipe* property on the following code:\n\n```javascript\ncliss({\n\tname: 'some-command',\n\tdescription: 'Just an example that will do nothing but concat all the parameters.',\n\tversion: '1.0.0',\n\toptions: [{\n\t\tname: 'param1',\n\t\tdescription: 'This param is needed to compute everything else.',\n\t\trequired: true,\n\t\ttype: 'String'\n\t}, {\n\t\tname: 'args',\n\t\trequired: true\n\t}],\n\tpipe: {\n\t\tstdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\targs.param3 = stdinValue;\n\t\t\treturn args;\n\t\t},\n\t\tbefore: (args, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\tpositionalArgs.reverse();\n\t\t\treturn args;\n\t\t},\n\t\tafter: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\treturn `======\\n${result}\\n======`;\n\t\t}\n\t},\n\taction: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) =\u003e {\n\t\tlet result = `param1: ${param1} \\n`;\n\t\tresult += `param2: ${param2} \\n`;\n\t\tresult += `param3: ${param3} \\n`;\n\t\tresult += `args: ${args.join(',')}`;\n\n\t\treturn result;\n\t}\n});\n```\n\nCalling it as:\n`echo \"fromSTDIN\" | node cli.js --param1=001 --param2=002 a r g s`\n\nWill result in:\n\n```bash\n=======\nparam1: 001\nparam2: 2\nparam3: fromSTDIN\nargs: s,g,r,a\n=======\n```\n\n### Subcommands\n\nSubcommands can be defined in a very simple way. Thinking naturally, a subcommand should be just a command that comes nested into another one, and it is exactly how it's done.\n\nHere one more property of the *cliSpec* is introduced: **commands**. It is an *Array* that can contains N commands, including the **commands** property (commands can be nested down to N levels).\n\nAs each subcommand is a command itself, they also counts with its own `--help` section, and possibly its own `--version` (if it is not defined for a subcommand, the one defined for the root will be shown).\n\nThe following example will introduce:\n\n* 1 subcommand, thas has no action, and contains more 2 subcommands\n* 1 subcommand that contains an action\n\n```javascript\ncliss({\n\tname: 'some-command',\n\tdescription: 'Just an example that will do nothing but concat all the parameters.',\n\tversion: '1.0.0',\n\toptions: [{\n\t\tname: 'param1',\n\t\tdescription: 'This param is the base value to compute everything else.',\n\t\trequired: true,\n\t\ttype: 'String'\n\t}, {\n\t\tname: 'args',\n\t\trequired: true\n\t}],\n\tpipe: {\n\t\tstdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\targs.param3 = stdinValue;\n\t\t\treturn args;\n\t\t},\n\t\tbefore: (args, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\tpositionalArgs.reverse();\n\t\t\treturn args;\n\t\t},\n\t\tafter: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) =\u003e {\n\t\t\treturn `======\\n${result}\\n======`;\n\t\t}\n\t},\n\taction: (param1, param2, { someProp: [[ param3 ]] = [[]] } = {}, ...args) =\u003e {\n\t\tlet result = `param1: ${param1} \\n`;\n\t\tresult += `param2: ${param2} \\n`;\n\t\tresult += `param3: ${param3} \\n`;\n\t\tresult += `args: ${args.join(',')}`;\n\n\t\treturn result;\n\t},\n\tcommands: [{\n\t\tname: 'subcommand1',\n\t\tcommands: [{\n\t\t\tname: 'action1',\n\t\t\toptions: [{\n\t\t\t\tname: 'param',\n\t\t\t\trequired: true\n\t\t\t}],\n\t\t\taction: param =\u003e `subcommand1 action1 param: ${param}`\n\t\t}, {\n\t\t\tname: 'action2',\n\t\t\taction: () =\u003e 'subcommand1 action2'\n\t\t}]\n\t}, {\n\t\tname: 'subcommand2',\n\t\taction: () =\u003e console.log('subcommand2')\n\t}]\n});\n```\n\nCall `--help` to see that a new section *Commands:* is presented:\n\n```bash\nDescription:\n\n  Just an example that will do nothing but concat all the parameters.\n\nUsage:\n\n  $ some-command  \u003coptions\u003e \u003cargs...\u003e\n  $ some-command  [command]\n\nOptions:\n\n  --param1 String   Required - This param is needed to compute\n                    everything else.\n  --param2\n  --param3\n  --args            Required\n\nCommands:\n\n  subcommand1\n  subcommand2\n```\n\nEach subcomand has its own help section, check:\n`node cli.js subcommand1 --help`\n\n```bash\nUsage:\n\n  $ some-command subcommand1\n```\n\n`node cli.js subcommand2 --help`:\n\n```bash\nUsage:\n\n  $ some-command subcommand2 \u003ccommand\u003e\n\nCommands:\n\n  action1\n  action2\n```\n\nJust call the commands names separated by space:\n`node cli.js subcommand2 action1 --param=VALUE`\n\nResult:\n\n```bash\nsubcommand2 action1 param: VALUE\n```\n\n### Cliss options\n`cliss(cliSpec, clissOptions)`\n\nAn `Object Literal` with the following options can be passed in as the second parameter to cliss:\n\n* **command**\n\n  * **subcommandsDelimiter**\n  To define a delimiter for a subcommand to be used instead of a white space. For example, if `'-'` is passed in, the subcommands should be called as `subcommand1-action1` instead of `subcommand1 action1`.\n\n* **options**\n\n  * **validateRequiredParameters**\n  If set to `true`, the required parameters will be checked before the command action is called, and the help section will be shown in case a required parameter is missing.\n\n* **help**\n  * **option**\n  \tTo define a different option name to show the help section. For example, if `'helpsection'` is passed in, `--helpsection` must be used instead of `--help`.\n\n  * **stripAnsi**\n  \tSet to `true` to strip all ansi escape codes (colors, underline, etc.) and output just a raw text.\n\n* **version**\n  * **option**\n  To define a different option name to show the version. For example, if `'moduleversion'` is passed in, `--moduleversion` must be used instead of `--version`.\n\n* **pipe**\nAs it is defined on *cliSpec* for each command, **pipe** can also be defined in *clissOptions* to implement for all commands a unique way to handle **stdin** and also, some steps of the execution flow (**before** and **after**) in case it is needed. The pipeline execution of a command is:\n**stdin** *(command.pipe.stdin || clissOptions.pipe.stdin)* =\u003e\n=\u003e **clissOptions.pipe.before** =\u003e\n=\u003e **command.pipe.before** =\u003e\n=\u003e **command.action** =\u003e\n=\u003e **command.pipe.after** =\u003e\n=\u003e **clissOptions.pipe.after**\n=\u003e **stdout**\nThe properties expected by **pipe** are:\n\n  * **stdin**\n`(stdinValue, args, positionalArgs, argsAfterEndOfOptions)`\n\n  * **before**\n`(args, positionalArgs, argsAfterEndOfOptions)`\nTo transform the data being input, before it is passed in to the main command action.\n\n  * **after**\n`(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)`\nTo transform the *output* (for example, to JSON.stringify an *Object Literal*)\n\nNote: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegozoracky%2Fcliss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdiegozoracky%2Fcliss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdiegozoracky%2Fcliss/lists"}