@clo4/cli 0.0.0-stage → 1.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 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,99 @@
1
- # Temporary Holding Version
1
+ # @clo4/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ ### Completion modes: `--runtime` (default) and `--shell`
63
+
64
+ By default, `completion <shell>` emits a a shim that executes itself as `<cli> completion complete -- <commandline>`, allowing it to reuse its own parser. This is slower on average, but allows it to provide native-feeling completions that respect your nitpicky parser options. So long as you keep the startup time of your CLI acceptable, there isn't much of a downside to this.
65
+
66
+ Alternatively, `completion <shell> --shell` emits a self-contained shell completion script. It can't track line state, so relationship filters like `exclusiveOn`/`isRepeatable` aren't applied. Dynamic generators still call back via `completion generate-suggestions`.
67
+
68
+ In runtime mode, the bash and fish shims pass `--gate-options`, so options are only offered once the word starts with `-` (matching fish/bash convention); zsh omits it and surfaces options as usual. Flip this by editing which shims pass the option.
69
+
70
+ ### Argument suggestions
71
+
72
+ Arguments can declare completion metadata. These are only used for completions, not parsing.
73
+
74
+ ```ts
75
+ {
76
+ // dynamic completions: file/folder paths
77
+ name: "--config",
78
+ args: { name: "file", template: "filepaths" },
79
+ }
80
+
81
+ {
82
+ // static completions
83
+ name: "--mode",
84
+ args: { name: "mode", suggestions: ["debug", "release"] },
85
+ }
86
+
87
+ {
88
+ // fully dynamic completions using your own code
89
+ name: "--target",
90
+ args: {
91
+ name: "target",
92
+ generators: { custom: (tokens) => listTargets() },
93
+ },
94
+ }
95
+ ```
96
+
97
+ - `template` (`"filepaths"` / `"folders"`) completes paths.
98
+ - `suggestions` is a static list (strings or `{ name, description }`).
99
+ - `generators.custom(tokens)` returns dynamic suggestions: in runtime mode it runs in-process, in `--shell` mode it is reached via the hidden `completion generate-suggestions <location>` callback.
@@ -0,0 +1,229 @@
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
+ /** Allow this option to be supplied multiple times. */
134
+ isRepeatable?: true | number;
135
+ /** Require this option to be supplied before the selected action runs. */
136
+ isRequired?: true;
137
+ /** Fail parsing if these options are provided. */
138
+ exclusiveOn?: NonEmptyArray<string>;
139
+ /** Fail parsing if these options are not provided. */
140
+ dependsOn?: NonEmptyArray<string>;
141
+ /**
142
+ * Action performed when this option is provided.
143
+ *
144
+ * Option actions take precedence over command actions. The final parsed option
145
+ * action is selected.
146
+ */
147
+ action?: Action;
148
+ /**
149
+ * This property never exists. It prevents accidental structural assignment
150
+ * between options and subcommands.
151
+ *
152
+ * @ignore
153
+ */
154
+ [kind]?: "Option";
155
+ }
156
+ /**
157
+ * A built-in completion template.
158
+ *
159
+ * Resolves to the shell's native path completion. Completions only — no effect
160
+ * on parsing.
161
+ */
162
+ type Template = "filepaths" | "folders" | ["folders", "filepaths"] | ["filepaths", "folders"];
163
+ /**
164
+ * A single completion suggestion.
165
+ *
166
+ * This is a deliberately small subset of the Fig `Suggestion` shape — just the
167
+ * fields the completion engine uses. Presentational fields (icon, priority,
168
+ * displayName, insertValue, type) are intentionally omitted.
169
+ */
170
+ interface Suggestion {
171
+ /** Value(s) inserted when the suggestion is accepted. */
172
+ name: SingleOrArray<string>;
173
+ /** Additional information shown next to the suggestion. */
174
+ description?: string;
175
+ }
176
+ /**
177
+ * Generates dynamic completion suggestions.
178
+ *
179
+ * Generators run by re-invoking the CLI's hidden `completion
180
+ * generate-suggestions` command, so `custom` executes in the CLI's own
181
+ * process. Completions only — no effect on parsing.
182
+ */
183
+ interface Generator {
184
+ /** Produce suggestions from the current command-line tokens. */
185
+ custom?(tokens: string[]): (Suggestion | string)[] | Promise<(Suggestion | string)[]>;
186
+ }
187
+ /** Positional or option argument metadata. */
188
+ interface Arg {
189
+ /** Name shown in generated help. */
190
+ name?: string;
191
+ /** Description for documentation or future help output. */
192
+ description?: string;
193
+ /** Allow this argument to take unlimited values. */
194
+ isVariadic?: true;
195
+ /** Allow this argument to be omitted. */
196
+ isOptional?: true;
197
+ /** Completions: complete file or folder paths for this argument. */
198
+ template?: Template;
199
+ /** Completions: static suggestions offered while typing this argument. */
200
+ suggestions?: readonly (string | Suggestion)[];
201
+ /** Completions: dynamically generated suggestions for this argument. */
202
+ generators?: SingleOrArray<Generator>;
203
+ }
204
+ /** Shells that {@link getCompletion} can generate scripts for. */
205
+ type Shell = "bash" | "zsh" | "fish";
206
+ /** A subcommand that prints a shell completion script for the whole CLI. */
207
+ export declare const completion: Subcommand;
208
+ /** A subcommand that prints help for its parent or a sibling command. */
209
+ export declare const helpCommand: Subcommand;
210
+ /** A persistent help option that prints help for the selected command. */
211
+ export declare const help: Option;
212
+ /**
213
+ * Parse argv and execute the selected action.
214
+ *
215
+ * The final command action runs unless a parsed option has an action. Option
216
+ * actions take precedence so helpers like `--help` can run without satisfying
217
+ * the selected command's required arguments.
218
+ */
219
+ export declare function run(spec: Spec, args?: readonly string[]): Promise<number>;
220
+ export declare function printError(...strings: unknown[]): void;
221
+ declare const _default: {
222
+ completion: Subcommand;
223
+ help: Option;
224
+ helpCommand: Subcommand;
225
+ printError: typeof printError;
226
+ run: typeof run;
227
+ };
228
+ 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 };
229
+ //# sourceMappingURL=index.d.ts.map