Flag
Defines named options for command-line applications.
A Flag<A> describes how to read one named value from parsed command-line
input, validate it, and produce an A. Flags are useful for inputs such as
ports, verbosity switches, configuration files, output directories, choices,
secrets, and repeated values. The helpers here build flags with aliases,
defaults, optional values, prompts, configuration fallbacks, validation, and
value transformations.
Aliasing
Adds an alias to a flag, allowing it to be referenced by multiple names.
Signature
declare const withAlias: { <A>(alias: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, alias: string): Flag<A>;}import { Flag } from "effect/unstable/cli"
// Flag can be used as both --verbose and -vconst verboseFlag = Flag.Boolean("verbose").pipe( Flag.withAlias("v"))
// Multiple aliases can be chainedconst helpFlag = Flag.Boolean("help").pipe( Flag.withAlias("h"), Flag.withAlias("?"))const kinds = [verboseFlag.kind, helpFlag.kind] // => ["flag", "flag"]Alternatives
Provides an alternative flag if the first one fails to parse.
Signature
declare const orElse: { <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<A | B>;}import { Flag } from "effect/unstable/cli"
// Try parsing as integer, fallback to stringconst valueFlag = Flag.orElse( Flag.Int("value"), () => Flag.String("value"))
// Multiple input sources with fallbackconst configFlag = Flag.orElse( Flag.File("config"), () => Flag.String("config-url"))const kinds = [valueFlag.kind, configFlag.kind] // => ["flag", "flag"]orElseResult
Tries to parse with the first flag, then the second, returning a Result that indicates which succeeded.
Signature
declare const orElseResult: { <B>(that: LazyArg<Flag<B>>): <A>(self: Flag<A>) => Flag<Result<A, B>>; <A, B>(self: Flag<A>, that: LazyArg<Flag<B>>): Flag<Result<A, B>>;}import { Effect, FileSystem, Layer, Path, Result, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const sourceFlag = Flag.orElseResult( Flag.String("source"), () => Flag.String("source-url"))
const program = Effect.gen(function*() { const [, source] = yield* sourceFlag.parse({ arguments: [], flags: { "source-url": ["https://example.com"] } }) return source})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Result.fail("https://example.com")Combinators
withFallbackConfig
Adds a fallback config that is loaded when a required flag is missing.
Signature
declare const withFallbackConfig: { <B>(config: Config<B>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, config: Config<B>): Flag<A | B>;}import { Config } from "effect"import { Flag } from "effect/unstable/cli"
const verbose = Flag.Boolean("verbose").pipe( Flag.withFallbackConfig(Config.Boolean("VERBOSE")))verbose.kind // => "flag"withFallbackPrompt
Adds a fallback prompt that is shown when a required flag is missing.
Signature
declare const withFallbackPrompt: { <B>(prompt: FallbackPrompt<B>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, prompt: FallbackPrompt<B>): Flag<A | B>;}import { Flag, Prompt } from "effect/unstable/cli"
const name = Flag.String("name").pipe( Flag.withFallbackPrompt(Prompt.String({ message: "Name" })))name.kind // => "flag"Constructors
Creates a boolean flag that can be enabled or disabled.
Signature
declare function Boolean(name: string): Flag<boolean>import { Flag } from "effect/unstable/cli"
const verboseFlag = Flag.Boolean("verbose")// Usage: --verbose (true) or --no-verbose (false)// Omission fails unless the flag is made optional or given a fallback.verboseFlag.kind // => "flag"ChoiceWithValue
Constructs option parameters that represent a choice between several inputs. Each tuple maps a string flag value to an associated typed value.
Signature
declare function ChoiceWithValue<Choice extends readonly Array<readonly [string, any]>>(name: string, choices: Choice): Flag<Choice[number][1]>import { Flag } from "effect/unstable/cli"
// simple enum like choice mapping directly to string unionconst color = Flag.Literals("color", ["red", "green", "blue"])
// choice with custom value mappingconst logLevel = Flag.ChoiceWithValue("log-level", [ ["debug", "Debug" as const], ["info", "Info" as const], ["error", "Error" as const]])const kinds = [color.kind, logLevel.kind] // => ["flag", "flag"]Creates a date flag that accepts date input in ISO format.
Signature
declare function Date(name: string): Flag<Date>import { Flag } from "effect/unstable/cli"
const startDateFlag = Flag.Date("start-date")// Usage: --start-date 2023-12-25startDateFlag.kind // => "flag"Creates a directory path flag that accepts directory paths with optional existence validation.
Signature
declare function Directory(name: string, options?: { readonly mustExist?: boolean;}): Flag<string>import { Flag } from "effect/unstable/cli"
// Basic directory flagconst outputFlag = Flag.Directory("output")// Usage: --output ./build
// Directory that must existconst sourceFlag = Flag.Directory("source", { mustExist: true })// Usage: --source ./src (directory must exist)const kinds = [outputFlag.kind, sourceFlag.kind] // => ["flag", "flag"]Creates a file path flag that accepts file paths with optional existence validation.
Signature
declare function File(name: string, options?: { readonly mustExist?: boolean;}): Flag<string>import { Flag } from "effect/unstable/cli"
// Basic file flagconst inputFlag = Flag.File("input")// Usage: --input ./data.json
// File that must existconst configFlag = Flag.File("config", { mustExist: true })// Usage: --config ./config.yaml (file must exist)const kinds = [inputFlag.kind, configFlag.kind] // => ["flag", "flag"]Creates a flag that reads and parses the content of the specified file.
Details
The parser that is utilized will depend on the specified format, or the
extension of the file passed on the command-line if no format is specified.
Signature
declare function FileParse(name: string, options?: FileParseOptions): Flag<unknown>import { Flag } from "effect/unstable/cli"
// Will use the extension of the file passed on the command line to determine// the parser to useconst config = Flag.FileParse("config")
// Will use the JSON parserconst jsonConfig = Flag.FileParse("json-config", { format: "json" })const kinds = [config.kind, jsonConfig.kind] // => ["flag", "flag"]FileSchema
Creates a flag that reads and validates file content using the specified schema.
Signature
declare function FileSchema<A>(name: string, schema: ConstraintDecoder<A, Environment>, options?: { readonly errorFormatter?: Formatter<string>; readonly format?: "json" | "ini" | "toml" | "yaml";}): Flag<A>import { Schema } from "effect"import { Flag } from "effect/unstable/cli"
const ConfigSchema = Schema.Struct({ port: Schema.Number, host: Schema.String})
const config = Flag.FileSchema("config", ConfigSchema, { format: "json" })config.kind // => "flag"Creates a flag that reads and returns file content as a string.
Signature
declare function FileText(name: string): Flag<string>import { Flag } from "effect/unstable/cli"
const config = Flag.FileText("config-file")// --config-file ./app.json will read the file contentconfig.kind // => "flag"Creates a float flag that accepts decimal number input.
Signature
declare function Finite(name: string): Flag<number>import { Flag } from "effect/unstable/cli"
const rateFlag = Flag.Finite("rate")// Usage: --rate 3.14rateFlag.kind // => "flag"Creates an integer flag that accepts whole number input.
Signature
declare function Int(name: string): Flag<number>import { Flag } from "effect/unstable/cli"
const portFlag = Flag.Int("port")// Usage: --port 8080portFlag.kind // => "flag"KeyValuePair
Creates a flag that parses key=value pairs.
When to use
Use when you need a CLI flag that accepts one or more key=value
configuration entries.
Details
Requires at least one key=value pair. Multiple pairs are merged into a single record.
Signature
declare function KeyValuePair(name: string): Flag<Record<string, string>>import { Flag } from "effect/unstable/cli"
const env = Flag.KeyValuePair("env")// --env FOO=bar --env BAZ=qux will parse to { FOO: "bar", BAZ: "qux" }env.kind // => "flag"Accepts one of the provided strings. An empty array rejects all input.
See
- ChoiceWithValue for mapping accepted strings to different typed values
Signature
declare function Literals<Literals extends readonly Array<string>>(name: string, literals: Literals): Flag<Literals[number]>A flag that always fails to parse.
Signature
declare const Never: Flag<never>import { Flag } from "effect/unstable/cli"
const makeValueFlag = (includeValue: boolean) => includeValue ? Flag.String("value") : Flag.Never
makeValueFlag(true) === Flag.Never // => falsemakeValueFlag(false) === Flag.Never // => trueCreates a path flag that accepts file system path input with validation options.
Signature
declare function Path(name: string, options?: { readonly mustExist?: boolean; readonly pathType?: "either" | "file" | "directory"; readonly typeName?: string;}): Flag<string>import { Flag } from "effect/unstable/cli"
// Basic path flagconst pathFlag = Flag.Path("config-path")
// File-only path that must existconst fileFlag = Flag.Path("input-file", { pathType: "file", mustExist: true})
// Directory path with custom type nameconst dirFlag = Flag.Path("output-dir", { pathType: "directory", typeName: "OUTPUT_DIRECTORY"})const kinds = [pathFlag.kind, fileFlag.kind, dirFlag.kind] // => ["flag", "flag", "flag"]Creates a string flag whose parsed value is wrapped in Redacted.Redacted so
stringification and logging redact the value.
Gotchas
Values supplied on the command line may still be visible to the operating system or shell history.
Signature
declare function Redacted(name: string): Flag<Redacted<string>>import { Effect, FileSystem, Layer, Path, Redacted, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const passwordFlag = Flag.Redacted("password")
const program = Effect.gen(function*() { const [, password] = yield* passwordFlag.parse({ arguments: [], flags: { "password": ["abc123"] } }) return Redacted.value(password).length})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => 6Creates a string flag that accepts text input.
Signature
declare function String(name: string): Flag<string>import { Flag } from "effect/unstable/cli"
const nameFlag = Flag.String("name")// Usage: --name "John Doe"nameFlag.kind // => "flag"Filtering
Filters a flag value based on a predicate, failing with a custom error if the predicate returns false.
Signature
declare const filter: { <A>(predicate: (a: A) => boolean, onFalse: (a: A) => string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, predicate: (a: A) => boolean, onFalse: (a: A) => string): Flag<A>;}import { Flag } from "effect/unstable/cli"
// Ensure port is in valid rangeconst portFlag = Flag.Int("port").pipe( Flag.filter( (port) => port >= 1 && port <= 65535, (port) => `Port ${port} is out of range (1-65535)` ))
// Ensure non-empty stringconst nameFlag = Flag.String("name").pipe( Flag.filter( (name) => name.trim().length > 0, () => "Name cannot be empty" ))const kinds = [portFlag.kind, nameFlag.kind] // => ["flag", "flag"]Transforms and filters a flag value, failing with a custom error if the transformation returns None.
Signature
declare const filterMap: { <A, B>(f: (a: A) => Option<B>, onNone: (a: A) => string): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => Option<B>, onNone: (a: A) => string): Flag<B>;}import { Option } from "effect"import { Flag } from "effect/unstable/cli"
// Parse positive integers onlyconst positiveInt = Flag.Int("count").pipe( Flag.filterMap( (n) => n > 0 ? Option.some(n) : Option.none(), (n) => `Expected positive integer, got ${n}` ))
// Parse valid email addressesconst emailFlag = Flag.String("email").pipe( Flag.filterMap( (email) => email.includes("@") ? Option.some(email) : Option.none(), (email) => `Invalid email address: ${email}` ))const kinds = [positiveInt.kind, emailFlag.kind] // => ["flag", "flag"]Mapping
Transforms the parsed value of a flag using a mapping function.
Signature
declare const map: { <A, B>(f: (a: A) => B): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => B): Flag<B>;}import { Flag } from "effect/unstable/cli"
// Convert string to uppercaseconst nameFlag = Flag.String("name").pipe( Flag.map((name) => name.toUpperCase()))
// Convert port to URLconst urlFlag = Flag.Int("port").pipe( Flag.map((port) => `http://localhost:${port}`))const kinds = [nameFlag.kind, urlFlag.kind] // => ["flag", "flag"]Transforms the parsed value using an Effect that can perform IO operations.
Signature
declare const mapEffect: { <A, B>(f: (a: A) => Effect<B, CliError, Environment>): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => Effect<B, CliError, Environment>): Flag<B>;}import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const upperName = Flag.String("name").pipe( Flag.mapEffect((name) => Effect.succeed(name.toUpperCase())))
const [, value] = await Effect.runPromise( upperName.parse({ arguments: [], flags: { name: ["alice"] } }).pipe(Effect.provide(CliTestLayer)))value // => "ALICE"mapTryCatch
Transforms the parsed value using a function that might throw, with error handling.
Signature
declare const mapTryCatch: { <A, B>(f: (a: A) => B, onError: (error: unknown) => string): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, f: (a: A) => B, onError: (error: unknown) => string): Flag<B>;}import { Effect, FileSystem, Layer, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
// Parse JSON string with error handlingconst jsonFlag = Flag.String("config").pipe( Flag.mapTryCatch( (json) => JSON.parse(json), (error) => `Invalid JSON: ${error}` ))
// Parse URL with error handlingconst urlFlag = Flag.String("url").pipe( Flag.mapTryCatch( (url) => new URL(url), (error) => `Invalid URL: ${error}` ))
const [, value] = await Effect.runPromise( jsonFlag.parse({ arguments: [], flags: { config: ['{"enabled":true}'] } }).pipe(Effect.provide(CliTestLayer)))value // => { enabled: true }Metadata
withDescription
Adds a description to a flag for help documentation.
Signature
declare const withDescription: { <A>(description: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, description: string): Flag<A>;}import { Flag } from "effect/unstable/cli"
const portFlag = Flag.Int("port").pipe( Flag.withDescription("The port number to listen on"))
const configFlag = Flag.File("config").pipe( Flag.withDescription("Path to the configuration file"))const kinds = [portFlag.kind, configFlag.kind] // => ["flag", "flag"]withHidden
Hides a flag from generated help output and shell completions while keeping it fully parseable on the command line.
When to use
Use when experimental or internal flags should be accepted but not advertised, such as
--experimental-foo, debug toggles, or escape hatches that are not yet committed to the
public CLI surface.
Signature
declare function withHidden<A>(self: Flag<A>): Flag<A>import { Flag } from "effect/unstable/cli"
// Flag still parses --experimental-foo, but it does not appear in --help.const experimental = Flag.Boolean("experimental-foo").pipe( Flag.withHidden)experimental.kind // => "flag"withMetavar
Sets a custom metavar (placeholder name) for the flag in help documentation.
Details
The metavar is displayed in usage text to indicate what value the user should
provide. For example, --output FILE shows FILE as the metavar.
Signature
declare const withMetavar: { <A>(metavar: string): (self: Flag<A>) => Flag<A>; <A>(self: Flag<A>, metavar: string): Flag<A>;}import { Flag } from "effect/unstable/cli"
const databaseFlag = Flag.String("database-url").pipe( Flag.withMetavar("URL"), Flag.withDescription("Database connection URL"))// In help: --database-url URL
const timeoutFlag = Flag.Int("timeout").pipe( Flag.withMetavar("SECONDS"))// In help: --timeout SECONDSconst kinds = [databaseFlag.kind, timeoutFlag.kind] // => ["flag", "flag"]Models
Optionality
Makes a flag optional, returning an Option type that can be None if not provided.
Signature
declare function optional<A>(param: Flag<A>): Flag<Option<A>>import { Effect, FileSystem, Layer, Option, Path, Stdio, Terminal } from "effect"import { Flag } from "effect/unstable/cli"import { ChildProcessSpawner } from "effect/unstable/process"
const CliTestLayer = Layer.mergeAll( FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({}), Layer.succeed(Terminal.Terminal, Terminal.make({ columns: Effect.succeed(80), rows: Effect.succeed(24), readInput: Effect.die("unused"), readLine: Effect.die("unused"), display: () => Effect.void })), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unused")) ))
const optionalPort = Flag.optional(Flag.Int("port"))
const program = Effect.gen(function*() { const [, port] = yield* optionalPort.parse({ arguments: [], flags: { "port": ["4000"] } }) return port})
await Effect.runPromise(program.pipe(Effect.provide(CliTestLayer))) // => Option.some(4000)withDefault
Provides a default value for a flag when it's not specified.
Signature
declare const withDefault: { <B>(defaultValue: B | Effect<B, CliError, Environment>): <A>(self: Flag<A>) => Flag<B | A>; <A, B>(self: Flag<A>, defaultValue: B | Effect<B, CliError, Environment>): Flag<A | B>;}import { Flag } from "effect/unstable/cli"
const portFlag = Flag.Int("port").pipe( Flag.withDefault(8080))// If --port is not provided, defaults to 8080
const hostFlag = Flag.String("host").pipe( Flag.withDefault("localhost"))// If --host is not provided, defaults to "localhost"const kinds = [portFlag.kind, hostFlag.kind] // => ["flag", "flag"]Repetition
Ensures a flag is specified at least a minimum number of times.
Signature
declare const atLeast: { <A>(min: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, min: number): Flag<readonly Array<A>>;}import { Flag } from "effect/unstable/cli"
const sourceFlag = Flag.atLeast(Flag.File("source"), 2)// Requires at least 2 source files// Usage: --source file1.ts --source file2.ts
const tagFlag = Flag.String("tag").pipe( Flag.atLeast(1))// Requires at least 1 tagconst kinds = [sourceFlag.kind, tagFlag.kind] // => ["flag", "flag"]Ensures a flag is specified at most a maximum number of times.
Signature
declare const atMost: { <A>(max: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, max: number): Flag<readonly Array<A>>;}import { Flag } from "effect/unstable/cli"
const warningFlag = Flag.atMost(Flag.String("warning"), 3)// Allows up to 3 warning flags// Usage: --warning w1 --warning w2 --warning w3
const debugFlag = Flag.String("debug").pipe( Flag.atMost(1))// Allows at most 1 debug flagconst kinds = [warningFlag.kind, debugFlag.kind] // => ["flag", "flag"]Ensures a flag is specified between a minimum and maximum number of times.
Signature
declare const between: { <A>(min: number, max: number): (self: Flag<A>) => Flag<readonly Array<A>>; <A>(self: Flag<A>, min: number, max: number): Flag<readonly Array<A>>;}import { Flag } from "effect/unstable/cli"
const hostFlag = Flag.between(Flag.String("host"), 1, 3)// Requires 1-3 host flags// Usage: --host host1 --host host2
const excludeFlag = Flag.String("exclude").pipe( Flag.between(0, 5))// Allows 0-5 exclude patternsconst kinds = [hostFlag.kind, excludeFlag.kind] // => ["flag", "flag"]Schemas
withSchema
Validates and transforms a flag value using a Schema codec.
Signature
declare const withSchema: { <A, B>(schema: ConstraintCodec<B, A, Environment, unknown>): (self: Flag<A>) => Flag<B>; <A, B>(self: Flag<A>, schema: ConstraintCodec<B, A, Environment, unknown>): Flag<B>;}import { Schema } from "effect"import { Flag } from "effect/unstable/cli"
const isEmail = Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, { message: "Must be a valid email address"})
// Parse and validate email with custom schemaconst EmailSchema = Schema.String.pipe( Schema.check(isEmail))
const emailFlag = Flag.String("email").pipe( Flag.withSchema(EmailSchema))
// Parse JSON configuration with schema validationconst ConfigSchema = Schema.Struct({ port: Schema.Number, host: Schema.String, ssl: Schema.optional(Schema.Boolean)}).pipe(Schema.fromJsonString)
const configFlag = Flag.String("config").pipe( Flag.withSchema(ConfigSchema))const kinds = [emailFlag.kind, configFlag.kind] // => ["flag", "flag"]