{"id":26693807,"url":"https://github.com/alwaysai/alwayscli","last_synced_at":"2026-03-11T04:03:15.666Z","repository":{"id":34243287,"uuid":"173008533","full_name":"alwaysai/alwayscli","owner":"alwaysai","description":"A framework for building command-line interfaces in Node.js","archived":false,"fork":false,"pushed_at":"2024-05-06T21:21:50.000Z","size":555,"stargazers_count":6,"open_issues_count":3,"forks_count":2,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-06-22T08:47:05.670Z","etag":null,"topics":["alwaysai","cli","nodejs","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/alwaysai.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"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":"2019-02-27T23:51:16.000Z","updated_at":"2024-05-06T21:21:52.000Z","dependencies_parsed_at":"2024-05-06T20:36:57.675Z","dependency_job_id":"25e94fbb-b0fc-46a5-9b1b-2d1cffd74fcb","html_url":"https://github.com/alwaysai/alwayscli","commit_stats":{"total_commits":72,"total_committers":4,"mean_commits":18.0,"dds":"0.48611111111111116","last_synced_commit":"b9426b40180d52e5513e1ca0b74db92570265f60"},"previous_names":["alwaysai/always-cli"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/alwaysai/alwayscli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysai%2Falwayscli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysai%2Falwayscli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysai%2Falwayscli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysai%2Falwayscli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alwaysai","download_url":"https://codeload.github.com/alwaysai/alwayscli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alwaysai%2Falwayscli/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263469457,"owners_count":23471529,"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":["alwaysai","cli","nodejs","typescript"],"created_at":"2025-03-26T18:27:21.267Z","updated_at":"2026-03-11T04:03:10.622Z","avatar_url":"https://github.com/alwaysai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `alwaysCLI` [![Build Status](https://travis-ci.com/alwaysai/alwayscli.svg?branch=master)](https://travis-ci.com/alwaysai/alwayscli)\n\nA framework for building command-line interfaces (CLIs) in Node.js. This package includes runtime JavaScript files suitable for Node.js \u003e=8 as well as the corresponding TypeScript type declarations.\n\n## Usage\n\n```\nnpm install @alwaysai/alwayscli\n```\n\nIn an `alwaysCLI` program, commands are organized into a tree. Each \"leaf\" represents an action whereas \"branches\" collect and organize leaves. For example, in the command `alwaysai user login`, `alwaysai` is the \"root\" command (a branch), `user` is a branch of commands related to user authentication and `login` is the action leaf. Your CLI need not have branches. Here is a simple CLI whose root is a leaf:\n```ts\n// readme.ts\nimport {\n  CliLeaf,\n  CliFlagInput,\n  CliNumberArrayInput,\n  runCliAndExit,\n} from '@alwaysai/alwayscli';\n\nexport const root = CliLeaf({\n  name: 'multiply',\n  description: 'Multiply numbers and print the result',\n  positionalInput: CliNumberArrayInput({ required: true }),\n  namedInputs: {\n    squared: CliFlagInput({\n      description: 'Square the result before printing it',\n    }),\n  },\n  action(args, { squared }) {\n    const multiplied = args.reduce((a, b) =\u003e a * b, 1);\n    if (squared) {\n      return multiplied * multiplied;\n    }\n    return multiplied;\n  },\n});\n\nif (require.main === module) {\n  runCliAndExit(root);\n}\n```\n\nHere's how that behaves as a CLI.\n```\n$ ts-node readme.ts\nUsage: multiply \u003cnum0\u003e [...] [\u003coptions\u003e]\n\n   Multiply numbers and print the result\n\nOptions:\n\n   [--squared] : Square the result before printing it\n\nError: \"\u003cnum0\u003e [...]\": Value is required\n```\n\nWith arguments:\n```\n$ ts-node readme.ts 1 2 3\n6\n$ ts-node readme.ts 1 2 3 --squared\n36\n```\nThe `if (require.main === module)` conditional is Node.js for \"if this file is the entry point\", which is `true` when you do `ts-node readme.ts`, but not when you do `import { root } from './readme.ts'` for unit tests for example.\n\nMore generally the usage of an `alwaysCLI` CLI is:\n```\n\u003cprogram\u003e \u003cbranch\u003e \u003cleaf\u003e \u003cpositional-args\u003e --name \u003cnamed-args\u003e -- \u003cescaped-args\u003e\n```\nTo invoke an action the user provides (in order):\n- zero or more branch names\n- a leaf name\n- zero or more positional args\n- zero or more \"options\" (inputs of the form `--foo bar`)\n\n## API\n### Input\u003cT, U\u003e\nTODO\n\n### CliLeaf({name, description?, args?, options?, action, hidden?, version?})\nA factory for creating \"action\" commands. Returns the newly-created `leaf`.\n\n#### name\nIf this \"leaf\" is a subcommand, `name` is the string that the user will pass as the \"subcommand\" argument to invoke this action. If this \"leaf\" is the root command, `name` should be the CLI's name.\n\n#### description\n(Optional) A string that will be included in `Usage:` if present.\n\n#### positionalInput\n(Optional) An `Input` for\n\n#### namedInputs\n(Optional) An object of named `Input`s, for example:\n```ts\nconst options = {\n  path: createStringInput({\n    description: 'An absolute or relative path',\n  }),\n}\n```\nThe `args` and `options` properties define how the command-line arguments get parsed and transformed before being passed into the `action` function.\n\n#### action(args, options)\nThe function that defines your command logic. `action` can return a value synchronously like in the \"multiply\" example above, or it can be an `async` function that returns a `Promise`. If `action` returns/resolves a value, that value is `console.log`ged before the CLI exits. If `action` throws/rejects, the exception is `console.log`ged before the CLI exits. That means that if you don't want the user to see a stack trace, your `action` should throw a `string` instead of an `Error` object. The type of the `args` argument received by `action` is derived by the `args` property of the leaf. Similarly, the `options` argument type is derived from `leaf.options`.\n\n#### hidden\n(Optional) `boolean`\n\n#### version\n(Optional) `string`. If provided, this string will be printed when the user does `cli --version` or `cli -v`. If this value is not provided, alwaysCLI will attempt to find a version string in your package.json file.\n\n### CliBranch({name, description, subcommands, hidden?})\nA factory function similar to `CliLeaf`. Returns the newly-created `Branch` object.\n\n#### name\nIf this \"branch\" is not the root command, `name` is the string that the user will pass as the \"subcommand\" argument to invoke actions in this part of the command tree. If this \"branch\" command is the root command, `name` should be the CLI's name.\n\n#### description\n(Optional) A string that will be included in `Usage:` if present.\n\n#### subcommands\nAn array of `Branch` and/or `Leaf` objects.\n\n#### hidden\n(Optional) `boolean`\n\n### createCli(root)\nReturns a function of the form `(...args: string[]) =\u003e Promise\u003cany\u003e` that can be invoked as e.g. `cli('foo', 'bar')` for unit tests or as `cli(process.argv.slice(2))` in an executable CLI script.\n\n#### root\nA `Leaf` or `Branch`\n\n### ArgvInterface\n`cli` is a function that takes command-line arguments (strings) as input and returns a `Promise` representing the execution of the arguments. We export `cli` so that we can unit test it [like so](src/examples/__tests__/readme.test.ts).\n\n## More information\nThis library has a couple dozen unit tests with \u003e90% coverage. If you want to see more examples of how things works, check out the `.test.ts` files in the [src](src) directory. Also check out [src/examples](src/examples). If you encounter any bugs or have any questions or feature requests, please don't hesitate to file an issue or submit a pull request on this project's repository on GitHub.\n\n## Release procedure\nTo release the package to [npmjs.org](https://www.npmjs.com/package/@alwaysai/alwayscli) follow the steps:\n- publish new version: \u003ccode\u003enpm run publish:\u003cmajor|minor|patch\u003e\u003c/code\u003e\n- check the [github pipeline](https://github.com/alwaysai/alwayscli/actions) running, if successful a new version will be created and published to npmjs.org\n- to get the auto-generated commit and tags, simply pull: \u003ccode\u003egit pull\u003c/code\u003e\n\n## Pipeline is lintng, unit testing and building package on:\n- [x] Ubuntu latest / Node.js: 16.x, 18.x, 20.x, 22.x\n- [x] MacOS latest / Node.js: 16.x, 18.x, 20.x, 22.x\n- [x] Windows latest / Node.js: 16.x, 18.x, 20.x, 22.x\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwaysai%2Falwayscli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falwaysai%2Falwayscli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falwaysai%2Falwayscli/lists"}