@cloud-copilot/cli 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Cloud Copilot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # CLI
2
+
3
+ Utilities for standardizing working CLIs in Typescript.
4
+
5
+ - Standardizes the way subcommands, arguments, and operands are parsed and validated
6
+ - Includes "fuzzy matching" of subcommands and arguments, so users can provide partial names if they match only one subcommand or argument
7
+ - Allows users to default arguments with environment variables
8
+ - Automatically generates a help text for the user
9
+ - Provides a type safe response of the parsed arguments
10
+ - Has zero dependencies
11
+
12
+ I wrote this because I'm building several CLI applications and want to have a standard parsing of arguments that users can rely on across all of them.
13
+
14
+ I was inspired by https://github.com/lirantal/nodejs-cli-apps-best-practices and https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html to follow best practices for accepting arguments and environment variables while keeping my scope extremely limited and having no dependencies.
15
+
16
+ ## Example
17
+
18
+ ```typescript
19
+ import {parseCliArguments} from '@cloud-copilot/cli';
20
+
21
+ const parsedArguments = const parsed2 = parseArguments(
22
+ 'my-command',
23
+ // optional subcommands
24
+ init: {
25
+ description: 'Initialize the project',
26
+ // Subcommand Specific Arguments
27
+ options: {
28
+ s3: {
29
+ description: 'Use S3 as the storage',
30
+ character: 's',
31
+ values: 'none'
32
+ },
33
+ language: {
34
+ description: 'The language to use',
35
+ values: 'single',
36
+ type: 'enum',
37
+ validValues: ['typescript', 'javascript']
38
+ }
39
+ }
40
+ },
41
+ download: {
42
+ description: 'Download a file',
43
+ // Subcommand Specific Arguments
44
+ options: {
45
+ url: {
46
+ description: 'The URL to download from',
47
+ values: 'single',
48
+ type: 'string'
49
+ }
50
+ }
51
+ },
52
+ // Global Arguments
53
+ {
54
+ regions: { type: 'string', values: 'multiple', description: 'Regions to deploy to' },
55
+ account: { type: 'string', values: 'single', description: 'AWS account to deploy to' },
56
+ ssl: { values: 'none', description: 'Enable SSL', character: 's', default: false },
57
+ port: { type: 'number', values: 'single', description: 'Port to use' },
58
+ points: { type: 'number', values: 'multiple', description: 'Data points' }
59
+ },
60
+ {}
61
+ )
62
+ ```
63
+
64
+ This will automatically pull in the arguments from the command line and validate them. If any arguments are not valid, it will throw an error with the message of what is wrong. If all arguments and subcommands are valid, it will return an object with a type safe response.
65
+
66
+ ```typescript
67
+ parsedArguments.command // string | undefined
68
+ parsedArguments.args.regions // string[]
69
+ parsedArguments.args.account // string | undefined
70
+ parsedArguments.args.ssl // boolean
71
+ parsedArguments.args.port // number | undefined
72
+ parsedArguments.args.points // number[]
73
+
74
+ parsedArguments.anyValues // boolean, whether any values were provided on the CLI
75
+
76
+ if (parsedArguments.command === 'init') {
77
+ // Type Checked command specific arguments
78
+ parsedArguments.args.s3 // boolean
79
+ }
80
+ ```
81
+
82
+ If the user provides `--help` a help message will be printed out with the available subcommands and arguments.
83
+
84
+ ## Installation
85
+
86
+ ```bash
87
+ npm install @cloud-copilot/cli
88
+ ```
89
+
90
+ ## Parsing CLI Input
91
+
92
+ Arguments are parsed into three things:
93
+
94
+ ```bash
95
+ my-command [subcommand] [arguments] [--] [operands]
96
+ ```
97
+
98
+ - The subcommand which is the first argument and is optional based on your configuration
99
+ - The arguments that start with `--` (or optionally `-` for boolean flags) in the CLI
100
+ - The operands, which are the remaining values after the arguments. If provided, all arguments after the literal ` -- ` are considered operands.
101
+
102
+ ### Parsing subcommands
103
+
104
+ If you provide a list of subcommands, the first argument will be parsed as a subcommand if it does not start with a `-`. If the subcommand is not provided, it will be `undefined`.
105
+
106
+ Partial matching will be used. If only one subcommand matches the string from the user input, the subcommand it matches will be returned. If more than one subcommand matches, an error will be thrown. If the string provided does not match any subcommand, an error will be thrown.
107
+
108
+ ### Parsing Arguments
109
+
110
+ Arguments are parsed from the CLI input. They are generally parsed as `--name value` or `--name value1 value2 value3`. If the argument is a boolean flag, it can be provided as `--name` or `-n`. Arguments are case insensitive.
111
+
112
+ #### Boolean Arguments
113
+
114
+ Boolean values can be provided as `--name` or `-n`. If the argument is a boolean, the value will be `true` if the argument is provided and `false` if it is not. If an argument is passed to a boolean argument, an error will be thrown.
115
+
116
+ Multiple single character boolean flags can be combined into a single argument. For example, `-abc` is equivalent to `-a -b -c`.
117
+
118
+ #### String or Number Arguments
119
+
120
+ String or number arguments can be provided as `--name value` or `--name value1 value2 value3`. If the argument is a single value, it will be parsed as a single value. If the argument is multiple values, it will be parsed as an array of strings or numbers.
121
+
122
+ String or number arguments will throw an error if:
123
+
124
+ - The flag is specified an no values are provided
125
+ - The argument accepts only one value and multiple values are provided
126
+ - The argument is a number and a non-number value is provided
127
+
128
+ #### Enum Arguments
129
+
130
+ Enum arguments can be provided as `--name value1 value2`. The values must be one of the values provided in the `values` field of the argument configuration. The values are returned as a string or a string array.
131
+
132
+ Enum arguments will throw an error if:
133
+
134
+ - The flag is specified an no values are provided
135
+ - The argument accepts only one value and multiple values are provided
136
+ - Any of the arguments provided are not in the list of allowed values.
137
+
138
+ If the value is not one of the values provided, an error will be thrown.
139
+
140
+ If a string or number argument is provided with no values an error will be thrown.
141
+
142
+ ### Operands
143
+
144
+ Operands are the remaining values after the arguments.
145
+ If there are no arguments, all strings after the subcommand (if any) are considered operands.
146
+ If the last argument is a boolean all strings after the boolean are considered operands.
147
+ If the last argument is a single value all the strings after the value for the argument are considered operands.
148
+ If the last argument is multiple values, use ` -- ` to separate the arguments from the operands.
149
+
150
+ ### Environment Variables
151
+
152
+ If an `environmentPrefix` is specified in `additionalArgs`, the library will look for environment variables that start with the prefix and use them as defaults for the arguments. For example, if the prefix is `MY_APP`, the library will look for environment variables like `MY_APP_REGIONS`, `MY_APP_ACCOUNT`, etc. These variables will be validated just like the CLI arguments. Any values provided on the CLI will override the environment variables.
153
+
154
+ ## Printing Help
155
+
156
+ If the user provides `--help` as an argument the library will print out a help message with the available subcommands and arguments. This can also be done manually with the function `printHelpContents` which accepts the same arguments as `parseCliArguments`.
157
+
158
+ ```typescript
159
+ //Given this config
160
+ parseCliArguments(
161
+ 'my-command',
162
+ // optional commands
163
+ [
164
+ { name: 'init', description: 'Initialize my environment' },
165
+ {
166
+ name: 'execute',
167
+ description: 'Run the download'
168
+ }
169
+ ],
170
+ {
171
+ expandAsterisk: {
172
+ character: 'e',
173
+ description: 'Expand a single wildcard asterisk (*) to all possible values',
174
+ values: 'none'
175
+ },
176
+ invalidActionBehavior: {
177
+ description: 'Behavior when an invalid action is encountered, defaults to remove',
178
+ validValues: ['remove', 'error', 'include'],
179
+ type: 'enum',
180
+ values: 'single'
181
+ },
182
+ readWaitMs: {
183
+ description: 'Milliseconds to wait between read operations',
184
+ type: 'number',
185
+ values: 'single'
186
+ }
187
+ },
188
+ {
189
+ operandsName: 'action'
190
+ }
191
+ )
192
+ ```
193
+
194
+ ````
195
+
196
+ ```bash
197
+ $ my-command --help
198
+ Usage: my-command [command] [options] [flags] [--] [action1] [action2]
199
+ Commands:
200
+ init : Initialize my environment
201
+ execute: Run the download
202
+ Options:
203
+ --expand-asterisk: (-e) Expand a single wildcard asterisk (*) to all possible values.
204
+ --invalid-action-behavior: Behavior when an invalid action is encountered, defaults to remove. Must
205
+ be one of: remove, error, include.
206
+ --read-wait-ms: Milliseconds to wait between read operations. One number required
207
+ --help: Print this help message and exit
208
+ ````
209
+
210
+ ## API
211
+
212
+ `parseCliArguments` and `printHelpContents` are the two main functions for arguments and help text. They take the same arguments.
213
+
214
+ - `commandName` - The name of the command. This is used in the help text.
215
+ - `subcommands` - An object or sub commands that can be provided. The key is the name of the subcommand and the value is the configuration. Can be an empty object `{}`.
216
+ - `description` - The description of the subcommand. This is used in the help text.
217
+ - `options` - An object of argument definitions. The key is the name of the argument that will be returned in your results and the value is the configuration. Option objects are the same as described in `cliOptions`
218
+ - `cliOptions` - An object of argument definitions. The key is the name of the argument that will be returned in your results and the value is the configuration. One of
219
+
220
+ - Standard Argument:
221
+
222
+ - `values` - The number of values the argument accepts. Can be `single` or `multiple`.
223
+ - `type` - The type of the argument. Can be `string` or `number`.
224
+ - `description` - The description of the argument. This is used in the help text.
225
+
226
+ - Enum Argument:
227
+
228
+ - `values` - The number of values the argument accepts. Can be `single` or `multiple`.
229
+ - `type` - The type of the argument. Must be `enum`.
230
+ - `validValues` - An array of valid values for the argument.
231
+ - `description` - The description of the argument. This is used in the help text.
232
+
233
+ - Boolean Argument:
234
+
235
+ - `character` - The single character flag for the argument.
236
+ - `values` - The number of values the argument accepts. Must be `none`.
237
+ - `description` - The description of the argument. This is used in the help text.
238
+
239
+ - `additionalArgs` - An object of additional arguments. This can include:
240
+
241
+ - `version` - The version of the command. If this is provided the argument `--version` will print out the version and exit.
242
+ - `args` - Override the string array or arguments to parse, by default it will use `process.argv.slice(2)`.
243
+ - `env` - Override the environment variables to use, by default it will use `process.env`.
244
+ - `envPrefix` - A prefix to use for environment variables. If provided, the library will look for environment variables that start with the prefix and use them as defaults for the arguments. For example, if the prefix is `MY_APP`, the library will look for environment variables like `MY_APP_REGIONS`, `MY_APP_ACCOUNT`, etc. These variables will be validated just like the CLI arguments. Any values provided on the CLI will override the environment variables.
245
+ - `operandsName` - The name of the operands. This is used in the help text. By default, this is `operand`.
246
+ - `requireSubcommand` - If true, a subcommand is required. By default, this is false.
247
+
248
+ ## Reading from stdin
249
+
250
+ The function `readStdin` can be used to read from stdin. It returns a promise that resolves with the string read from stdin.
251
+
252
+ ```typescript
253
+ import { readStdin } from '@cloud-copilot/cli'
254
+
255
+ const stdin = await readStdin(undefined)
256
+ ```
257
+
258
+ It optionally takes a read timeout in milliseconds to wait to receive the first byte before it assumes there is no input. This is useful if for instance your CLI is being piped input from another process that may more than a few seconds, such as a `curl` command to an API.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * A boolean argument that has no values.
3
+ */
4
+ export type BooleanArgument = {
5
+ /**
6
+ * The single character flag for the argument.
7
+ */
8
+ character: string;
9
+ /**
10
+ * Must be 'none' for a boolean argument.
11
+ */
12
+ values: 'none';
13
+ /**
14
+ * The description of the argument.
15
+ */
16
+ description: string;
17
+ };
18
+ /**
19
+ * A standard argument that has one or more values.
20
+ */
21
+ export type StandardArgument = {
22
+ /**
23
+ * Whether the argument accepts single or multiple values.
24
+ */
25
+ values: 'single' | 'multiple';
26
+ /**
27
+ * The type of the values accepted.
28
+ */
29
+ type: 'string' | 'number';
30
+ /**
31
+ * The description of the argument.
32
+ */
33
+ description: string;
34
+ };
35
+ export type EnumArgument = {
36
+ /**
37
+ * Whether the argument accepts single or multiple values.
38
+ */
39
+ values: 'single' | 'multiple';
40
+ /**
41
+ * Must be 'enum' for an enum argument.
42
+ */
43
+ type: 'enum';
44
+ /**
45
+ * The valid values for the argument.
46
+ */
47
+ validValues: string[];
48
+ /**
49
+ * The description of the argument.
50
+ */
51
+ description: string;
52
+ };
53
+ /**
54
+ * A CLI argument that can be accepted for a command.
55
+ */
56
+ export type CliArgument = BooleanArgument | StandardArgument | EnumArgument;
57
+ export type Command = {
58
+ description: string;
59
+ options: Record<string, CliArgument>;
60
+ };
61
+ export type OptionConfig = {
62
+ type: 'string' | 'number' | 'boolean';
63
+ values: 'single' | 'multiple';
64
+ description: string;
65
+ character?: string;
66
+ };
67
+ /**
68
+ * A map of CLI argument keys to their configuration.
69
+ */
70
+ export type Config<T extends Record<string, CliArgument>> = T;
71
+ /**
72
+ * Create a type safe configuration for CLI arguments.
73
+ *
74
+ * @param config the configuration for the CLI arguments.
75
+ * @returns the configuration object.
76
+ */
77
+ export declare function createConfig<T extends Record<string, CliArgument>>(config: T): T;
78
+ type ParsedArgs<T extends Record<string, CliArgument>> = {
79
+ [K in keyof T as K]: T[K] extends {
80
+ type: 'string';
81
+ } ? T[K]['values'] extends 'single' ? string | undefined : string[] : T[K] extends {
82
+ type: 'number';
83
+ } ? T[K]['values'] extends 'single' ? number | undefined : number[] : T[K] extends {
84
+ type: 'boolean';
85
+ } ? boolean : never;
86
+ };
87
+ /**
88
+ * Additional arguments that can be used to configure the CLI parser.
89
+ */
90
+ export interface AdditionalCliArguments {
91
+ /**
92
+ * The version of the CLI command. If provided, the CLI provides a --version flag that prints the version and exits.
93
+ */
94
+ version?: string;
95
+ /**
96
+ * The argument string from the CLI. If not provided, the process.argv.slice(2) is used.
97
+ */
98
+ args?: string[];
99
+ /**
100
+ * The environment variables to use for parsing. If not provided, process.env is used.
101
+ */
102
+ env?: Record<string, string>;
103
+ /**
104
+ * The prefix to use for environment variables. If provided, the CLI will look for environment variables with the prefix followed by an underscore.
105
+ */
106
+ envPrefix?: string;
107
+ /**
108
+ * The name of the operands to display in the help message. Defaults to "operands".
109
+ */
110
+ operandsName?: string;
111
+ /**
112
+ * Whether a subcommand is required. If true, the CLI will exit with an error if no subcommand is provided.
113
+ */
114
+ requireSubcommand?: boolean;
115
+ /**
116
+ * If there are zero arguments, show the help message. Defaults to false.
117
+ */
118
+ showHelpIfNoArgs?: boolean;
119
+ }
120
+ export type SelectedCommandWithArgs<C extends Record<string, Command>, O extends Record<string, CliArgument>> = {
121
+ [K in keyof C]: {
122
+ command: K | undefined;
123
+ args: ParsedArgs<C[K]['options']> & ParsedArgs<O>;
124
+ operands: string[];
125
+ anyValues: boolean;
126
+ };
127
+ }[keyof C];
128
+ /**
129
+ * Parse CLI Arguments and return the parsed typesafe results.
130
+ *
131
+ * @param command the name of the command arguments are being parsed for.
132
+ * @param subcommands the list of subcommands that can be used, if any.
133
+ * @param cliOptions the configuration options for the CLI command.
134
+ * @param additionalArgs additional arguments to be used for parsing and displaying help.
135
+ * @returns the parsed arguments, operands, and subcommand if applicable.
136
+ */
137
+ export declare function parseCliArguments<O extends Record<string, CliArgument>, C extends Record<string, Command>>(command: string, subcommands: C, cliOptions: Config<O>, additionalArgs?: AdditionalCliArguments): SelectedCommandWithArgs<C, O>;
138
+ export declare function printHelpContents<O extends Record<string, CliArgument>, C extends Record<string, Command>>(command: string, subcommands: C, cliOptions: O, additionalArgs?: AdditionalCliArguments, selectedSubcommand?: string | undefined): void;
139
+ export {};
140
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}