@outfitter/cli 0.4.0 → 0.4.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/README.md CHANGED
@@ -361,6 +361,31 @@ if (flags.reset) {
361
361
  }
362
362
  ```
363
363
 
364
+ ## Conventions
365
+
366
+ Composable flag presets provide typed, reusable CLI flag definitions. See the [full conventions guide](../../docs/CLI-CONVENTIONS.md) for the complete catalog.
367
+
368
+ ```typescript
369
+ import { composePresets, verbosePreset, cwdPreset, forcePreset } from "@outfitter/cli/flags";
370
+ import { outputModePreset } from "@outfitter/cli/query";
371
+
372
+ const preset = composePresets(verbosePreset(), cwdPreset(), forcePreset());
373
+
374
+ command("deploy")
375
+ .preset(preset)
376
+ .action(async ({ flags }) => {
377
+ const { verbose, cwd, force } = preset.resolve(flags);
378
+ // All typed, all composable
379
+ });
380
+ ```
381
+
382
+ **Available presets:** `verbosePreset`, `cwdPreset`, `dryRunPreset`, `forcePreset`, `interactionPreset`, `strictPreset`, `colorPreset`, `projectionPreset`, `paginationPreset`, `timeWindowPreset`, `executionPreset`, `outputModePreset`, `jqPreset`
383
+
384
+ **Additional modules:**
385
+ - `@outfitter/cli/verbs` — Standard verb families (`create`, `modify`, `remove`, `list`, `show`)
386
+ - `@outfitter/cli/query` — Output mode and jq expression presets
387
+ - `@outfitter/cli/completion` — Shell completion script generation
388
+
364
389
  ## Configuration
365
390
 
366
391
  ### Environment Variables
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CLI, CLIConfig } from "./shared/@outfitter/cli-02kyvj7h";
1
+ import { CLI, CLIConfig } from "./shared/@outfitter/cli-md9347gn";
2
2
  /**
3
3
  * Create a new CLI instance with the given configuration.
4
4
  *
package/dist/command.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CLI, CLIConfig, CommandAction, CommandBuilder, CommandConfig, CommandFlags } from "./shared/@outfitter/cli-02kyvj7h";
1
+ import { CLI, CLIConfig, CommandAction, CommandBuilder, CommandConfig, CommandFlags, FlagPreset } from "./shared/@outfitter/cli-md9347gn";
2
2
  /**
3
3
  * Create a CLI instance with a portable return type from this module.
4
4
  */
@@ -40,4 +40,4 @@ declare function createCLI(config: CLIConfig): CLI;
40
40
  * ```
41
41
  */
42
42
  declare function command(name: string): CommandBuilder;
43
- export { createCLI, command, CommandFlags, CommandConfig, CommandBuilder, CommandAction, CLIConfig, CLI };
43
+ export { createCLI, command, FlagPreset, CommandFlags, CommandConfig, CommandBuilder, CommandAction, CLIConfig, CLI };
package/dist/command.js CHANGED
@@ -47,6 +47,16 @@ class CommandBuilderImpl {
47
47
  this.command.alias(alias);
48
48
  return this;
49
49
  }
50
+ preset(preset) {
51
+ for (const opt of preset.options) {
52
+ if (opt.required) {
53
+ this.command.requiredOption(opt.flags, opt.description, opt.defaultValue);
54
+ } else {
55
+ this.command.option(opt.flags, opt.description, opt.defaultValue);
56
+ }
57
+ }
58
+ return this;
59
+ }
50
60
  action(handler) {
51
61
  this.command.action(async (...args) => {
52
62
  const command = args.at(-1);
@@ -0,0 +1,36 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Configuration for the completion command.
4
+ */
5
+ interface CompletionConfig {
6
+ /** Supported shells (default: bash, zsh, fish) */
7
+ readonly shells?: readonly ("bash" | "zsh" | "fish")[];
8
+ /** Program name for completion scripts (inferred from CLI name if not provided) */
9
+ readonly programName?: string;
10
+ }
11
+ /**
12
+ * Generate a completion script for the given shell.
13
+ *
14
+ * @param shell - Target shell
15
+ * @param programName - CLI program name
16
+ * @returns The completion script string, or undefined for unsupported shells
17
+ */
18
+ declare function generateCompletion(shell: string, programName: string): string | undefined;
19
+ /**
20
+ * Create a `completion <shell>` command that outputs install-ready
21
+ * completion scripts.
22
+ *
23
+ * @param config - Optional configuration
24
+ * @returns A Commander command instance
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createCLI } from "@outfitter/cli";
29
+ * import { createCompletionCommand } from "@outfitter/cli/completion";
30
+ *
31
+ * const cli = createCLI({ name: "mycli", version: "1.0.0" });
32
+ * cli.register(createCompletionCommand({ programName: "mycli" }));
33
+ * ```
34
+ */
35
+ declare function createCompletionCommand(config?: CompletionConfig): Command;
36
+ export { generateCompletion, createCompletionCommand, CompletionConfig };
@@ -0,0 +1,91 @@
1
+ // @bun
2
+ // packages/cli/src/completion.ts
3
+ import { Command } from "commander";
4
+ var SHELL_SAFE_PROGRAM_NAME = /^[A-Za-z0-9._-]+$/;
5
+ function assertSafeProgramName(programName) {
6
+ if (SHELL_SAFE_PROGRAM_NAME.test(programName))
7
+ return;
8
+ throw new Error(`Invalid program name for shell completion: "${programName}".` + " Must match /^[A-Za-z0-9._-]+$/.");
9
+ }
10
+ function toShellIdentifier(programName) {
11
+ assertSafeProgramName(programName);
12
+ return programName.replaceAll(/[^A-Za-z0-9_]/g, "_");
13
+ }
14
+ function generateBashCompletion(programName) {
15
+ assertSafeProgramName(programName);
16
+ const shellIdentifier = toShellIdentifier(programName);
17
+ return `# bash completion for ${programName}
18
+ # Add to ~/.bashrc or ~/.bash_profile:
19
+ # eval "$(${programName} completion bash)"
20
+
21
+ _${shellIdentifier}_completions() {
22
+ local cur
23
+ cur="\${COMP_WORDS[COMP_CWORD]}"
24
+
25
+ COMPREPLY=( $(compgen -W "$(${programName} --help 2>/dev/null | grep -oE '^ [a-z][-a-z]*' | tr -d ' ')" -- "\${cur}") )
26
+ }
27
+
28
+ complete -F _${shellIdentifier}_completions ${programName}
29
+ `;
30
+ }
31
+ function generateZshCompletion(programName) {
32
+ assertSafeProgramName(programName);
33
+ const shellIdentifier = toShellIdentifier(programName);
34
+ return `#compdef ${programName}
35
+ # zsh completion for ${programName}
36
+ # Add to ~/.zshrc:
37
+ # eval "$(${programName} completion zsh)"
38
+
39
+ _${shellIdentifier}() {
40
+ local -a commands
41
+ commands=($(${programName} --help 2>/dev/null | grep -oE '^ [a-z][-a-z]*' | tr -d ' '))
42
+
43
+ _arguments \\
44
+ '1:command:compadd -a commands' \\
45
+ '*::arg:->args'
46
+ }
47
+
48
+ compdef _${shellIdentifier} ${programName}
49
+ `;
50
+ }
51
+ function generateFishCompletion(programName) {
52
+ assertSafeProgramName(programName);
53
+ return `# fish completion for ${programName}
54
+ # Add to ~/.config/fish/completions/${programName}.fish:
55
+ # ${programName} completion fish > ~/.config/fish/completions/${programName}.fish
56
+
57
+ complete -c ${programName} -f
58
+ complete -c ${programName} -n "__fish_use_subcommand" -a "(${programName} --help 2>/dev/null | string match -r '^ [a-z][-a-z]*' | string trim)"
59
+ `;
60
+ }
61
+ var GENERATORS = {
62
+ bash: generateBashCompletion,
63
+ zsh: generateZshCompletion,
64
+ fish: generateFishCompletion
65
+ };
66
+ function generateCompletion(shell, programName) {
67
+ const generator = Object.hasOwn(GENERATORS, shell) ? GENERATORS[shell] : undefined;
68
+ return generator?.(programName);
69
+ }
70
+ function createCompletionCommand(config) {
71
+ const shells = config?.shells ?? ["bash", "zsh", "fish"];
72
+ const shellSet = new Set(shells);
73
+ const cmd = new Command("completion").description(`Generate shell completion script (${shells.join(", ")})`).argument("<shell>", `Shell type (${shells.join(", ")})`).action((shell) => {
74
+ if (!shellSet.has(shell)) {
75
+ cmd.error(`error: unsupported shell '${shell}'. Supported: ${shells.join(", ")}`);
76
+ return;
77
+ }
78
+ const generator = GENERATORS[shell];
79
+ if (generator) {
80
+ const programName = config?.programName ?? cmd.parent?.name() ?? "program";
81
+ assertSafeProgramName(programName);
82
+ const script = generator(programName);
83
+ process.stdout.write(script);
84
+ }
85
+ });
86
+ return cmd;
87
+ }
88
+ export {
89
+ generateCompletion,
90
+ createCompletionCommand
91
+ };
@@ -0,0 +1,167 @@
1
+ import { ColorFlags, ColorMode, ComposedPreset, ExecutionFlags, ExecutionPresetConfig, FlagPreset, FlagPresetConfig, InteractionFlags, PaginationFlags, PaginationPresetConfig, ProjectionFlags, StrictFlags, TimeWindowFlags, TimeWindowPresetConfig } from "./shared/@outfitter/cli-md9347gn";
2
+ /**
3
+ * Create a typed flag preset.
4
+ *
5
+ * @param config - Preset configuration with id, options, and resolver
6
+ * @returns A flag preset that can be applied to commands or composed
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const myPreset = createPreset({
11
+ * id: "output-format",
12
+ * options: [{ flags: "--format <type>", description: "Output format" }],
13
+ * resolve: (flags) => ({
14
+ * format: String(flags["format"] ?? "text"),
15
+ * }),
16
+ * });
17
+ * ```
18
+ */
19
+ declare function createPreset<TResolved extends Record<string, unknown>>(config: FlagPresetConfig<TResolved>): FlagPreset<TResolved>;
20
+ type ResolvedType<T> = T extends FlagPreset<infer R> ? R : never;
21
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
22
+ /**
23
+ * Resolves the merged type from a tuple of presets.
24
+ * Falls back to Record<string, unknown> when the tuple is empty
25
+ * (since UnionToIntersection<never> produces unknown).
26
+ */
27
+ type MergedPresetResult<TPresets extends readonly FlagPreset<Record<string, unknown>>[]> = UnionToIntersection<ResolvedType<TPresets[number]>> extends Record<string, unknown> ? UnionToIntersection<ResolvedType<TPresets[number]>> : Record<string, unknown>;
28
+ /**
29
+ * Compose multiple presets into one, deduplicating by id (first wins).
30
+ *
31
+ * @param presets - Presets to compose together
32
+ * @returns A single preset merging all options and resolvers
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const composed = composePresets(
37
+ * verbosePreset(),
38
+ * cwdPreset(),
39
+ * forcePreset(),
40
+ * );
41
+ * // composed.resolve({ verbose: true, cwd: "/tmp", force: false })
42
+ * // => { verbose: true, cwd: "/tmp", force: false }
43
+ * ```
44
+ */
45
+ declare function composePresets<TPresets extends readonly FlagPreset<Record<string, unknown>>[]>(...presets: TPresets): ComposedPreset<MergedPresetResult<TPresets>>;
46
+ /**
47
+ * Verbose output flag preset.
48
+ *
49
+ * Adds: `-v, --verbose`
50
+ * Resolves: `{ verbose: boolean }`
51
+ */
52
+ declare function verbosePreset(): FlagPreset<{
53
+ verbose: boolean;
54
+ }>;
55
+ /**
56
+ * Working directory flag preset.
57
+ *
58
+ * Adds: `--cwd <path>`
59
+ * Resolves: `{ cwd: string }` (defaults to `process.cwd()`)
60
+ */
61
+ declare function cwdPreset(): FlagPreset<{
62
+ cwd: string;
63
+ }>;
64
+ /**
65
+ * Dry-run flag preset.
66
+ *
67
+ * Adds: `--dry-run`
68
+ * Resolves: `{ dryRun: boolean }`
69
+ */
70
+ declare function dryRunPreset(): FlagPreset<{
71
+ dryRun: boolean;
72
+ }>;
73
+ /**
74
+ * Force flag preset.
75
+ *
76
+ * Adds: `-f, --force`
77
+ * Resolves: `{ force: boolean }`
78
+ */
79
+ declare function forcePreset(): FlagPreset<{
80
+ force: boolean;
81
+ }>;
82
+ /**
83
+ * Interaction mode flag preset.
84
+ *
85
+ * Adds: `--non-interactive`, `--no-input`, `-y, --yes`
86
+ * Resolves: `{ interactive: boolean, yes: boolean }`
87
+ *
88
+ * `interactive` defaults to `true` and is set to `false` when
89
+ * `--non-interactive` or `--no-input` is passed. The two flags
90
+ * are synonyms for user convenience.
91
+ */
92
+ declare function interactionPreset(): FlagPreset<InteractionFlags>;
93
+ /**
94
+ * Strict mode flag preset.
95
+ *
96
+ * Adds: `--strict`
97
+ * Resolves: `{ strict: boolean }`
98
+ */
99
+ declare function strictPreset(): FlagPreset<StrictFlags>;
100
+ /**
101
+ * Color mode flag preset.
102
+ *
103
+ * Adds: `--color [mode]`, `--no-color`
104
+ * Resolves: `{ color: "auto" | "always" | "never" }`
105
+ *
106
+ * Commander's `--no-color` negation sets `flags["color"]` to `false`,
107
+ * which resolves to `"never"`. Invalid values default to `"auto"`.
108
+ *
109
+ * This preset resolves the user's *intent*. Consumers should compose
110
+ * with terminal detection (`supportsColor()`, `resolveColorEnv()`)
111
+ * to determine actual color output behavior.
112
+ */
113
+ declare function colorPreset(): FlagPreset<ColorFlags>;
114
+ /**
115
+ * Projection flag preset.
116
+ *
117
+ * Adds: `--fields <fields>`, `--exclude-fields <fields>`, `--count`
118
+ * Resolves: `{ fields: string[] | undefined, excludeFields: string[] | undefined, count: boolean }`
119
+ *
120
+ * Fields are parsed as comma-separated strings with whitespace trimming.
121
+ * `undefined` when not provided (not empty array) to distinguish
122
+ * "not specified" from "empty".
123
+ *
124
+ * Conflict between `--fields` and `--exclude-fields` is a handler
125
+ * concern (documented, not enforced).
126
+ */
127
+ declare function projectionPreset(): FlagPreset<ProjectionFlags>;
128
+ /**
129
+ * Time-window flag preset.
130
+ *
131
+ * Adds: `--since <date>`, `--until <date>`
132
+ * Resolves: `{ since: Date | undefined, until: Date | undefined }`
133
+ *
134
+ * Accepts ISO 8601 dates (`2024-01-15`) or relative durations
135
+ * (`7d`, `24h`, `30m`, `2w`). Durations are past-relative
136
+ * (subtracted from now).
137
+ *
138
+ * Returns `undefined` for unparseable values (does not throw).
139
+ * When `config.maxRange` is set and both bounds are provided,
140
+ * ranges above the limit are treated as invalid.
141
+ */
142
+ declare function timeWindowPreset(config?: TimeWindowPresetConfig): FlagPreset<TimeWindowFlags>;
143
+ /**
144
+ * Execution flag preset.
145
+ *
146
+ * Adds: `--timeout <ms>`, `--retries <n>`, `--offline`
147
+ * Resolves: `{ timeout: number | undefined, retries: number, offline: boolean }`
148
+ *
149
+ * Timeout is parsed as a positive integer in milliseconds.
150
+ * Retries are parsed as a non-negative integer, clamped to maxRetries.
151
+ */
152
+ declare function executionPreset(config?: ExecutionPresetConfig): FlagPreset<ExecutionFlags>;
153
+ /**
154
+ * Pagination flag preset.
155
+ *
156
+ * Adds: `-l, --limit <n>`, `--next`, `--reset`
157
+ * Resolves: `{ limit: number, next: boolean, reset: boolean }`
158
+ *
159
+ * Limit is parsed as an integer, clamped to maxLimit, and defaults
160
+ * to defaultLimit on invalid input. Mutual exclusivity of --next
161
+ * and --reset is a handler concern (not enforced here).
162
+ *
163
+ * Integrates with loadCursor/saveCursor/clearCursor from
164
+ * `@outfitter/cli/pagination`.
165
+ */
166
+ declare function paginationPreset(config?: PaginationPresetConfig): FlagPreset<PaginationFlags>;
167
+ export { verbosePreset, timeWindowPreset, strictPreset, projectionPreset, paginationPreset, interactionPreset, forcePreset, executionPreset, dryRunPreset, cwdPreset, createPreset, composePresets, colorPreset, TimeWindowPresetConfig, TimeWindowFlags, StrictFlags, ProjectionFlags, PaginationPresetConfig, PaginationFlags, InteractionFlags, FlagPresetConfig, FlagPreset, ExecutionPresetConfig, ExecutionFlags, ComposedPreset, ColorMode, ColorFlags };
package/dist/flags.js ADDED
@@ -0,0 +1,31 @@
1
+ // @bun
2
+ import {
3
+ colorPreset,
4
+ composePresets,
5
+ createPreset,
6
+ cwdPreset,
7
+ dryRunPreset,
8
+ executionPreset,
9
+ forcePreset,
10
+ interactionPreset,
11
+ paginationPreset,
12
+ projectionPreset,
13
+ strictPreset,
14
+ timeWindowPreset,
15
+ verbosePreset
16
+ } from "./shared/@outfitter/cli-b2zk8fb3.js";
17
+ export {
18
+ verbosePreset,
19
+ timeWindowPreset,
20
+ strictPreset,
21
+ projectionPreset,
22
+ paginationPreset,
23
+ interactionPreset,
24
+ forcePreset,
25
+ executionPreset,
26
+ dryRunPreset,
27
+ cwdPreset,
28
+ createPreset,
29
+ composePresets,
30
+ colorPreset
31
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./shared/@outfitter/cli-qz47jk6d";
2
2
  import { ANSI, Theme, Tokens, createTheme } from "./shared/@outfitter/cli-xppg982q";
3
- import { output } from "./shared/@outfitter/cli-f79bwzsp";
4
- import { OutputMode } from "./shared/@outfitter/cli-02kyvj7h";
5
- export { output, createTheme, Tokens, Theme, OutputMode, ANSI };
3
+ import { exitWithError, output } from "./shared/@outfitter/cli-k0yvzn6d";
4
+ import { OutputMode } from "./shared/@outfitter/cli-md9347gn";
5
+ export { output, exitWithError, createTheme, Tokens, Theme, OutputMode, ANSI };
package/dist/index.js CHANGED
@@ -6,10 +6,12 @@ import {
6
6
  } from "./shared/@outfitter/cli-rk9zagkm.js";
7
7
  import"./shared/@outfitter/cli-jbj78ac5.js";
8
8
  import {
9
+ exitWithError,
9
10
  output
10
11
  } from "./shared/@outfitter/cli-7wp5nj0s.js";
11
12
  export {
12
13
  output,
14
+ exitWithError,
13
15
  createTheme,
14
16
  ANSI
15
17
  };
package/dist/input.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CollectIdsOptions, ExpandFileOptions, FilterExpression, KeyValuePair, NormalizeIdOptions, ParseGlobOptions, Range, SortCriteria } from "./shared/@outfitter/cli-02kyvj7h";
1
+ import { CollectIdsOptions, ExpandFileOptions, FilterExpression, KeyValuePair, NormalizeIdOptions, ParseGlobOptions, Range, SortCriteria } from "./shared/@outfitter/cli-md9347gn";
2
2
  import { ValidationError } from "@outfitter/contracts";
3
3
  import { Result } from "better-result";
4
4
  /**
package/dist/output.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { exitWithError, output, resolveVerbose } from "./shared/@outfitter/cli-f79bwzsp";
2
- import "./shared/@outfitter/cli-02kyvj7h";
1
+ import { exitWithError, output, resolveVerbose } from "./shared/@outfitter/cli-k0yvzn6d";
2
+ import "./shared/@outfitter/cli-md9347gn";
3
3
  export { resolveVerbose, output, exitWithError };
@@ -1,4 +1,4 @@
1
- import { CursorOptions, PaginationState } from "./shared/@outfitter/cli-02kyvj7h";
1
+ import { CursorOptions, PaginationState } from "./shared/@outfitter/cli-md9347gn";
2
2
  /**
3
3
  * Load persisted pagination state for a command.
4
4
  *
@@ -0,0 +1,50 @@
1
+ import { FlagPreset, OutputMode } from "./shared/@outfitter/cli-md9347gn";
2
+ /**
3
+ * Configuration for the output mode preset.
4
+ */
5
+ interface OutputModePresetConfig {
6
+ /** Allowed output modes (default: ["human", "json"]) */
7
+ readonly modes?: readonly OutputMode[];
8
+ /** Default mode when not specified (default: "human") */
9
+ readonly defaultMode?: OutputMode;
10
+ /** Whether to include "jsonl" in allowed modes (default: false) */
11
+ readonly includeJsonl?: boolean;
12
+ }
13
+ /**
14
+ * Resolved output mode from CLI input.
15
+ */
16
+ type OutputModeFlags = {
17
+ /** The resolved output mode */
18
+ readonly outputMode: OutputMode;
19
+ };
20
+ /**
21
+ * Output mode flag preset.
22
+ *
23
+ * Adds: `-o, --output <mode>`
24
+ * Resolves: `{ outputMode: string }`
25
+ *
26
+ * Replaces ad-hoc `--json`/`--jsonl` handling with a single
27
+ * `--output` flag that accepts a mode name. Invalid modes
28
+ * fall back to the configured default.
29
+ *
30
+ * @param config - Optional configuration for allowed modes and default
31
+ */
32
+ declare function outputModePreset(config?: OutputModePresetConfig): FlagPreset<OutputModeFlags>;
33
+ /**
34
+ * Resolved jq expression from CLI input.
35
+ */
36
+ type JqFlags = {
37
+ /** The jq expression, or undefined if not provided */
38
+ readonly jq: string | undefined;
39
+ };
40
+ /**
41
+ * JQ expression flag preset.
42
+ *
43
+ * Adds: `--jq <expr>`
44
+ * Resolves: `{ jq: string | undefined }`
45
+ *
46
+ * The resolver returns the expression string or `undefined`.
47
+ * Actual jq execution is a consumer concern.
48
+ */
49
+ declare function jqPreset(): FlagPreset<JqFlags>;
50
+ export { outputModePreset, jqPreset, OutputModePresetConfig, OutputModeFlags, JqFlags };
package/dist/query.js ADDED
@@ -0,0 +1,51 @@
1
+ // @bun
2
+ import {
3
+ createPreset
4
+ } from "./shared/@outfitter/cli-b2zk8fb3.js";
5
+
6
+ // packages/cli/src/query.ts
7
+ function outputModePreset(config) {
8
+ const defaultMode = config?.defaultMode ?? "human";
9
+ const baseModes = config?.modes ?? ["human", "json"];
10
+ const modes = new Set(config?.includeJsonl ? [...baseModes, "jsonl"] : baseModes);
11
+ modes.add(defaultMode);
12
+ return createPreset({
13
+ id: "outputMode",
14
+ options: [
15
+ {
16
+ flags: "-o, --output <mode>",
17
+ description: `Output mode (${[...modes].join(", ")})`,
18
+ defaultValue: defaultMode
19
+ }
20
+ ],
21
+ resolve: (flags) => {
22
+ const raw = flags["output"];
23
+ if (typeof raw === "string" && modes.has(raw)) {
24
+ return { outputMode: raw };
25
+ }
26
+ return { outputMode: defaultMode };
27
+ }
28
+ });
29
+ }
30
+ function jqPreset() {
31
+ return createPreset({
32
+ id: "jq",
33
+ options: [
34
+ {
35
+ flags: "--jq <expr>",
36
+ description: "Filter JSON output with a jq expression"
37
+ }
38
+ ],
39
+ resolve: (flags) => {
40
+ const raw = flags["jq"];
41
+ if (typeof raw === "string" && raw.length > 0) {
42
+ return { jq: raw };
43
+ }
44
+ return { jq: undefined };
45
+ }
46
+ });
47
+ }
48
+ export {
49
+ outputModePreset,
50
+ jqPreset
51
+ };
@@ -0,0 +1,357 @@
1
+ // @bun
2
+ // packages/cli/src/flags.ts
3
+ function createPreset(config) {
4
+ const preset = {
5
+ id: config.id,
6
+ options: config.options,
7
+ resolve: config.resolve
8
+ };
9
+ preset[PRESET_IDS] = [config.id];
10
+ return preset;
11
+ }
12
+ var PRESET_IDS = Symbol("presetIds");
13
+ function getPresetIds(preset) {
14
+ const ids = preset[PRESET_IDS];
15
+ return ids && ids.length > 0 ? ids : [preset.id];
16
+ }
17
+ function composePresets(...presets) {
18
+ const seen = new Set;
19
+ const mergedIds = [];
20
+ const mergedOptions = [];
21
+ const resolvers = [];
22
+ for (const preset of presets) {
23
+ const presetIds = getPresetIds(preset);
24
+ if (presetIds.every((id) => seen.has(id)))
25
+ continue;
26
+ for (const id of presetIds) {
27
+ if (seen.has(id))
28
+ continue;
29
+ seen.add(id);
30
+ mergedIds.push(id);
31
+ }
32
+ mergedOptions.push(...preset.options);
33
+ resolvers.push(preset.resolve);
34
+ }
35
+ const composed = {
36
+ id: mergedIds.join("+"),
37
+ options: mergedOptions,
38
+ resolve: (flags) => {
39
+ let result = {};
40
+ for (const resolver of resolvers) {
41
+ result = { ...result, ...resolver(flags) };
42
+ }
43
+ return result;
44
+ }
45
+ };
46
+ composed[PRESET_IDS] = mergedIds;
47
+ return composed;
48
+ }
49
+ function verbosePreset() {
50
+ return createPreset({
51
+ id: "verbose",
52
+ options: [
53
+ {
54
+ flags: "-v, --verbose",
55
+ description: "Verbose output",
56
+ defaultValue: false
57
+ }
58
+ ],
59
+ resolve: (flags) => ({
60
+ verbose: Boolean(flags["verbose"])
61
+ })
62
+ });
63
+ }
64
+ function cwdPreset() {
65
+ return createPreset({
66
+ id: "cwd",
67
+ options: [
68
+ {
69
+ flags: "--cwd <path>",
70
+ description: "Working directory"
71
+ }
72
+ ],
73
+ resolve: (flags) => ({
74
+ cwd: typeof flags["cwd"] === "string" ? flags["cwd"] : process.cwd()
75
+ })
76
+ });
77
+ }
78
+ function dryRunPreset() {
79
+ return createPreset({
80
+ id: "dryRun",
81
+ options: [
82
+ {
83
+ flags: "--dry-run",
84
+ description: "Preview changes without applying",
85
+ defaultValue: false
86
+ }
87
+ ],
88
+ resolve: (flags) => ({
89
+ dryRun: Boolean(flags["dryRun"] ?? flags["dry-run"])
90
+ })
91
+ });
92
+ }
93
+ function forcePreset() {
94
+ return createPreset({
95
+ id: "force",
96
+ options: [
97
+ {
98
+ flags: "-f, --force",
99
+ description: "Force operation (skip confirmations)",
100
+ defaultValue: false
101
+ }
102
+ ],
103
+ resolve: (flags) => ({
104
+ force: Boolean(flags["force"])
105
+ })
106
+ });
107
+ }
108
+ function interactionPreset() {
109
+ return createPreset({
110
+ id: "interaction",
111
+ options: [
112
+ {
113
+ flags: "--non-interactive",
114
+ description: "Disable interactive prompts",
115
+ defaultValue: false
116
+ },
117
+ {
118
+ flags: "--no-input",
119
+ description: "Disable interactive prompts (alias)"
120
+ },
121
+ {
122
+ flags: "-y, --yes",
123
+ description: "Auto-confirm prompts",
124
+ defaultValue: false
125
+ }
126
+ ],
127
+ resolve: (flags) => {
128
+ const nonInteractive = flags["input"] === false || Boolean(flags["nonInteractive"]) || Boolean(flags["non-interactive"]) || Boolean(flags["noInput"]) || Boolean(flags["no-input"]);
129
+ return {
130
+ interactive: !nonInteractive,
131
+ yes: Boolean(flags["yes"])
132
+ };
133
+ }
134
+ });
135
+ }
136
+ function strictPreset() {
137
+ return createPreset({
138
+ id: "strict",
139
+ options: [
140
+ {
141
+ flags: "--strict",
142
+ description: "Enable strict mode (treat warnings as errors)",
143
+ defaultValue: false
144
+ }
145
+ ],
146
+ resolve: (flags) => ({
147
+ strict: Boolean(flags["strict"])
148
+ })
149
+ });
150
+ }
151
+ var COLOR_MODES = new Set(["auto", "always", "never"]);
152
+ function colorPreset() {
153
+ return createPreset({
154
+ id: "color",
155
+ options: [
156
+ {
157
+ flags: "--color [mode]",
158
+ description: "Color output mode (auto, always, never)",
159
+ defaultValue: "auto"
160
+ },
161
+ {
162
+ flags: "--no-color",
163
+ description: "Disable color output"
164
+ }
165
+ ],
166
+ resolve: (flags) => {
167
+ const raw = flags["color"];
168
+ if (raw === false)
169
+ return { color: "never" };
170
+ if (raw === true)
171
+ return { color: "always" };
172
+ if (typeof raw === "string" && COLOR_MODES.has(raw)) {
173
+ return { color: raw };
174
+ }
175
+ return { color: "auto" };
176
+ }
177
+ });
178
+ }
179
+ function parseCommaSeparated(value) {
180
+ if (typeof value !== "string")
181
+ return;
182
+ const items = value.split(",").map((s) => s.trim()).filter(Boolean);
183
+ return items.length > 0 ? items : undefined;
184
+ }
185
+ function projectionPreset() {
186
+ return createPreset({
187
+ id: "projection",
188
+ options: [
189
+ {
190
+ flags: "--fields <fields>",
191
+ description: "Comma-separated list of fields to include"
192
+ },
193
+ {
194
+ flags: "--exclude-fields <fields>",
195
+ description: "Comma-separated list of fields to exclude"
196
+ },
197
+ {
198
+ flags: "--count",
199
+ description: "Output only the count of results",
200
+ defaultValue: false
201
+ }
202
+ ],
203
+ resolve: (flags) => ({
204
+ fields: parseCommaSeparated(flags["fields"]),
205
+ excludeFields: parseCommaSeparated(flags["excludeFields"] ?? flags["exclude-fields"]),
206
+ count: Boolean(flags["count"])
207
+ })
208
+ });
209
+ }
210
+ var DURATION_SUFFIXES = {
211
+ w: 7 * 24 * 60 * 60 * 1000,
212
+ d: 24 * 60 * 60 * 1000,
213
+ h: 60 * 60 * 1000,
214
+ m: 60 * 1000
215
+ };
216
+ function parseDate(value, nowMs = Date.now()) {
217
+ if (typeof value !== "string" || value === "")
218
+ return;
219
+ const durationMatch = value.match(/^(\d+(?:\.\d+)?)(w|d|h|m)$/);
220
+ if (durationMatch) {
221
+ const amount = Number(durationMatch[1]);
222
+ const suffix = durationMatch[2];
223
+ const multiplier = suffix ? DURATION_SUFFIXES[suffix] : undefined;
224
+ if (amount > 0 && multiplier !== undefined) {
225
+ return new Date(nowMs - amount * multiplier);
226
+ }
227
+ return;
228
+ }
229
+ const date = new Date(value);
230
+ if (Number.isNaN(date.getTime()))
231
+ return;
232
+ return date;
233
+ }
234
+ function sanitizePositiveInteger(value, fallback) {
235
+ const parsed = Number(value);
236
+ if (!Number.isFinite(parsed) || parsed <= 0)
237
+ return fallback;
238
+ return Math.floor(parsed);
239
+ }
240
+ function timeWindowPreset(config) {
241
+ return createPreset({
242
+ id: "timeWindow",
243
+ options: [
244
+ {
245
+ flags: "--since <date>",
246
+ description: "Start of time window (ISO date or duration: 7d, 24h, 2w)"
247
+ },
248
+ {
249
+ flags: "--until <date>",
250
+ description: "End of time window (ISO date or duration: 7d, 24h, 2w)"
251
+ }
252
+ ],
253
+ resolve: (flags) => {
254
+ const now = Date.now();
255
+ const since = parseDate(flags["since"], now);
256
+ const until = parseDate(flags["until"], now);
257
+ if (since && until && typeof config?.maxRange === "number" && Number.isFinite(config.maxRange) && config.maxRange > 0) {
258
+ const range = Math.abs(until.getTime() - since.getTime());
259
+ if (range > config.maxRange) {
260
+ return {
261
+ since: undefined,
262
+ until: undefined
263
+ };
264
+ }
265
+ }
266
+ return { since, until };
267
+ }
268
+ });
269
+ }
270
+ function executionPreset(config) {
271
+ const defaultTimeout = config?.defaultTimeout;
272
+ const defaultRetries = config?.defaultRetries ?? 0;
273
+ const maxRetries = config?.maxRetries ?? 10;
274
+ return createPreset({
275
+ id: "execution",
276
+ options: [
277
+ {
278
+ flags: "--timeout <ms>",
279
+ description: "Timeout in milliseconds"
280
+ },
281
+ {
282
+ flags: "--retries <n>",
283
+ description: `Number of retries (default: ${defaultRetries}, max: ${maxRetries})`
284
+ },
285
+ {
286
+ flags: "--offline",
287
+ description: "Operate in offline mode",
288
+ defaultValue: false
289
+ }
290
+ ],
291
+ resolve: (flags) => {
292
+ let timeout = defaultTimeout;
293
+ const rawTimeout = flags["timeout"];
294
+ if (rawTimeout !== undefined) {
295
+ const parsed = Number(rawTimeout);
296
+ if (Number.isFinite(parsed) && parsed > 0) {
297
+ timeout = Math.floor(parsed);
298
+ } else {
299
+ timeout = undefined;
300
+ }
301
+ }
302
+ let retries = defaultRetries;
303
+ const rawRetries = flags["retries"];
304
+ if (rawRetries !== undefined) {
305
+ const parsed = Number(rawRetries);
306
+ if (Number.isFinite(parsed) && parsed >= 0) {
307
+ retries = Math.min(Math.floor(parsed), maxRetries);
308
+ }
309
+ }
310
+ return {
311
+ timeout,
312
+ retries,
313
+ offline: Boolean(flags["offline"])
314
+ };
315
+ }
316
+ });
317
+ }
318
+ function paginationPreset(config) {
319
+ const maxLimit = sanitizePositiveInteger(config?.maxLimit, 100);
320
+ const defaultLimit = Math.min(sanitizePositiveInteger(config?.defaultLimit, 20), maxLimit);
321
+ return createPreset({
322
+ id: "pagination",
323
+ options: [
324
+ {
325
+ flags: "-l, --limit <n>",
326
+ description: `Maximum number of results (default: ${defaultLimit}, max: ${maxLimit})`
327
+ },
328
+ {
329
+ flags: "--next",
330
+ description: "Continue from last position",
331
+ defaultValue: false
332
+ },
333
+ {
334
+ flags: "--reset",
335
+ description: "Clear saved cursor and start fresh",
336
+ defaultValue: false
337
+ }
338
+ ],
339
+ resolve: (flags) => {
340
+ let limit = defaultLimit;
341
+ const rawLimit = flags["limit"];
342
+ if (rawLimit !== undefined) {
343
+ const parsed = Number(rawLimit);
344
+ if (Number.isFinite(parsed) && parsed > 0) {
345
+ limit = Math.min(Math.floor(parsed), maxLimit);
346
+ }
347
+ }
348
+ return {
349
+ limit,
350
+ next: Boolean(flags["next"]),
351
+ reset: Boolean(flags["reset"])
352
+ };
353
+ }
354
+ });
355
+ }
356
+
357
+ export { createPreset, composePresets, verbosePreset, cwdPreset, dryRunPreset, forcePreset, interactionPreset, strictPreset, colorPreset, projectionPreset, timeWindowPreset, executionPreset, paginationPreset };
@@ -1,4 +1,4 @@
1
- import { OutputOptions } from "./cli-02kyvj7h";
1
+ import { OutputOptions } from "./cli-md9347gn";
2
2
  /**
3
3
  * Output data to the console with automatic mode selection.
4
4
  *
@@ -1,3 +1,4 @@
1
+ import { ActionCliOption } from "@outfitter/contracts";
1
2
  import { Command } from "commander";
2
3
  import { CancelledError, ErrorCategory, ValidationError } from "@outfitter/contracts";
3
4
  import { Result } from "better-result";
@@ -79,12 +80,164 @@ interface CommandBuilder {
79
80
  requiredOption(flags: string, description: string, defaultValue?: unknown): this;
80
81
  /** Add command aliases */
81
82
  alias(alias: string): this;
83
+ /** Apply a flag preset (adds its options to the command) */
84
+ preset(preset: FlagPreset<Record<string, unknown>>): this;
82
85
  /** Set the action handler */
83
86
  action<TFlags extends CommandFlags = CommandFlags>(handler: CommandAction<TFlags>): this;
84
87
  /** Build the underlying Commander command */
85
88
  build(): Command;
86
89
  }
87
90
  /**
91
+ * A family of related command verbs with a primary name and aliases.
92
+ */
93
+ interface VerbFamily {
94
+ /** Primary verb name */
95
+ readonly primary: string;
96
+ /** Alternative names for this verb */
97
+ readonly aliases: readonly string[];
98
+ /** Description of what this verb family does */
99
+ readonly description: string;
100
+ }
101
+ /**
102
+ * Configuration for resolving a verb family with project-level overrides.
103
+ */
104
+ interface VerbConfig {
105
+ /** Override the primary verb (e.g., "edit" instead of "modify") */
106
+ readonly primary?: string;
107
+ /** Whether to include aliases (default: true) */
108
+ readonly aliases?: boolean;
109
+ /** Additional aliases beyond defaults */
110
+ readonly extraAliases?: readonly string[];
111
+ /** Aliases to exclude */
112
+ readonly excludeAliases?: readonly string[];
113
+ }
114
+ /**
115
+ * A composable set of CLI flags with typed resolution.
116
+ *
117
+ * Presets bundle flag definitions with a resolver that coerces
118
+ * raw Commander output into typed values.
119
+ */
120
+ interface FlagPreset<TResolved extends Record<string, unknown>> {
121
+ /** Unique identifier for deduplication in composePresets */
122
+ readonly id: string;
123
+ /** Commander option definitions */
124
+ readonly options: readonly ActionCliOption[];
125
+ /** Resolve raw Commander flags into typed values */
126
+ readonly resolve: (flags: Record<string, unknown>) => TResolved;
127
+ }
128
+ /**
129
+ * Configuration for creating a flag preset.
130
+ */
131
+ interface FlagPresetConfig<TResolved extends Record<string, unknown>> {
132
+ /** Unique identifier for deduplication */
133
+ readonly id: string;
134
+ /** Commander option definitions */
135
+ readonly options: readonly ActionCliOption[];
136
+ /** Resolve raw Commander flags into typed values */
137
+ readonly resolve: (flags: Record<string, unknown>) => TResolved;
138
+ }
139
+ /**
140
+ * Result of composing multiple presets together.
141
+ * Options are deduplicated by preset id (first wins).
142
+ */
143
+ type ComposedPreset<TResolved extends Record<string, unknown>> = FlagPreset<TResolved>;
144
+ /**
145
+ * Configuration for the pagination flag preset.
146
+ */
147
+ interface PaginationPresetConfig {
148
+ /** Default limit when not specified (default: 20) */
149
+ readonly defaultLimit?: number;
150
+ /** Maximum allowed limit (default: 100) */
151
+ readonly maxLimit?: number;
152
+ }
153
+ /**
154
+ * Resolved interaction flags from CLI input.
155
+ */
156
+ type InteractionFlags = {
157
+ /** Whether interactive prompts are allowed */
158
+ readonly interactive: boolean;
159
+ /** Whether to auto-confirm prompts */
160
+ readonly yes: boolean;
161
+ };
162
+ /**
163
+ * Resolved strict mode flags from CLI input.
164
+ */
165
+ type StrictFlags = {
166
+ /** Whether strict mode is enabled */
167
+ readonly strict: boolean;
168
+ };
169
+ /**
170
+ * Resolved time-window flags from CLI input.
171
+ */
172
+ type TimeWindowFlags = {
173
+ /** Start of time window */
174
+ readonly since: Date | undefined;
175
+ /** End of time window */
176
+ readonly until: Date | undefined;
177
+ };
178
+ /**
179
+ * Configuration for the time-window flag preset.
180
+ */
181
+ interface TimeWindowPresetConfig {
182
+ /** Maximum range in milliseconds between since and until (optional guard) */
183
+ readonly maxRange?: number;
184
+ }
185
+ /**
186
+ * Resolved execution flags from CLI input.
187
+ */
188
+ type ExecutionFlags = {
189
+ /** Timeout in milliseconds (undefined = no timeout) */
190
+ readonly timeout: number | undefined;
191
+ /** Number of retries */
192
+ readonly retries: number;
193
+ /** Whether to operate in offline mode */
194
+ readonly offline: boolean;
195
+ };
196
+ /**
197
+ * Configuration for the execution flag preset.
198
+ */
199
+ interface ExecutionPresetConfig {
200
+ /** Default timeout in milliseconds (default: undefined) */
201
+ readonly defaultTimeout?: number;
202
+ /** Default number of retries (default: 0) */
203
+ readonly defaultRetries?: number;
204
+ /** Maximum number of retries (default: 10) */
205
+ readonly maxRetries?: number;
206
+ }
207
+ /**
208
+ * Resolved projection flags from CLI input.
209
+ */
210
+ type ProjectionFlags = {
211
+ /** Fields to include (undefined = all) */
212
+ readonly fields: string[] | undefined;
213
+ /** Fields to exclude (undefined = none) */
214
+ readonly excludeFields: string[] | undefined;
215
+ /** Whether to output only the count of results */
216
+ readonly count: boolean;
217
+ };
218
+ /**
219
+ * Color mode for CLI output.
220
+ */
221
+ type ColorMode = "auto" | "always" | "never";
222
+ /**
223
+ * Resolved color flags from CLI input.
224
+ */
225
+ type ColorFlags = {
226
+ /** Color output mode */
227
+ readonly color: ColorMode;
228
+ };
229
+ /**
230
+ * Resolved pagination flags from CLI input.
231
+ */
232
+ type PaginationFlags = {
233
+ /** Number of results to return */
234
+ readonly limit: number;
235
+ /** Continue from last position */
236
+ readonly next: boolean;
237
+ /** Clear saved cursor and start fresh */
238
+ readonly reset: boolean;
239
+ };
240
+ /**
88
241
  * Available output modes for CLI commands.
89
242
  */
90
243
  type OutputMode = "human" | "json" | "jsonl" | "tree" | "table";
@@ -239,4 +392,4 @@ interface CursorOptions {
239
392
  /** Total count of results (if known) */
240
393
  readonly total?: number;
241
394
  }
242
- export { CLIConfig, CLI, CommandConfig, CommandAction, CommandFlags, CommandBuilder, OutputMode, OutputOptions, CollectIdsOptions, ExpandFileOptions, ParseGlobOptions, NormalizeIdOptions, Range, NumericRange, DateRange, FilterExpression, SortCriteria, KeyValuePair, PaginationState, CursorOptions, CancelledError, ErrorCategory, ValidationError, Result };
395
+ export { CLIConfig, CLI, CommandConfig, CommandAction, CommandFlags, CommandBuilder, VerbFamily, VerbConfig, FlagPreset, FlagPresetConfig, ComposedPreset, PaginationPresetConfig, InteractionFlags, StrictFlags, TimeWindowFlags, TimeWindowPresetConfig, ExecutionFlags, ExecutionPresetConfig, ProjectionFlags, ColorMode, ColorFlags, PaginationFlags, OutputMode, OutputOptions, CollectIdsOptions, ExpandFileOptions, ParseGlobOptions, NormalizeIdOptions, Range, NumericRange, DateRange, FilterExpression, SortCriteria, KeyValuePair, PaginationState, CursorOptions, CancelledError, ErrorCategory, ValidationError, Result };
package/dist/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { CLI, CLIConfig, CancelledError, CollectIdsOptions, CommandAction, CommandBuilder, CommandConfig, CommandFlags, CursorOptions, DateRange, ErrorCategory, ExpandFileOptions, FilterExpression, KeyValuePair, NormalizeIdOptions, NumericRange, OutputMode, OutputOptions, PaginationState, ParseGlobOptions, Range, Result, SortCriteria, ValidationError } from "./shared/@outfitter/cli-02kyvj7h";
2
- export { ValidationError, SortCriteria, Result, Range, ParseGlobOptions, PaginationState, OutputOptions, OutputMode, NumericRange, NormalizeIdOptions, KeyValuePair, FilterExpression, ExpandFileOptions, ErrorCategory, DateRange, CursorOptions, CommandFlags, CommandConfig, CommandBuilder, CommandAction, CollectIdsOptions, CancelledError, CLIConfig, CLI };
1
+ import { CLI, CLIConfig, CancelledError, CollectIdsOptions, ColorFlags, ColorMode, CommandAction, CommandBuilder, CommandConfig, CommandFlags, ComposedPreset, CursorOptions, DateRange, ErrorCategory, ExecutionFlags, ExecutionPresetConfig, ExpandFileOptions, FilterExpression, FlagPreset, FlagPresetConfig, InteractionFlags, KeyValuePair, NormalizeIdOptions, NumericRange, OutputMode, OutputOptions, PaginationFlags, PaginationPresetConfig, PaginationState, ParseGlobOptions, ProjectionFlags, Range, Result, SortCriteria, StrictFlags, TimeWindowFlags, TimeWindowPresetConfig, ValidationError, VerbConfig, VerbFamily } from "./shared/@outfitter/cli-md9347gn";
2
+ export { VerbFamily, VerbConfig, ValidationError, TimeWindowPresetConfig, TimeWindowFlags, StrictFlags, SortCriteria, Result, Range, ProjectionFlags, ParseGlobOptions, PaginationState, PaginationPresetConfig, PaginationFlags, OutputOptions, OutputMode, NumericRange, NormalizeIdOptions, KeyValuePair, InteractionFlags, FlagPresetConfig, FlagPreset, FilterExpression, ExpandFileOptions, ExecutionPresetConfig, ExecutionFlags, ErrorCategory, DateRange, CursorOptions, ComposedPreset, CommandFlags, CommandConfig, CommandBuilder, CommandAction, ColorMode, ColorFlags, CollectIdsOptions, CancelledError, CLIConfig, CLI };
@@ -0,0 +1,50 @@
1
+ import { CommandBuilder, VerbConfig, VerbFamily } from "./shared/@outfitter/cli-md9347gn";
2
+ /**
3
+ * Built-in verb families with standard primary verbs and aliases.
4
+ *
5
+ * These follow POSIX and modern CLI conventions:
6
+ * - `create` / `new` — Create a new resource
7
+ * - `modify` / `edit` / `update` — Modify an existing resource
8
+ * - `remove` / `delete` / `rm` — Remove a resource
9
+ * - `list` / `ls` — List resources
10
+ * - `show` / `get` / `view` — Show resource details
11
+ */
12
+ declare const VERB_FAMILIES: Readonly<Record<string, VerbFamily>>;
13
+ /**
14
+ * Resolve a verb family with optional project-level config overrides.
15
+ *
16
+ * @param family - Name of the verb family (e.g., "create", "modify")
17
+ * @param config - Optional overrides for primary verb, aliases, etc.
18
+ * @returns Resolved verb name and aliases
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * resolveVerb("modify"); // { name: "modify", aliases: ["edit", "update"] }
23
+ * resolveVerb("modify", { primary: "edit" }); // { name: "edit", aliases: ["update"] }
24
+ * resolveVerb("remove", { aliases: false }); // { name: "remove", aliases: [] }
25
+ * ```
26
+ */
27
+ declare function resolveVerb(family: string, config?: VerbConfig): {
28
+ name: string;
29
+ aliases: string[];
30
+ };
31
+ /**
32
+ * Apply verb conventions to a CommandBuilder.
33
+ *
34
+ * Adds aliases from the resolved verb family to the command builder.
35
+ *
36
+ * @param builder - CommandBuilder to apply aliases to
37
+ * @param family - Name of the verb family
38
+ * @param config - Optional overrides
39
+ * @returns The builder for chaining
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const cmd = command("remove <id>")
44
+ * .description("Remove a resource");
45
+ * applyVerb(cmd, "remove");
46
+ * // Adds aliases: "delete", "rm"
47
+ * ```
48
+ */
49
+ declare function applyVerb(builder: CommandBuilder, family: string, config?: VerbConfig): CommandBuilder;
50
+ export { resolveVerb, applyVerb, VERB_FAMILIES };
package/dist/verbs.js ADDED
@@ -0,0 +1,61 @@
1
+ // @bun
2
+ // packages/cli/src/verbs.ts
3
+ var VERB_FAMILIES = {
4
+ create: {
5
+ primary: "create",
6
+ aliases: ["new"],
7
+ description: "Create a new resource"
8
+ },
9
+ modify: {
10
+ primary: "modify",
11
+ aliases: ["edit", "update"],
12
+ description: "Modify a resource"
13
+ },
14
+ remove: {
15
+ primary: "remove",
16
+ aliases: ["delete", "rm"],
17
+ description: "Remove a resource"
18
+ },
19
+ list: {
20
+ primary: "list",
21
+ aliases: ["ls"],
22
+ description: "List resources"
23
+ },
24
+ show: {
25
+ primary: "show",
26
+ aliases: ["get", "view"],
27
+ description: "Show details"
28
+ }
29
+ };
30
+ function resolveVerb(family, config) {
31
+ const def = VERB_FAMILIES[family];
32
+ if (!Object.hasOwn(VERB_FAMILIES, family) || def === undefined) {
33
+ throw new Error(`Unknown verb family: "${family}"`);
34
+ }
35
+ const name = config?.primary ?? def.primary;
36
+ if (config?.aliases === false) {
37
+ return { name, aliases: [] };
38
+ }
39
+ let aliases = [...def.aliases].filter((a) => a !== name);
40
+ if (config?.extraAliases) {
41
+ aliases.push(...config.extraAliases);
42
+ }
43
+ if (config?.excludeAliases) {
44
+ const exclude = new Set(config.excludeAliases);
45
+ aliases = aliases.filter((a) => !exclude.has(a));
46
+ }
47
+ aliases = [...new Set(aliases)];
48
+ return { name, aliases };
49
+ }
50
+ function applyVerb(builder, family, config) {
51
+ const { aliases } = resolveVerb(family, config);
52
+ for (const alias of aliases) {
53
+ builder.alias(alias);
54
+ }
55
+ return builder;
56
+ }
57
+ export {
58
+ resolveVerb,
59
+ applyVerb,
60
+ VERB_FAMILIES
61
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@outfitter/cli",
3
3
  "description": "Typed CLI runtime with terminal detection, rendering, output contracts, and input parsing",
4
- "version": "0.4.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
@@ -33,6 +33,18 @@
33
33
  "default": "./dist/command.js"
34
34
  }
35
35
  },
36
+ "./completion": {
37
+ "import": {
38
+ "types": "./dist/completion.d.ts",
39
+ "default": "./dist/completion.js"
40
+ }
41
+ },
42
+ "./flags": {
43
+ "import": {
44
+ "types": "./dist/flags.d.ts",
45
+ "default": "./dist/flags.js"
46
+ }
47
+ },
36
48
  "./input": {
37
49
  "import": {
38
50
  "types": "./dist/input.d.ts",
@@ -52,6 +64,12 @@
52
64
  "default": "./dist/pagination.js"
53
65
  }
54
66
  },
67
+ "./query": {
68
+ "import": {
69
+ "types": "./dist/query.d.ts",
70
+ "default": "./dist/query.js"
71
+ }
72
+ },
55
73
  "./terminal": {
56
74
  "import": {
57
75
  "types": "./dist/terminal/index.d.ts",
@@ -75,6 +93,12 @@
75
93
  "types": "./dist/types.d.ts",
76
94
  "default": "./dist/types.js"
77
95
  }
96
+ },
97
+ "./verbs": {
98
+ "import": {
99
+ "types": "./dist/verbs.d.ts",
100
+ "default": "./dist/verbs.js"
101
+ }
78
102
  }
79
103
  },
80
104
  "sideEffects": false,