@clo4/cli 0.0.0-stage → 2.0.0
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 +21 -0
- package/README.md +96 -2
- package/dist/index.d.ts +234 -0
- package/dist/index.js +1229 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +325 -0
- package/dist/testing.js +430 -0
- package/dist/testing.js.map +1 -0
- package/package.json +62 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert
|
|
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
CHANGED
|
@@ -1,3 +1,97 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @clo4/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fully declarative CLI framework, lean footprint, fast, good API for completions.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import * as CLI from "@clo4/cli";
|
|
7
|
+
|
|
8
|
+
const spec: CLI.Spec = {
|
|
9
|
+
name: "demo",
|
|
10
|
+
description: "Compile a project.",
|
|
11
|
+
options: [CLI.help],
|
|
12
|
+
subcommands: [
|
|
13
|
+
{
|
|
14
|
+
name: "build",
|
|
15
|
+
description: "Build a project.",
|
|
16
|
+
args: { name: "input" },
|
|
17
|
+
options: [
|
|
18
|
+
{ name: "--watch", description: "Watch for changes." },
|
|
19
|
+
{
|
|
20
|
+
name: ["-o", "--output"],
|
|
21
|
+
args: { name: "dir" },
|
|
22
|
+
isRequired: true,
|
|
23
|
+
description: "Write output to this directory.",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
action({ args: [input], options }) {
|
|
27
|
+
console.log(`${input} -> ${options.get("--output")}`);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
process.exitCode = await CLI.run(spec);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Specs define `args`, `options`, `subcommands`, and `action` functions. Parsing and validating values stay in your code, it's not the job of the library.
|
|
37
|
+
|
|
38
|
+
Option actions override command actions. That lets helpers like `--help` run even when the selected command is missing required positional arguments.
|
|
39
|
+
|
|
40
|
+
Use `@clo4/cli/testing` in package tests to validate structural constraints that TypeScript cannot express, such as duplicate names, bad required/optional argument order, invalid repeatable options, and invalid option references.
|
|
41
|
+
|
|
42
|
+
## Shell completion
|
|
43
|
+
|
|
44
|
+
Add `CLI.completion` to a spec's `subcommands` to give the CLI a `completion <shell>` command that prints a completion script:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
const spec: CLI.Spec = {
|
|
48
|
+
name: "demo",
|
|
49
|
+
options: [CLI.help],
|
|
50
|
+
subcommands: [CLI.completion /* ... */],
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
demo completion bash > /etc/bash_completion.d/demo
|
|
56
|
+
demo completion zsh > "${fpath[1]}/_demo"
|
|
57
|
+
demo completion fish > ~/.config/fish/completions/demo.fish
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Completion describes the whole CLI from the root spec regardless of where the subcommand is placed. Hidden options and subcommands are excluded; persistent options are offered on the descendants that inherit them.
|
|
61
|
+
|
|
62
|
+
### How completion works
|
|
63
|
+
|
|
64
|
+
`completion <shell>` prints a short script. On every Tab, the script runs `<cli> completion complete -- <commandline>`, and the CLI answers using its own parser. Completions therefore follow every parser option and relationship rule (`exclusiveOn`, `isRepeatable`, `dependsOn`, and so on). Each Tab starts the CLI once, so keep its startup fast.
|
|
65
|
+
|
|
66
|
+
The bash and fish scripts pass `--gate-options`, so options are only offered once the word starts with `-`, which is the bash and fish convention. The zsh script leaves it out and offers options as usual.
|
|
67
|
+
|
|
68
|
+
### Argument suggestions
|
|
69
|
+
|
|
70
|
+
Arguments can declare completion metadata. These are only used for completions, not parsing.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
{
|
|
74
|
+
// dynamic completions: file/folder paths
|
|
75
|
+
name: "--config",
|
|
76
|
+
args: { name: "file", template: "filepaths" },
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
// static completions
|
|
81
|
+
name: "--mode",
|
|
82
|
+
args: { name: "mode", suggestions: ["debug", "release"] },
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
{
|
|
86
|
+
// fully dynamic completions using your own code
|
|
87
|
+
name: "--target",
|
|
88
|
+
args: {
|
|
89
|
+
name: "target",
|
|
90
|
+
generators: { custom: (tokens) => listTargets() },
|
|
91
|
+
},
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- `template` (`"filepaths"` / `"folders"`) completes paths.
|
|
96
|
+
- `suggestions` is a static list (strings or `{ name, description }`).
|
|
97
|
+
- `generators.custom(tokens)` returns dynamic suggestions. It runs inside the CLI when the shell asks for completions.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative CLI spec types.
|
|
3
|
+
*
|
|
4
|
+
* The schema is intentionally small: define commands, positional args, options,
|
|
5
|
+
* and actions. Runtime values from argv are always strings.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
declare const kind: unique symbol;
|
|
10
|
+
/** An array with at least one value in it. */
|
|
11
|
+
type NonEmptyArray<T> = readonly [T, ...T[]];
|
|
12
|
+
/** A single value, or an array with at least two values. */
|
|
13
|
+
type SingleOrArray<T> = T | readonly [T, T, ...T[]];
|
|
14
|
+
/** A single value, or an array with either zero or at least two values. */
|
|
15
|
+
type SingleOrArrayOrEmpty<T> = T | [] | readonly [T, T, ...T[]];
|
|
16
|
+
/**
|
|
17
|
+
* An action associated with a subcommand or option.
|
|
18
|
+
*
|
|
19
|
+
* Actions can be sync or async. Returning `undefined` is treated as exit code 0.
|
|
20
|
+
*/
|
|
21
|
+
interface Action {
|
|
22
|
+
(init: ActionInit): Promise<number | void> | number | void;
|
|
23
|
+
/**
|
|
24
|
+
* This property never exists. It prevents accidental structural assignment
|
|
25
|
+
* between otherwise compatible exported interfaces.
|
|
26
|
+
*
|
|
27
|
+
* @ignore
|
|
28
|
+
*/
|
|
29
|
+
[kind]?: "Action";
|
|
30
|
+
}
|
|
31
|
+
/** A command path, starting with the root spec. */
|
|
32
|
+
type CommandPath = readonly [Spec, ...Subcommand[]];
|
|
33
|
+
/** Access parsed options. */
|
|
34
|
+
interface OptionArgs {
|
|
35
|
+
/** Escape hatch: the underlying map of option names to values. */
|
|
36
|
+
readonly options: Map<string, string[]>;
|
|
37
|
+
/** Get every argument supplied to a option. */
|
|
38
|
+
all(name: string): string[] | undefined;
|
|
39
|
+
/** Get the first argument supplied to a option. */
|
|
40
|
+
get(name: string): string | undefined;
|
|
41
|
+
/** Check whether a option was supplied. */
|
|
42
|
+
has(name: string): boolean;
|
|
43
|
+
/** Count option values, useful for repeatable options. */
|
|
44
|
+
count(name: string): number;
|
|
45
|
+
}
|
|
46
|
+
/** The data an action receives. */
|
|
47
|
+
interface ActionInit {
|
|
48
|
+
/** Options found for this invocation. */
|
|
49
|
+
options: OptionArgs;
|
|
50
|
+
/** Positional arguments found for the selected command. */
|
|
51
|
+
args: string[];
|
|
52
|
+
/** Commands used leading up to the selected action. */
|
|
53
|
+
path: CommandPath;
|
|
54
|
+
/** Index of the `--` argument separator, or -1 if it was not supplied. */
|
|
55
|
+
argSeparatorIndex: number;
|
|
56
|
+
/** Get help text for the currently executing command. */
|
|
57
|
+
help(config?: {
|
|
58
|
+
/** Include the command description? Default: true. */
|
|
59
|
+
description?: boolean;
|
|
60
|
+
/** Include the usage summary? Default: true. */
|
|
61
|
+
usage?: boolean;
|
|
62
|
+
/** Suggest the closest choice for an incorrect input. */
|
|
63
|
+
didYouMean?: {
|
|
64
|
+
input: string;
|
|
65
|
+
choices: readonly string[];
|
|
66
|
+
};
|
|
67
|
+
/** Alternative command path to render help for. */
|
|
68
|
+
path?: NonEmptyArray<Subcommand>;
|
|
69
|
+
}): string;
|
|
70
|
+
}
|
|
71
|
+
/** A top-level command. */
|
|
72
|
+
interface Spec extends Omit<Subcommand, "name"> {
|
|
73
|
+
/** Name of the CLI. */
|
|
74
|
+
name: string;
|
|
75
|
+
}
|
|
76
|
+
/** A subcommand that can be executed. */
|
|
77
|
+
interface Subcommand {
|
|
78
|
+
/** Name or aliases used for matching. */
|
|
79
|
+
name: SingleOrArray<string>;
|
|
80
|
+
/** Description shown in help. */
|
|
81
|
+
description?: string;
|
|
82
|
+
/** Positional arguments accepted by this command. */
|
|
83
|
+
args?: SingleOrArray<Arg>;
|
|
84
|
+
/** Options accepted by this command. */
|
|
85
|
+
options?: NonEmptyArray<Option>;
|
|
86
|
+
/** Subcommands of this command. */
|
|
87
|
+
subcommands?: NonEmptyArray<Subcommand>;
|
|
88
|
+
/** Direct parser behavior. */
|
|
89
|
+
parserDirectives?: {
|
|
90
|
+
/**
|
|
91
|
+
* Treat option names literally, disabling POSIX short-option chaining and
|
|
92
|
+
* unknown option errors for tokens that look option-like.
|
|
93
|
+
*/
|
|
94
|
+
flagsArePosixNoncompliant?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Once an argument has been found, all following tokens are arguments even
|
|
97
|
+
* if they match options.
|
|
98
|
+
*/
|
|
99
|
+
optionsMustPrecedeArguments?: boolean;
|
|
100
|
+
/** Separators that can attach a option argument to a option token. */
|
|
101
|
+
optionArgSeparators?: SingleOrArrayOrEmpty<string>;
|
|
102
|
+
/** Match subcommands by shortest unique prefix instead of exact name. */
|
|
103
|
+
subcommandsMatchUniquePrefix?: boolean;
|
|
104
|
+
};
|
|
105
|
+
/** Hide this command from help. */
|
|
106
|
+
hidden?: true;
|
|
107
|
+
/** Require a subcommand; without one, print usage. */
|
|
108
|
+
requiresSubcommand?: true;
|
|
109
|
+
/** Action to run when this command is selected. */
|
|
110
|
+
action?: Action;
|
|
111
|
+
/**
|
|
112
|
+
* This property never exists. It prevents accidental structural assignment
|
|
113
|
+
* between options and subcommands.
|
|
114
|
+
*
|
|
115
|
+
* @ignore
|
|
116
|
+
*/
|
|
117
|
+
[kind]?: "Subcommand";
|
|
118
|
+
}
|
|
119
|
+
/** A option that modifies or overrides command execution. */
|
|
120
|
+
interface Option {
|
|
121
|
+
/** Name or aliases, including any desired prefix such as `-`, `--`, or `/`. */
|
|
122
|
+
name: SingleOrArray<string>;
|
|
123
|
+
/** Description shown in help. */
|
|
124
|
+
description?: string;
|
|
125
|
+
/** Arguments this option accepts. */
|
|
126
|
+
args?: SingleOrArray<Arg>;
|
|
127
|
+
/** Allow this option to be used by descendant subcommands. */
|
|
128
|
+
isPersistent?: true;
|
|
129
|
+
/** Hide this option from help. */
|
|
130
|
+
hidden?: true;
|
|
131
|
+
/** Only allow arguments to be supplied with a separator. */
|
|
132
|
+
requiresSeparator?: true | string;
|
|
133
|
+
/**
|
|
134
|
+
* Allow this option to be supplied multiple times. `true` allows any number
|
|
135
|
+
* of uses, and a number is the most uses allowed. An option with arguments
|
|
136
|
+
* collects the values from every use, in order. Without arguments, each use
|
|
137
|
+
* adds an empty string, so `count()` returns the number of uses.
|
|
138
|
+
*/
|
|
139
|
+
isRepeatable?: true | number;
|
|
140
|
+
/** Require this option to be supplied before the selected action runs. */
|
|
141
|
+
isRequired?: true;
|
|
142
|
+
/** Fail parsing if these options are provided. */
|
|
143
|
+
exclusiveOn?: NonEmptyArray<string>;
|
|
144
|
+
/** Fail parsing if these options are not provided. */
|
|
145
|
+
dependsOn?: NonEmptyArray<string>;
|
|
146
|
+
/**
|
|
147
|
+
* Action performed when this option is provided.
|
|
148
|
+
*
|
|
149
|
+
* Option actions take precedence over command actions. The final parsed option
|
|
150
|
+
* action is selected.
|
|
151
|
+
*/
|
|
152
|
+
action?: Action;
|
|
153
|
+
/**
|
|
154
|
+
* This property never exists. It prevents accidental structural assignment
|
|
155
|
+
* between options and subcommands.
|
|
156
|
+
*
|
|
157
|
+
* @ignore
|
|
158
|
+
*/
|
|
159
|
+
[kind]?: "Option";
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* A built-in completion template.
|
|
163
|
+
*
|
|
164
|
+
* Resolves to the shell's native path completion. Completions only — no effect
|
|
165
|
+
* on parsing.
|
|
166
|
+
*/
|
|
167
|
+
type Template = "filepaths" | "folders" | ["folders", "filepaths"] | ["filepaths", "folders"];
|
|
168
|
+
/**
|
|
169
|
+
* A single completion suggestion.
|
|
170
|
+
*
|
|
171
|
+
* This is a deliberately small subset of the Fig `Suggestion` shape — just the
|
|
172
|
+
* fields the completion engine uses. Presentational fields (icon, priority,
|
|
173
|
+
* displayName, insertValue, type) are intentionally omitted.
|
|
174
|
+
*/
|
|
175
|
+
interface Suggestion {
|
|
176
|
+
/** Value(s) inserted when the suggestion is accepted. */
|
|
177
|
+
name: SingleOrArray<string>;
|
|
178
|
+
/** Additional information shown next to the suggestion. */
|
|
179
|
+
description?: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Generates dynamic completion suggestions.
|
|
183
|
+
*
|
|
184
|
+
* `custom` runs inside the CLI when the shell asks it for completions. It only
|
|
185
|
+
* affects completion and has no effect on parsing.
|
|
186
|
+
*/
|
|
187
|
+
interface Generator {
|
|
188
|
+
/** Produce suggestions from the current command-line tokens. */
|
|
189
|
+
custom?(tokens: string[]): (Suggestion | string)[] | Promise<(Suggestion | string)[]>;
|
|
190
|
+
}
|
|
191
|
+
/** Positional or option argument metadata. */
|
|
192
|
+
interface Arg {
|
|
193
|
+
/** Name shown in generated help. */
|
|
194
|
+
name?: string;
|
|
195
|
+
/** Description for documentation or future help output. */
|
|
196
|
+
description?: string;
|
|
197
|
+
/** Allow this argument to take unlimited values. */
|
|
198
|
+
isVariadic?: true;
|
|
199
|
+
/** Allow this argument to be omitted. */
|
|
200
|
+
isOptional?: true;
|
|
201
|
+
/** Completions: complete file or folder paths for this argument. */
|
|
202
|
+
template?: Template;
|
|
203
|
+
/** Completions: static suggestions offered while typing this argument. */
|
|
204
|
+
suggestions?: readonly (string | Suggestion)[];
|
|
205
|
+
/** Completions: dynamically generated suggestions for this argument. */
|
|
206
|
+
generators?: SingleOrArray<Generator>;
|
|
207
|
+
}
|
|
208
|
+
/** Shells that {@link getCompletion} can generate scripts for. */
|
|
209
|
+
type Shell = "bash" | "zsh" | "fish";
|
|
210
|
+
/** A subcommand that prints a shell completion script for the whole CLI. */
|
|
211
|
+
export declare const completion: Subcommand;
|
|
212
|
+
/** A subcommand that prints help for its parent or a sibling command. */
|
|
213
|
+
export declare const helpCommand: Subcommand;
|
|
214
|
+
/** A persistent help option that prints help for the selected command. */
|
|
215
|
+
export declare const help: Option;
|
|
216
|
+
/**
|
|
217
|
+
* Parse argv and execute the selected action.
|
|
218
|
+
*
|
|
219
|
+
* The final command action runs unless a parsed option has an action. Option
|
|
220
|
+
* actions take precedence so helpers like `--help` can run without satisfying
|
|
221
|
+
* the selected command's required arguments.
|
|
222
|
+
*/
|
|
223
|
+
export declare function run(spec: Spec, args?: readonly string[]): Promise<number>;
|
|
224
|
+
/** Print `Error:` and the given values to stderr, separated by spaces. */
|
|
225
|
+
export declare function printError(...strings: unknown[]): void;
|
|
226
|
+
declare const _default: {
|
|
227
|
+
completion: Subcommand;
|
|
228
|
+
help: Option;
|
|
229
|
+
helpCommand: Subcommand;
|
|
230
|
+
printError: typeof printError;
|
|
231
|
+
run: typeof run;
|
|
232
|
+
};
|
|
233
|
+
export { type Action, type ActionInit, type Arg, type CommandPath, type Generator, type NonEmptyArray, type Option, type OptionArgs, type Shell, type SingleOrArray, type SingleOrArrayOrEmpty, type Spec, type Subcommand, type Suggestion, type Template, _default as default };
|
|
234
|
+
//# sourceMappingURL=index.d.ts.map
|