1/**
2 * Defines named options for command-line applications.
3 *
4 * A `Flag<A>` describes how to read one named value from parsed command-line
5 * input, validate it, and produce an `A`. Flags are useful for inputs such as
6 * ports, verbosity switches, configuration files, output directories, choices,
7 * secrets, and repeated values. The helpers here build flags with aliases,
8 * defaults, optional values, prompts, configuration fallbacks, validation, and
9 * value transformations.
10 *
11 * @stability unstable
12 * @since 4.0.0
13 */
14import type * as Config from "../Config.ts"
15import type * as Effect from "../Effect.ts"
16import { dual, type LazyArg } from "../Function.ts"
17import type * as Option from "../Option.ts"
18import type * as Redacted_ from "../Redacted.ts"
19import type * as Result from "../Result.ts"
20import type * as Schema from "../Schema.ts"
21import type * as CliError from "./CliError.ts"
22import type { Environment } from "./Command.ts"
23import * as Param from "./Param.ts"
24import type * as Primitive from "./Primitive.ts"
25
26// -------------------------------------------------------------------------------------
27// models
28// -------------------------------------------------------------------------------------
29
30/**
31 * Represents a command-line flag.
32 *
33 * @stability unstable
34 * @category models
35 * @since 4.0.0
36 */
37export interface Flag<A> extends Param.Param<typeof Param.flagKind, A> {}
38
39// -------------------------------------------------------------------------------------
40// constructors
41// -------------------------------------------------------------------------------------
42
43/**
44 * Creates a string flag that accepts text input.
45 *
46 * **Example** (Creating string flags)
47 *
48 * ```ts import.meta.vitest
49 * import { Flag } from "effect/cli"
50 *
51 * const nameFlag = Flag.String("name")
52 * // Usage: --name "John Doe"
53 * nameFlag.kind // => "flag"
54 * ```
55 *
56 * @stability unstable
57 * @category constructors
58 * @since 4.0.0
59 */
60export const String = (name: string): Flag<string> => Param.String(Param.flagKind, name)
61
62/**
63 * Creates a boolean flag that can be enabled or disabled.
64 *
65 * **Example** (Creating boolean flags)
66 *
67 * ```ts import.meta.vitest
68 * import { Flag } from "effect/cli"
69 *
70 * const verboseFlag = Flag.Boolean("verbose")
71 * // Usage: --verbose (true) or --no-verbose (false)
72 * // Omission fails unless the flag is made optional or given a fallback.
73 * verboseFlag.kind // => "flag"
74 * ```
75 *
76 * @stability unstable
77 * @category constructors
78 * @since 4.0.0
79 */
80export const Boolean = (name: string): Flag<boolean> => Param.Boolean(Param.flagKind, name)
81
82/**
83 * Creates an integer flag that accepts whole number input.
84 *
85 * **Example** (Creating integer flags)
86 *
87 * ```ts import.meta.vitest
88 * import { Flag } from "effect/cli"
89 *
90 * const portFlag = Flag.Int("port")
91 * // Usage: --port 8080
92 * portFlag.kind // => "flag"
93 * ```
94 *
95 * @stability unstable
96 * @category constructors
97 * @since 4.0.0
98 */
99export const Int = (name: string): Flag<number> => Param.Int(Param.flagKind, name)
100
101/**
102 * Creates a float flag that accepts decimal number input.
103 *
104 * **Example** (Creating float flags)
105 *
106 * ```ts import.meta.vitest
107 * import { Flag } from "effect/cli"
108 *
109 * const rateFlag = Flag.Finite("rate")
110 * // Usage: --rate 3.14
111 * rateFlag.kind // => "flag"
112 * ```
113 *
114 * @stability unstable
115 * @category constructors
116 * @since 4.0.0
117 */
118export const Finite = (name: string): Flag<number> => Param.Finite(Param.flagKind, name)
119
120/**
121 * Creates a date flag that accepts date input in ISO format.
122 *
123 * **Example** (Creating date flags)
124 *
125 * ```ts import.meta.vitest
126 * import { Flag } from "effect/cli"
127 *
128 * const startDateFlag = Flag.Date("start-date")
129 * // Usage: --start-date 2023-12-25
130 * startDateFlag.kind // => "flag"
131 * ```
132 *
133 * @stability unstable
134 * @category constructors
135 * @since 4.0.0
136 */
137export const Date = (name: string): Flag<globalThis.Date> => Param.Date(Param.flagKind, name)
138
139/**
140 * Constructs option parameters that represent a choice between several inputs.
141 * Each tuple maps a string flag value to an associated typed value.
142 *
143 * **Example** (Creating flag choices with values)
144 *
145 * ```ts import.meta.vitest
146 * import { Flag } from "effect/cli"
147 *
148 * // simple enum like choice mapping directly to string union
149 * const color = Flag.Literals("color", ["red", "green", "blue"])
150 *
151 * // choice with custom value mapping
152 * const logLevel = Flag.ChoiceWithValue("log-level", [
153 * ["debug", "Debug" as const],
154 * ["info", "Info" as const],
155 * ["error", "Error" as const]
156 * ])
157 * const kinds = [color.kind, logLevel.kind] // => ["flag", "flag"]
158 * ```
159 *
160 * @stability unstable
161 * @category constructors
162 * @since 4.0.0
163 */
164export const ChoiceWithValue = <const Choice extends ReadonlyArray<readonly [string, any]>>(
165 name: string,
166 choices: Choice
167): Flag<Choice[number][1]> => Param.ChoiceWithValue(Param.flagKind, name, choices)
168
169/**
170 * Accepts one of the provided strings. An empty array rejects all input.
171 *
172 * @see {@link ChoiceWithValue} for mapping accepted strings to different typed values
173 *
174 * @stability unstable
175 * @category constructors
176 * @since 4.0.0
177 */
178export const Literals = <const Literals extends ReadonlyArray<string>>(
179 name: string,
180 literals: Literals
181): Flag<Literals[number]> => Param.Literals(Param.flagKind, name, literals)
182
183/**
184 * Creates a path flag that accepts file system path input with validation options.
185 *
186 * **Example** (Creating path flags)
187 *
188 * ```ts import.meta.vitest
189 * import { Flag } from "effect/cli"
190 *
191 * // Basic path flag
192 * const pathFlag = Flag.Path("config-path")
193 *
194 * // File-only path that must exist
195 * const fileFlag = Flag.Path("input-file", {
196 * pathType: "file",
197 * mustExist: true
198 * })
199 *
200 * // Directory path with custom type name
201 * const dirFlag = Flag.Path("output-dir", {
202 * pathType: "directory",
203 * typeName: "OUTPUT_DIRECTORY"
204 * })
205 * const kinds = [pathFlag.kind, fileFlag.kind, dirFlag.kind] // => ["flag", "flag", "flag"]
206 * ```
207 *
208 * @stability unstable
209 * @category constructors
210 * @since 4.0.0
211 */
212export const Path = (name: string, options?: {
213 readonly pathType?: "file" | "directory" | "either" | undefined
214 readonly mustExist?: boolean | undefined
215 readonly typeName?: string | undefined
216}): Flag<string> => Param.Path(Param.flagKind, name, options)
217
218/**
219 * Creates a file path flag that accepts file paths with optional existence validation.
220 *
221 * **Example** (Creating file flags)
222 *
223 * ```ts import.meta.vitest
224 * import { Flag } from "effect/cli"
225 *
226 * // Basic file flag
227 * const inputFlag = Flag.File("input")
228 * // Usage: --input ./data.json
229 *
230 * // File that must exist
231 * const configFlag = Flag.File("config", { mustExist: true })
232 * // Usage: --config ./config.yaml (file must exist)
233 * const kinds = [inputFlag.kind, configFlag.kind] // => ["flag", "flag"]
234 * ```
235 *
236 * @stability unstable
237 * @category constructors
238 * @since 4.0.0
239 */
240export const File = (name: string, options?: {
241 readonly mustExist?: boolean | undefined
242}): Flag<string> => Param.File(Param.flagKind, name, options)
243
244/**
245 * Creates a directory path flag that accepts directory paths with optional existence validation.
246 *
247 * **Example** (Creating directory flags)
248 *
249 * ```ts import.meta.vitest
250 * import { Flag } from "effect/cli"
251 *
252 * // Basic directory flag
253 * const outputFlag = Flag.Directory("output")
254 * // Usage: --output ./build
255 *
256 * // Directory that must exist
257 * const sourceFlag = Flag.Directory("source", { mustExist: true })
258 * // Usage: --source ./src (directory must exist)
259 * const kinds = [outputFlag.kind, sourceFlag.kind] // => ["flag", "flag"]
260 * ```
261 *
262 * @stability unstable
263 * @category constructors
264 * @since 4.0.0
265 */
266export const Directory = (name: string, options?: {
267 readonly mustExist?: boolean | undefined
268}): Flag<string> => Param.Directory(Param.flagKind, name, options)
269
270/**
271 * Creates a string flag whose parsed value is wrapped in `Redacted.Redacted` so
272 * stringification and logging redact the value.
273 *
274 * **Gotchas**
275 *
276 * Values supplied on the command line may still be visible to the operating
277 * system or shell history.
278 *
279 * **Example** (Creating redacted flags)
280 *
281 * ```ts import.meta.vitest
282 * import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect"
283 * import { Flag } from "effect/cli"
284 * import { ChildProcessSpawner } from "effect/process"
285 *
286 * const CliTestLayer = Layer.mergeAll(
287 * FileSystem.layerNoop({}),
288 * Path.layer,
289 * Stdio.layerTest({}),
290 * Layer.succeed(Terminal.Terminal, Terminal.make({
291 * columns: Effect.succeed(80),
292 * rows: Effect.succeed(24),
293 * readInput: Effect.die("unused"),
294 * readLine: Effect.die("unused"),
295 * display: () => Effect.void
296 * })),
297 * Layer.succeed(
298 * ChildProcessSpawner.ChildProcessSpawner,
299 * ChildProcessSpawner.make(() => Effect.die("unused"))
300 * )
301 * )
302 *
303 * const passwordFlag = Flag.Redacted("password")
304 *
305 * const program = Effect.gen(function*() {
306 * const [, password] = yield* passwordFlag.parse({
307 * arguments: [],
308 * flags: { "password": ["abc123"] }
309 * })
310 * return Redacted.value(password).length
311 * })
312 *
313 * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => 6
314 * ```
315 *
316 * @stability unstable
317 * @category constructors
318 * @since 4.0.0
319 */
320export const Redacted = (name: string): Flag<Redacted_.Redacted<string>> => Param.Redacted(Param.flagKind, name)
321
322/**
323 * Creates a flag that reads and returns file content as a string.
324 *
325 * **Example** (Reading file text)
326 *
327 * ```ts import.meta.vitest
328 * import { Flag } from "effect/cli"
329 *
330 * const config = Flag.FileText("config-file")
331 * // --config-file ./app.json will read the file content
332 * config.kind // => "flag"
333 * ```
334 *
335 * @stability unstable
336 * @category constructors
337 * @since 4.0.0
338 */
339export const FileText = (name: string): Flag<string> => Param.FileText(Param.flagKind, name)
340
341/**
342 * Creates a flag that reads and parses the content of the specified file.
343 *
344 * **Details**
345 *
346 * The parser that is utilized will depend on the specified `format`, or the
347 * extension of the file passed on the command-line if no `format` is specified.
348 *
349 * **Example** (Parsing file contents)
350 *
351 * ```ts import.meta.vitest
352 * import { Flag } from "effect/cli"
353 *
354 * // Will use the extension of the file passed on the command line to determine
355 * // the parser to use
356 * const config = Flag.FileParse("config")
357 *
358 * // Will use the JSON parser
359 * const jsonConfig = Flag.FileParse("json-config", { format: "json" })
360 * const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"]
361 * ```
362 *
363 * @stability unstable
364 * @category constructors
365 * @since 4.0.0
366 */
367export const FileParse = (
368 name: string,
369 options?: Primitive.FileParseOptions | undefined
370): Flag<unknown> => Param.FileParse(Param.flagKind, name, options)
371
372/**
373 * Creates a flag that reads and validates file content using the specified
374 * schema.
375 *
376 * **Example** (Validating file contents)
377 *
378 * ```ts import.meta.vitest
379 * import { Schema } from "effect"
380 * import { Flag } from "effect/cli"
381 *
382 * const ConfigSchema = Schema.Struct({
383 * port: Schema.Number,
384 * host: Schema.String
385 * })
386 *
387 * const config = Flag.FileSchema("config", ConfigSchema, { format: "json" })
388 * config.kind // => "flag"
389 * ```
390 *
391 * @stability unstable
392 * @category constructors
393 * @since 4.0.0
394 */
395export const FileSchema = <A>(
396 name: string,
397 schema: Schema.ConstraintDecoder<A, Environment>,
398 options?: Primitive.FileSchemaOptions | undefined
399): Flag<A> => Param.FileSchema(Param.flagKind, name, schema, options)
400
401/**
402 * Creates a flag that parses key=value pairs.
403 *
404 * **When to use**
405 *
406 * Use when you need a CLI flag that accepts one or more `key=value`
407 * configuration entries.
408 *
409 * **Details**
410 *
411 * Requires at least one key=value pair. Multiple pairs are merged into a single
412 * record.
413 *
414 * **Example** (Parsing key-value pairs)
415 *
416 * ```ts import.meta.vitest
417 * import { Flag } from "effect/cli"
418 *
419 * const env = Flag.KeyValuePair("env")
420 * // --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" }
421 * env.kind // => "flag"
422 * ```
423 *
424 * @stability unstable
425 * @category constructors
426 * @since 4.0.0
427 */
428export const KeyValuePair = (name: string): Flag<Record<string, string>> => Param.KeyValuePair(Param.flagKind, name)
429
430/**
431 * A flag that always fails to parse.
432 *
433 * **Example** (Creating sentinel flags)
434 *
435 * ```ts import.meta.vitest
436 * import { Flag } from "effect/cli"
437 *
438 * const makeValueFlag = (includeValue: boolean) =>
439 * includeValue ? Flag.String("value") : Flag.Never
440 *
441 * makeValueFlag(true) === Flag.Never // => false
442 * makeValueFlag(false) === Flag.Never // => true
443 * ```
444 *
445 * @stability unstable
446 * @category constructors
447 * @since 4.0.0
448 */
449export const Never: Flag<never> = Param.Never(Param.flagKind)
450
451// -------------------------------------------------------------------------------------
452// combinators
453// -------------------------------------------------------------------------------------
454
455/**
456 * Adds an alias to a flag, allowing it to be referenced by multiple names.
457 *
458 * **Example** (Adding flag aliases)
459 *
460 * ```ts import.meta.vitest
461 * import { Flag } from "effect/cli"
462 *
463 * // Flag can be used as both --verbose and -v
464 * const verboseFlag = Flag.Boolean("verbose").pipe(
465 * Flag.withAlias("v")
466 * )
467 *
468 * // Multiple aliases can be chained
469 * const helpFlag = Flag.Boolean("help").pipe(
470 * Flag.withAlias("h"),
471 * Flag.withAlias("?")
472 * )
473 * const kinds = [verboseFlag.kind, helpFlag.kind] // => ["flag", "flag"]
474 * ```
475 *
476 * @stability unstable
477 * @category aliasing
478 * @since 4.0.0
479 */
480export const withAlias: {
481 <A>(alias: string): (self: Flag<A>) => Flag<A>
482 <A>(self: Flag<A>, alias: string): Flag<A>
483} = dual(2, <A>(self: Flag<A>, alias: string): Flag<A> => Param.withAlias(self, alias))
484
485/**
486 * Adds a description to a flag for help documentation.
487 *
488 * **Example** (Adding help descriptions)
489 *
490 * ```ts import.meta.vitest
491 * import { Flag } from "effect/cli"
492 *
493 * const portFlag = Flag.Int("port").pipe(
494 * Flag.withDescription("The port number to listen on")
495 * )
496 *
497 * const configFlag = Flag.File("config").pipe(
498 * Flag.withDescription("Path to the configuration file")
499 * )
500 * const kinds = [portFlag.kind, configFlag.kind] // => ["flag", "flag"]
501 * ```
502 *
503 * @stability unstable
504 * @category metadata
505 * @since 4.0.0
506 */
507export const withDescription: {
508 <A>(description: string): (self: Flag<A>) => Flag<A>
509 <A>(self: Flag<A>, description: string): Flag<A>
510} = dual(2, <A>(self: Flag<A>, description: string) => Param.withDescription(self, description))
511
512// -------------------------------------------------------------------------------------
513// metadata
514// -------------------------------------------------------------------------------------
515
516/**
517 * Sets a custom metavar (placeholder name) for the flag in help documentation.
518 *
519 * **Details**
520 *
521 * The metavar is displayed in usage text to indicate what value the user should
522 * provide. For example, `--output FILE` shows `FILE` as the metavar.
523 *
524 * **Example** (Setting metavars)
525 *
526 * ```ts import.meta.vitest
527 * import { Flag } from "effect/cli"
528 *
529 * const databaseFlag = Flag.String("database-url").pipe(
530 * Flag.withMetavar("URL"),
531 * Flag.withDescription("Database connection URL")
532 * )
533 * // In help: --database-url URL
534 *
535 * const timeoutFlag = Flag.Int("timeout").pipe(
536 * Flag.withMetavar("SECONDS")
537 * )
538 * // In help: --timeout SECONDS
539 * const kinds = [databaseFlag.kind, timeoutFlag.kind] // => ["flag", "flag"]
540 * ```
541 *
542 * @stability unstable
543 * @category metadata
544 * @since 4.0.0
545 */
546export const withMetavar: {
547 <A>(metavar: string): (self: Flag<A>) => Flag<A>
548 <A>(self: Flag<A>, metavar: string): Flag<A>
549} = dual(2, <A>(self: Flag<A>, metavar: string) => Param.withMetavar(self, metavar))
550
551/**
552 * Hides a flag from generated help output and shell completions while keeping
553 * it fully parseable on the command line.
554 *
555 * **When to use**
556 *
557 * Use when experimental or internal flags should be accepted but not advertised, such as
558 * `--experimental-foo`, debug toggles, or escape hatches that are not yet committed to the
559 * public CLI surface.
560 *
561 * **Example** (Hiding a flag from help)
562 *
563 * ```ts import.meta.vitest
564 * import { Flag } from "effect/cli"
565 *
566 * // Flag still parses --experimental-foo, but it does not appear in --help.
567 * const experimental = Flag.Boolean("experimental-foo").pipe(
568 * Flag.withHidden
569 * )
570 * experimental.kind // => "flag"
571 * ```
572 *
573 * @stability unstable
574 * @category metadata
575 * @since 4.0.0
576 */
577export const withHidden = <A>(self: Flag<A>): Flag<A> => Param.withHidden(self)
578
579/**
580 * Makes a flag optional, returning an Option type that can be None if not provided.
581 *
582 * **Example** (Making flags optional)
583 *
584 * ```ts import.meta.vitest
585 * import { Effect, FileSystem, Layer, Option, Path, Stdio, Terminal } from "effect"
586 * import { Flag } from "effect/cli"
587 * import { ChildProcessSpawner } from "effect/process"
588 *
589 * const CliTestLayer = Layer.mergeAll(
590 * FileSystem.layerNoop({}),
591 * Path.layer,
592 * Stdio.layerTest({}),
593 * Layer.succeed(Terminal.Terminal, Terminal.make({
594 * columns: Effect.succeed(80),
595 * rows: Effect.succeed(24),
596 * readInput: Effect.die("unused"),
597 * readLine: Effect.die("unused"),
598 * display: () => Effect.void
599 * })),
600 * Layer.succeed(
601 * ChildProcessSpawner.ChildProcessSpawner,
602 * ChildProcessSpawner.make(() => Effect.die("unused"))
603 * )
604 * )
605 *
606 * const optionalPort = Flag.optional(Flag.Int("port"))
607 *
608 * const program = Effect.gen(function*() {
609 * const [, port] = yield* optionalPort.parse({
610 * arguments: [],
611 * flags: { "port": ["4000"] }
612 * })
613 * return port
614 * })
615 *
616 * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Option.some(4000)
617 * ```
618 *
619 * @stability unstable
620 * @category optionality
621 * @since 4.0.0
622 */
623export const optional = <A>(param: Flag<A>): Flag<Option.Option<A>> => Param.optional(param)
624
625/**
626 * Provides a default value for a flag when it's not specified.
627 *
628 * **Example** (Providing default values)
629 *
630 * ```ts import.meta.vitest
631 * import { Flag } from "effect/cli"
632 *
633 * const portFlag = Flag.Int("port").pipe(
634 * Flag.withDefault(8080)
635 * )
636 * // If --port is not provided, defaults to 8080
637 *
638 * const hostFlag = Flag.String("host").pipe(
639 * Flag.withDefault("localhost")
640 * )
641 * // If --host is not provided, defaults to "localhost"
642 * const kinds = [portFlag.kind, hostFlag.kind] // => ["flag", "flag"]
643 * ```
644 *
645 * @stability unstable
646 * @category optionality
647 * @since 4.0.0
648 */
649export const withDefault: {
650 <const B>(defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): <A>(self: Flag<A>) => Flag<A | B>
651 <A, const B>(self: Flag<A>, defaultValue: B | Effect.Effect<B, CliError.CliError, Environment>): Flag<A | B>
652} = Param.withDefault
653
654/**
655 * Adds a fallback config that is loaded when a required flag is missing.
656 *
657 * **Example** (Falling back to config)
658 *
659 * ```ts import.meta.vitest
660 * import { Config } from "effect"
661 * import { Flag } from "effect/cli"
662 *
663 * const verbose = Flag.Boolean("verbose").pipe(
664 * Flag.withFallbackConfig(Config.Boolean("VERBOSE"))
665 * )
666 * verbose.kind // => "flag"
667 * ```
668 *
669 * @stability unstable
670 * @category combinators
671 * @since 4.0.0
672 */
673export const withFallbackConfig: {
674 <B>(config: Config.Config<B>): <A>(self: Flag<A>) => Flag<A | B>
675 <A, B>(self: Flag<A>, config: Config.Config<B>): Flag<A | B>
676} = dual(2, <A, B>(self: Flag<A>, config: Config.Config<B>) => Param.withFallbackConfig(self, config))
677
678/**
679 * Adds a fallback prompt that is shown when a required flag is missing.
680 *
681 * **Example** (Falling back to prompts)
682 *
683 * ```ts import.meta.vitest
684 * import { Flag, Prompt } from "effect/cli"
685 *
686 * const name = Flag.String("name").pipe(
687 * Flag.withFallbackPrompt(Prompt.String({ message: "Name" }))
688 * )
689 * name.kind // => "flag"
690 * ```
691 *
692 * @stability unstable
693 * @category combinators
694 * @since 4.0.0
695 */
696export const withFallbackPrompt: {
697 <B>(prompt: Param.FallbackPrompt<B>): <A>(self: Flag<A>) => Flag<A | B>
698 <A, B>(self: Flag<A>, prompt: Param.FallbackPrompt<B>): Flag<A | B>
699} = dual(2, <A, B>(self: Flag<A>, prompt: Param.FallbackPrompt<B>) => Param.withFallbackPrompt(self, prompt))
700
701/**
702 * Transforms the parsed value of a flag using a mapping function.
703 *
704 * **Example** (Mapping parsed values)
705 *
706 * ```ts import.meta.vitest
707 * import { Flag } from "effect/cli"
708 *
709 * // Convert string to uppercase
710 * const nameFlag = Flag.String("name").pipe(
711 * Flag.map((name) => name.toUpperCase())
712 * )
713 *
714 * // Convert port to URL
715 * const urlFlag = Flag.Int("port").pipe(
716 * Flag.map((port) => `http://localhost:${port}`)
717 * )
718 * const kinds = [nameFlag.kind, urlFlag.kind] // => ["flag", "flag"]
719 * ```
720 *
721 * @stability unstable
722 * @category mapping
723 * @since 4.0.0
724 */
725export const map: {
726 <A, B>(f: (a: A) => B): (self: Flag<A>) => Flag<B>
727 <A, B>(self: Flag<A>, f: (a: A) => B): Flag<B>
728} = dual(2, <A, B>(self: Flag<A>, f: (a: A) => B) => Param.map(self, f))
729
730/**
731 * Transforms the parsed value using an Effect that can perform IO operations.
732 *
733 * **Example** (Mapping parsed values effectfully)
734 *
735 * ```ts import.meta.vitest
736 * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"
737 * import { Flag } from "effect/cli"
738 * import { ChildProcessSpawner } from "effect/process"
739 *
740 * const CliTestLayer = Layer.mergeAll(
741 * FileSystem.layerNoop({}),
742 * Path.layer,
743 * Stdio.layerTest({}),
744 * Layer.succeed(Terminal.Terminal, Terminal.make({
745 * columns: Effect.succeed(80),
746 * rows: Effect.succeed(24),
747 * readInput: Effect.die("unused"),
748 * readLine: Effect.die("unused"),
749 * display: () => Effect.void
750 * })),
751 * Layer.succeed(
752 * ChildProcessSpawner.ChildProcessSpawner,
753 * ChildProcessSpawner.make(() => Effect.die("unused"))
754 * )
755 * )
756 *
757 * const upperName = Flag.String("name").pipe(
758 * Flag.mapEffect((name) => Effect.succeed(name.toUpperCase()))
759 * )
760 *
761 * const [, value] = await Effect.runPromise(
762 * upperName.parse({
763 * arguments: [],
764 * flags: { name: ["alice"] }
765 * }).pipe(Effect.provide(CliTestLayer))
766 * )
767 * value // => "ALICE"
768 * ```
769 *
770 * @stability unstable
771 * @category mapping
772 * @since 4.0.0
773 */
774export const mapEffect: {
775 <A, B>(
776 f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
777 ): (self: Flag<A>) => Flag<B>
778 <A, B>(
779 self: Flag<A>,
780 f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
781 ): Flag<B>
782} = dual(2, <A, B>(
783 self: Flag<A>,
784 f: (a: A) => Effect.Effect<B, CliError.CliError, Environment>
785) => Param.mapEffect(self, f))
786
787/**
788 * Transforms the parsed value using a function that might throw, with error handling.
789 *
790 * **Example** (Mapping thrown errors)
791 *
792 * ```ts import.meta.vitest
793 * import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"
794 * import { Flag } from "effect/cli"
795 * import { ChildProcessSpawner } from "effect/process"
796 *
797 * const CliTestLayer = Layer.mergeAll(
798 * FileSystem.layerNoop({}),
799 * Path.layer,
800 * Stdio.layerTest({}),
801 * Layer.succeed(Terminal.Terminal, Terminal.make({
802 * columns: Effect.succeed(80),
803 * rows: Effect.succeed(24),
804 * readInput: Effect.die("unused"),
805 * readLine: Effect.die("unused"),
806 * display: () => Effect.void
807 * })),
808 * Layer.succeed(
809 * ChildProcessSpawner.ChildProcessSpawner,
810 * ChildProcessSpawner.make(() => Effect.die("unused"))
811 * )
812 * )
813 *
814 * // Parse JSON string with error handling
815 * const jsonFlag = Flag.String("config").pipe(
816 * Flag.mapTryCatch(
817 * (json) => JSON.parse(json),
818 * (error) => `Invalid JSON: ${error}`
819 * )
820 * )
821 *
822 * // Parse URL with error handling
823 * const urlFlag = Flag.String("url").pipe(
824 * Flag.mapTryCatch(
825 * (url) => new URL(url),
826 * (error) => `Invalid URL: ${error}`
827 * )
828 * )
829 *
830 * const [, value] = await Effect.runPromise(
831 * jsonFlag.parse({
832 * arguments: [],
833 * flags: { config: ['{"enabled":true}'] }
834 * }).pipe(Effect.provide(CliTestLayer))
835 * )
836 * value // => { enabled: true }
837 * ```
838 *
839 * @stability unstable
840 * @category mapping
841 * @since 4.0.0
842 */
843export const mapTryCatch: {
844 <A, B>(f: (a: A) => B, onError: (error: unknown) => string): (self: Flag<A>) => Flag<B>
845 <A, B>(self: Flag<A>, f: (a: A) => B, onError: (error: unknown) => string): Flag<B>
846} = dual(3, <A, B>(
847 self: Flag<A>,
848 f: (a: A) => B,
849 onError: (error: unknown) => string
850) => Param.mapTryCatch(self, f, onError))
851
852/**
853 * Ensures a flag is specified at least a minimum number of times.
854 *
855 * **Example** (Requiring repeated values)
856 *
857 * ```ts import.meta.vitest
858 * import { Flag } from "effect/cli"
859 *
860 * const sourceFlag = Flag.atLeast(Flag.File("source"), 2)
861 * // Requires at least 2 source files
862 * // Usage: --source file1.ts --source file2.ts
863 *
864 * const tagFlag = Flag.String("tag").pipe(
865 * Flag.atLeast(1)
866 * )
867 * // Requires at least 1 tag
868 * const kinds = [sourceFlag.kind, tagFlag.kind] // => ["flag", "flag"]
869 * ```
870 *
871 * @stability unstable
872 * @category repetition
873 * @since 4.0.0
874 */
875export const atLeast: {
876 <A>(min: number): (self: Flag<A>) => Flag<ReadonlyArray<A>>
877 <A>(self: Flag<A>, min: number): Flag<ReadonlyArray<A>>
878} = dual(2, <A>(self: Flag<A>, min: number) => Param.atLeast(self, min))
879
880/**
881 * Ensures a flag is specified at most a maximum number of times.
882 *
883 * **Example** (Limiting repeated values)
884 *
885 * ```ts import.meta.vitest
886 * import { Flag } from "effect/cli"
887 *
888 * const warningFlag = Flag.atMost(Flag.String("warning"), 3)
889 * // Allows up to 3 warning flags
890 * // Usage: --warning w1 --warning w2 --warning w3
891 *
892 * const debugFlag = Flag.String("debug").pipe(
893 * Flag.atMost(1)
894 * )
895 * // Allows at most 1 debug flag
896 * const kinds = [warningFlag.kind, debugFlag.kind] // => ["flag", "flag"]
897 * ```
898 *
899 * @stability unstable
900 * @category repetition
901 * @since 4.0.0
902 */
903export const atMost: {
904 <A>(max: number): (self: Flag<A>) => Flag<ReadonlyArray<A>>
905 <A>(self: Flag<A>, max: number): Flag<ReadonlyArray<A>>
906} = dual(2, <A>(self: Flag<A>, max: number) => Param.atMost(self, max))
907
908/**
909 * Ensures a flag is specified between a minimum and maximum number of times.
910 *
911 * **Example** (Bounding repeated values)
912 *
913 * ```ts import.meta.vitest
914 * import { Flag } from "effect/cli"
915 *
916 * const hostFlag = Flag.between(Flag.String("host"), 1, 3)
917 * // Requires 1-3 host flags
918 * // Usage: --host host1 --host host2
919 *
920 * const excludeFlag = Flag.String("exclude").pipe(
921 * Flag.between(0, 5)
922 * )
923 * // Allows 0-5 exclude patterns
924 * const kinds = [hostFlag.kind, excludeFlag.kind] // => ["flag", "flag"]
925 * ```
926 *
927 * @stability unstable
928 * @category repetition
929 * @since 4.0.0
930 */
931export const between: {
932 <A>(min: number, max: number): (self: Flag<A>) => Flag<ReadonlyArray<A>>
933 <A>(self: Flag<A>, min: number, max: number): Flag<ReadonlyArray<A>>
934} = dual(3, <A>(self: Flag<A>, min: number, max: number) => Param.between(self, min, max))
935
936/**
937 * Transforms and filters a flag value, failing with a custom error if the transformation returns None.
938 *
939 * **Example** (Filtering and transforming values)
940 *
941 * ```ts import.meta.vitest
942 * import { Option } from "effect"
943 * import { Flag } from "effect/cli"
944 *
945 * // Parse positive integers only
946 * const positiveInt = Flag.Int("count").pipe(
947 * Flag.filterMap(
948 * (n) => n > 0 ? Option.some(n) : Option.none(),
949 * (n) => `Expected positive integer, got ${n}`
950 * )
951 * )
952 *
953 * // Parse valid email addresses
954 * const emailFlag = Flag.String("email").pipe(
955 * Flag.filterMap(
956 * (email) => email.includes("@") ? Option.some(email) : Option.none(),
957 * (email) => `Invalid email address: ${email}`
958 * )
959 * )
960 * const kinds = [positiveInt.kind, emailFlag.kind] // => ["flag", "flag"]
961 * ```
962 *
963 * @stability unstable
964 * @category filtering
965 * @since 4.0.0
966 */
967export const filterMap: {
968 <A, B>(f: (a: A) => Option.Option<B>, onNone: (a: A) => string): (self: Flag<A>) => Flag<B>
969 <A, B>(self: Flag<A>, f: (a: A) => Option.Option<B>, onNone: (a: A) => string): Flag<B>
970} = dual(3, <A, B>(
971 self: Flag<A>,
972 f: (a: A) => Option.Option<B>,
973 onNone: (a: A) => string
974) => Param.filterMap(self, f, onNone))
975
976/**
977 * Filters a flag value based on a predicate, failing with a custom error if the predicate returns false.
978 *
979 * **Example** (Filtering parsed values)
980 *
981 * ```ts import.meta.vitest
982 * import { Flag } from "effect/cli"
983 *
984 * // Ensure port is in valid range
985 * const portFlag = Flag.Int("port").pipe(
986 * Flag.filter(
987 * (port) => port >= 1 && port <= 65535,
988 * (port) => `Port ${port} is out of range (1-65535)`
989 * )
990 * )
991 *
992 * // Ensure non-empty string
993 * const nameFlag = Flag.String("name").pipe(
994 * Flag.filter(
995 * (name) => name.trim().length > 0,
996 * () => "Name cannot be empty"
997 * )
998 * )
999 * const kinds = [portFlag.kind, nameFlag.kind] // => ["flag", "flag"]
1000 * ```
1001 *
1002 * @stability unstable
1003 * @category filtering
1004 * @since 4.0.0
1005 */
1006export const filter: {
1007 <A>(predicate: (a: A) => boolean, onFalse: (a: A) => string): (self: Flag<A>) => Flag<A>
1008 <A>(self: Flag<A>, predicate: (a: A) => boolean, onFalse: (a: A) => string): Flag<A>
1009} = dual(3, <A>(
1010 self: Flag<A>,
1011 predicate: (a: A) => boolean,
1012 onFalse: (a: A) => string
1013) => Param.filter(self, predicate, onFalse))
1014
1015/**
1016 * Provides an alternative flag if the first one fails to parse.
1017 *
1018 * **Example** (Falling back to another flag)
1019 *
1020 * ```ts import.meta.vitest
1021 * import { Flag } from "effect/cli"
1022 *
1023 * // Try parsing as integer, fallback to string
1024 * const valueFlag = Flag.orElse(
1025 * Flag.Int("value"),
1026 * () => Flag.String("value")
1027 * )
1028 *
1029 * // Multiple input sources with fallback
1030 * const configFlag = Flag.orElse(
1031 * Flag.File("config"),
1032 * () => Flag.String("config-url")
1033 * )
1034 * const kinds = [valueFlag.kind, configFlag.kind] // => ["flag", "flag"]
1035 * ```
1036 *
1037 * @stability unstable
1038 * @category alternatives
1039 * @since 4.0.0
1040 */
1041export const orElse: {
1042 <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<A | B>
1043 <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<A | B>
1044} = dual(2, <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>) => Param.orElse(self, that))
1045
1046/**
1047 * Tries to parse with the first flag, then the second, returning a Result that indicates which succeeded.
1048 *
1049 * **Example** (Returning fallback results)
1050 *
1051 * ```ts import.meta.vitest
1052 * import { Effect, FileSystem, Layer, Path, Result, Stdio, Terminal } from "effect"
1053 * import { Flag } from "effect/cli"
1054 * import { ChildProcessSpawner } from "effect/process"
1055 *
1056 * const CliTestLayer = Layer.mergeAll(
1057 * FileSystem.layerNoop({}),
1058 * Path.layer,
1059 * Stdio.layerTest({}),
1060 * Layer.succeed(Terminal.Terminal, Terminal.make({
1061 * columns: Effect.succeed(80),
1062 * rows: Effect.succeed(24),
1063 * readInput: Effect.die("unused"),
1064 * readLine: Effect.die("unused"),
1065 * display: () => Effect.void
1066 * })),
1067 * Layer.succeed(
1068 * ChildProcessSpawner.ChildProcessSpawner,
1069 * ChildProcessSpawner.make(() => Effect.die("unused"))
1070 * )
1071 * )
1072 *
1073 * const sourceFlag = Flag.orElseResult(
1074 * Flag.String("source"),
1075 * () => Flag.String("source-url")
1076 * )
1077 *
1078 * const program = Effect.gen(function*() {
1079 * const [, source] = yield* sourceFlag.parse({
1080 * arguments: [],
1081 * flags: { "source-url": ["https://example.com"] }
1082 * })
1083 * return source
1084 * })
1085 *
1086 * await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Result.fail("https://example.com")
1087 * ```
1088 *
1089 * @stability unstable
1090 * @category alternatives
1091 * @since 4.0.0
1092 */
1093export const orElseResult: {
1094 <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<Result.Result<A, B>>
1095 <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<Result.Result<A, B>>
1096} = dual(2, <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>) => Param.orElseResult(self, that))
1097
1098/**
1099 * Validates and transforms a flag value using a Schema codec.
1100 *
1101 * **Example** (Validating with schemas)
1102 *
1103 * ```ts import.meta.vitest
1104 * import { Schema } from "effect"
1105 * import { Flag } from "effect/cli"
1106 *
1107 * const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, {
1108 * message: "Must be a valid email address"
1109 * })
1110 *
1111 * // Parse and validate email with custom schema
1112 * const EmailSchema = Schema.String.pipe(
1113 * Schema.check(isEmail)
1114 * )
1115 *
1116 * const emailFlag = Flag.String("email").pipe(
1117 * Flag.withSchema(EmailSchema)
1118 * )
1119 *
1120 * // Parse JSON configuration with schema validation
1121 * const ConfigSchema = Schema.Struct({
1122 * port: Schema.Number,
1123 * host: Schema.String,
1124 * ssl: Schema.optional(Schema.Boolean)
1125 * }).pipe(Schema.fromJsonString)
1126 *
1127 * const configFlag = Flag.String("config").pipe(
1128 * Flag.withSchema(ConfigSchema)
1129 * )
1130 * const kinds = [emailFlag.kind, configFlag.kind] // => ["flag", "flag"]
1131 * ```
1132 *
1133 * @stability unstable
1134 * @category schemas
1135 * @since 4.0.0
1136 */
1137export const withSchema: {
1138 <A, B>(schema: Schema.ConstraintCodec<B, A, Environment, unknown>): (self: Flag<A>) => Flag<B>
1139 <A, B>(self: Flag<A>, schema: Schema.ConstraintCodec<B, A, Environment, unknown>): Flag<B>
1140} = dual(
1141 2,
1142 <A, B>(self: Flag<A>, schema: Schema.ConstraintCodec<B, A, Environment, unknown>) => Param.withSchema(self, schema)
1143)