# Arguments & Flags

A command's input is described by a **config object**: a record whose keys are
the field names your handler receives, and whose values are `Param`s. There are
two kinds of `Param`:

- **`Flag`** — a named option, written `--name value` (or `-n value` with an
  alias). Order-independent.
- **`Argument`** — a positional operand, matched left to right by position.

They share most constructors (`string`, `integer`, `choice`, `file`, …) and
combinators (`withDefault`, `optional`, `withSchema`, `map`, …), so once you know
one you mostly know the other. A few are specific to one kind — for example
`boolean` and `keyValuePair` are `Flag`-only, `withAlias` is `Flag`-only, and
`variadic` is `Argument`-only.

```ts
import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"

const deploy = Command.make(
  "deploy",
  {
    // Required positional argument. Without it, parsing fails with usage help.
    service: Argument.string("service").pipe(
      Argument.withDescription("Name of the service to deploy")
    ),

    // A choice flag restricts the value to a fixed set. The result type is the
    // union "staging" | "production", not just string.
    env: Flag.choice("env", ["staging", "production"]).pipe(
      Flag.withAlias("e"),
      Flag.withDescription("Target environment")
    ),

    // Optional flag with a default. Because of the default the field is always
    // present, so the handler never has to deal with "missing".
    replicas: Flag.integer("replicas").pipe(
      Flag.withDefault(1),
      Flag.withDescription("Number of replicas to run")
    ),

    // A boolean flag is true when present (`--force`) and false otherwise.
    // It also supports `--no-force` for explicit negation.
    force: Flag.boolean("force").pipe(
      Flag.withDescription("Skip the production confirmation prompt")
    )
  },
  Effect.fn(function*({ service, env, replicas, force }) {
    if (env === "production" && !force) {
      // Handlers are Effects, so failing is just `Effect.fail`.
      return yield* Effect.fail("Refusing to deploy to production without --force")
    }
    yield* Console.log(`Deploying ${service} to ${env} (${replicas} replicas)`)
  })
)
```

Invoked as `deploy api --env production --replicas 3 --force`, the handler
receives `{ service: "api", env: "production", replicas: 3, force: true }`,
fully typed.

## Required vs. optional

A bare `Flag` or `Argument` is **required** — if it is missing, parsing fails and
the user sees usage help. You make an input non-required in one of two ways:

```ts
import { Console, Effect, Option } from "effect"
import { Flag } from "effect/unstable/cli"

// 1. `withDefault` supplies a value when the flag is absent. The field type is
//    just the value type — the handler never sees the absence.
const port = Flag.integer("port").pipe(Flag.withDefault(8080))

// 2. `optional` wraps the value in an Option, so the handler can distinguish
//    "not provided" (Option.none) from any concrete value.
const tag = Flag.string("tag").pipe(Flag.optional)

const handler = Effect.fn(function*(config: {
  readonly port: number
  readonly tag: Option.Option<string>
}) {
  yield* Console.log(`port=${config.port}`)
  yield* Console.log(
    Option.match(config.tag, {
      onNone: () => "no tag supplied",
      onSome: (t) => `tag=${t}`
    })
  )
})
```
**Tip:** Reach for `withDefault` when there is a sensible fallback, and `optional` when
"absent" is genuinely a distinct case your logic must handle.

## Validating and transforming values

The built-in constructors already parse and validate primitive types (`integer`
rejects non-numbers, `choice` rejects values outside its set, `file` can require
existence). When you need more, use `withSchema` to run a
[Schema](https://effect.plants.sh/schema/) over the parsed value, or `map` to transform it.

```ts
import { Schema } from "effect"
import { Flag } from "effect/unstable/cli"

// Validate with a Schema: parsing fails with a clear error if the port is out
// of range. Schema lives in core `effect`.
const port = Flag.integer("port").pipe(
  Flag.withSchema(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 })))
)

// Transform a parsed value into a richer shape.
const endpoint = Flag.integer("port").pipe(
  Flag.map((p) => ({ port: p, url: `http://localhost:${p}` }))
)

// Read a secret as a Redacted value so it never prints in logs or errors.
const apiKey = Flag.redacted("api-key")
```

## Repeated and variadic inputs

Use `variadic` to collect a positional argument that may appear multiple times
into a `ReadonlyArray`. Pass `min`/`max` to bound how many are accepted.

```ts
import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"

const cat = Command.make(
  "cat",
  {
    // `cat a.txt b.txt c.txt` -> files: ["a.txt", "b.txt", "c.txt"]
    // `min: 1` makes at least one file mandatory.
    files: Argument.string("file").pipe(
      Argument.withDescription("Files to print"),
      Argument.variadic({ min: 1 })
    ),

    // A key=value flag merges repeated occurrences into one record:
    // `--define A=1 --define B=2` -> define: { A: "1", B: "2" }
    define: Flag.keyValuePair("define").pipe(
      Flag.withDescription("Template variables, repeatable")
    )
  },
  Effect.fn(function*({ files, define }) {
    for (const file of files) {
      yield* Console.log(`reading ${file}`)
    }
    yield* Console.log(JSON.stringify(define))
  })
)
```

For **flags**, repetition is expressed with the counting combinators
`atLeast` / `atMost` / `between` instead of `variadic` (see the reference
below) — each makes the flag repeatable and collects every occurrence into a
`ReadonlyArray`.

## Adding metadata

These combinators improve the generated `--help` output and ergonomics. They
work on any `Param` (and recursively find the underlying leaf, so they compose
after `optional`, `map`, etc.):

- `withDescription(text)` — help text shown next to the flag/argument.
- `withAlias("x")` *(Flag only)* — a short alias, e.g. `-x`.
- `withMetavar("NAME")` — the placeholder shown in usage, e.g. `<NAME>`.
- `withHidden` *(Flag only)* — omit from help and completions but still parse.
- `withFallbackConfig(config)` — fall back to a value from
  [Configuration](https://effect.plants.sh/configuration/) (an env var, for instance) when the flag is
  absent.

---

# Constructor reference

Every constructor below produces a leaf `Param`. Constructors marked *(Flag
only)* have no `Argument` counterpart. The `Argument` module deliberately omits
`boolean` (a positional `true`/`false` is ambiguous — use `Flag.boolean`, or
`Argument.choice` with explicit `"true"` / `"false"` strings) and
`keyValuePair`.

### `string`

Accepts any text value, with no validation. Available as `Flag.string(name)` and
`Argument.string(name)`. Result type: `string`.

```ts
import { Flag, Argument } from "effect/unstable/cli"

const name = Flag.string("name")          // --name "John Doe"  => "John Doe"
const filename = Argument.string("file")  // myfile.txt         => "myfile.txt"
```

### `integer`

Parses a whole number and rejects non-integer input. `Flag.integer(name)` /
`Argument.integer(name)`. Result type: `number`.

```ts
import { Flag } from "effect/unstable/cli"

const port = Flag.integer("port") // --port 8080 => 8080 ; --port 8.5 fails
```

### `float`

Parses a finite decimal number. `Flag.float(name)` / `Argument.float(name)`.
Result type: `number`.

```ts
import { Flag } from "effect/unstable/cli"

const rate = Flag.float("rate") // --rate 3.14 => 3.14
```

### `boolean` *(Flag only)*

A switch that is `true` when the flag is present and `false` otherwise; it also
accepts the negated form `--no-<name>`. Result type: `boolean`.

```ts
import { Flag } from "effect/unstable/cli"

const verbose = Flag.boolean("verbose") // --verbose => true ; --no-verbose => false ; absent => false
```

### `date`

Parses a string into a JavaScript `Date` (ISO dates and other forms the
underlying primitive accepts). `Flag.date(name)` / `Argument.date(name)`. Result
type: `Date`.

```ts
import { Flag } from "effect/unstable/cli"

const start = Flag.date("start-date") // --start-date 2023-12-25 => Date(2023-12-25)
```

### `choice`

Restricts input to a fixed set of strings; the result type is the literal union,
not `string`. `Flag.choice(name, choices)` / `Argument.choice(name, choices)`.

```ts
import { Flag } from "effect/unstable/cli"

const color = Flag.choice("color", ["red", "green", "blue"])
// --color red => "red" (typed as "red" | "green" | "blue")
// --color purple fails: Expected "red" | "green" | "blue", got "purple"
```

### `choiceWithValue`

Like `choice`, but maps each accepted string key to an arbitrary typed value.
`Flag.choiceWithValue(name, pairs)` / `Argument.choiceWithValue(name, pairs)`.
Result type: the union of the second tuple elements.

```ts
import { Flag } from "effect/unstable/cli"

const logLevel = Flag.choiceWithValue("log-level", [
  ["debug", 0],
  ["info", 1],
  ["error", 3]
] as const)
// --log-level info => 1 (typed as 0 | 1 | 3)
```

### `path`

A filesystem path resolved to an absolute path. The options control what kind of
entry is allowed and whether it must already exist. `Flag.path(name, options?)` /
`Argument.path(name, options?)`. Result type: `string`.

```ts
import { Flag } from "effect/unstable/cli"

// options: { pathType?: "file" | "directory" | "either"; mustExist?: boolean }
// Flag.path additionally accepts { typeName?: string } for help output.
const cfg = Flag.path("config-path", { pathType: "file", mustExist: true })
// --config-path ./app.json => "/abs/path/app.json" (fails if it does not exist or is a directory)
```

### `file`

Convenience for `path` with `pathType: "file"`. `Flag.file(name, options?)` /
`Argument.file(name, options?)`, where `options` is `{ mustExist?: boolean }`.
Result type: `string` (the resolved path).

```ts
import { Flag } from "effect/unstable/cli"

const input = Flag.file("input", { mustExist: true })
// --input ./data.json => "/abs/path/data.json" (fails if missing or not a file)
```

### `directory`

Convenience for `path` with `pathType: "directory"`. `Flag.directory(name,
options?)` / `Argument.directory(name, options?)`, with `{ mustExist?: boolean
}`. Result type: `string`.

```ts
import { Argument } from "effect/unstable/cli"

const workspace = Argument.directory("workspace", { mustExist: true })
// ./project => "/abs/path/project" (fails if it is not an existing directory)
```

### `redacted`

Wraps the parsed string in `Redacted.Redacted<string>` so it is masked in logs,
errors, and `String(...)`. Read the underlying value with `Redacted.value`.
`Flag.redacted(name)` / `Argument.redacted(name)`.

```ts
import { Redacted } from "effect"
import { Flag } from "effect/unstable/cli"

const password = Flag.redacted("password")
// --password abc123 => Redacted; String(value) => "<redacted>"; Redacted.value(value) => "abc123"
```

### `fileText`

Reads the referenced file and returns its full contents as a string (failing if
the path is missing or not a file). `Flag.fileText(name)` /
`Argument.fileText(name)`. Result type: `string`.

```ts
import { Flag } from "effect/unstable/cli"

const config = Flag.fileText("config-file")
// --config-file ./app.json => the file's text content as a string
```

### `fileParse`

Reads a file and parses it into structured data. The parser is chosen from
`options.format` or, when omitted, the file extension. Supported formats: `ini`,
`json`, `toml`, `yaml` / `yml`. `Flag.fileParse(name, options?)` /
`Argument.fileParse(name, options?)`. Result type: `unknown`.

```ts
import { Flag } from "effect/unstable/cli"

// Format inferred from the extension:
const config = Flag.fileParse("config")
// Force the JSON parser regardless of extension:
const json = Flag.fileParse("json-config", { format: "json" })
// --config ./app.toml => parsed object (typed as unknown)
```

### `fileSchema`

Reads and parses a file (like `fileParse`) and then decodes the result with a
[Schema](https://effect.plants.sh/schema/), giving a fully typed value. `Flag.fileSchema(name, schema,
options?)` / `Argument.fileSchema(name, schema, options?)`. Result type: the
schema's decoded type.

```ts
import { Schema } from "effect"
import { Flag } from "effect/unstable/cli"

const Config = Schema.Struct({ port: Schema.Number, host: Schema.String })
const config = Flag.fileSchema("config", Config, { format: "json" })
// --config ./app.json => { port: number; host: string } (decode failures become CLI errors)
```

### `keyValuePair` *(Flag only)*

Parses one or more `key=value` occurrences and merges them into a single record.
Requires at least one pair. `Flag.keyValuePair(name)`. Result type:
`Record<string, string>`.

```ts
import { Flag } from "effect/unstable/cli"

const env = Flag.keyValuePair("env")
// --env FOO=bar --env BAZ=qux => { FOO: "bar", BAZ: "qux" }
```

### `none`

A sentinel `Param` that always fails to parse, used as a placeholder in
conditional builders. `Flag.none` / `Argument.none`. Result type: `never`.

```ts
import { Flag } from "effect/unstable/cli"

const makeValueFlag = (include: boolean) =>
  include ? Flag.string("value") : Flag.none
// makeValueFlag(false) === Flag.none => true
```

---

# Combinator reference

These combinators are shared by `Flag` and `Argument` unless noted. They are
pipeable (`param.pipe(Flag.withDefault(...))`) and also accept the
data-first form (`Flag.withDefault(param, ...)`).

### `withDefault`

Supplies a fallback used only when the parameter is absent; provided values
parse normally. The fallback may be a plain value or an `Effect`. Widens the
result type to `A | B`.

```ts
import { Flag } from "effect/unstable/cli"

const host = Flag.string("host").pipe(Flag.withDefault("localhost"))
// absent => "localhost" ; --host db => "db"
```

### `optional`

Turns a missing parameter into `Option.none()` and a present one into
`Option.some(value)`. Result type: `Option.Option<A>`.

```ts
import { Option } from "effect"
import { Flag } from "effect/unstable/cli"

const port = Flag.integer("port").pipe(Flag.optional)
// absent => Option.none() ; --port 4000 => Option.some(4000)
```

### `withAlias` *(Flag only)*

Adds an alternative name for a flag, typically a single-character shortcut. Any
leading dashes are stripped. Chain it to add several aliases.

```ts
import { Flag } from "effect/unstable/cli"

const verbose = Flag.boolean("verbose").pipe(Flag.withAlias("v"))
// matches --verbose or -v
```

### `withDescription`

Attaches help text shown next to the flag/argument in generated `--help`.

```ts
import { Flag } from "effect/unstable/cli"

const port = Flag.integer("port").pipe(
  Flag.withDescription("The port number to listen on")
)
```

### `withMetavar`

Sets the placeholder shown for the value in usage text (for example `--timeout
SECONDS` instead of the default `--timeout integer`).

```ts
import { Flag } from "effect/unstable/cli"

const timeout = Flag.integer("timeout").pipe(Flag.withMetavar("SECONDS"))
// In help: --timeout SECONDS
```

### `withHidden` *(Flag only)*

Removes the flag from generated help and shell completions while keeping it fully
parseable — useful for experimental or internal toggles.

```ts
import { Flag } from "effect/unstable/cli"

const experimental = Flag.boolean("experimental-foo").pipe(Flag.withHidden)
// still parses --experimental-foo, but does not appear in --help
```

### `withSchema`

Validates and transforms the parsed value through a [Schema](https://effect.plants.sh/schema/) codec.
The schema's input type must match the parameter's current type; its output type
becomes the new result type.

```ts
import { Schema } from "effect"
import { Flag } from "effect/unstable/cli"

const email = Flag.string("email").pipe(
  Flag.withSchema(
    Schema.String.pipe(
      Schema.check(Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/))
    )
  )
)
// --email a@b.com => "a@b.com" ; --email nope fails with a validation error
```

### `map`

Transforms the parsed value with a pure function.

```ts
import { Flag } from "effect/unstable/cli"

const upper = Flag.string("name").pipe(Flag.map((n) => n.toUpperCase()))
// --name john => "JOHN"
```

### `mapEffect`

Transforms the parsed value with an `Effect`, allowing IO (filesystem, network)
or validation that fails with a `CliError`.

```ts
import { Effect, FileSystem } from "effect"
import { Flag } from "effect/unstable/cli"

const fileSize = Flag.file("input").pipe(
  Flag.mapEffect(Effect.fnUntraced(function*(path) {
    const fs = yield* FileSystem.FileSystem
    const stats = yield* Effect.orDie(fs.stat(path))
    return stats.size
  }))
)
// --input ./data.json => the file size in bytes
```

### `mapTryCatch`

Transforms with a function that may throw, converting thrown errors into a
validation message.

```ts
import { Flag } from "effect/unstable/cli"

const json = Flag.string("config").pipe(
  Flag.mapTryCatch(
    (s) => JSON.parse(s),
    (error) => `Invalid JSON: ${error}`
  )
)
// --config '{"a":1}' => { a: 1 } ; --config '{bad' fails with "Invalid JSON: ..."
```

### `filter`

Keeps the value only if a predicate holds, failing with a custom message
otherwise.

```ts
import { Flag } from "effect/unstable/cli"

const port = Flag.integer("port").pipe(
  Flag.filter(
    (p) => p >= 1 && p <= 65535,
    (p) => `Port ${p} is out of range (1-65535)`
  )
)
// --port 8080 => 8080 ; --port 0 fails with "Port 0 is out of range (1-65535)"
```

### `filterMap`

Transforms and filters in one step: return `Option.some(b)` to keep the mapped
value or `Option.none()` to reject with a custom message.

```ts
import { Option } from "effect"
import { Flag } from "effect/unstable/cli"

const positive = Flag.integer("count").pipe(
  Flag.filterMap(
    (n) => (n > 0 ? Option.some(n) : Option.none()),
    (n) => `Expected positive integer, got ${n}`
  )
)
// --count 5 => 5 ; --count -1 fails with "Expected positive integer, got -1"
```

### `orElse`

Falls back to a second parameter (built lazily) if the first fails to parse.
Result type: `A | B`.

```ts
import { Flag } from "effect/unstable/cli"

const value = Flag.integer("value").pipe(
  Flag.orElse(() => Flag.string("value"))
)
// --value 42 => 42 (number) ; --value abc => "abc" (string)
```

### `orElseResult`

Like `orElse`, but wraps the outcome in a `Result` so the handler can tell which
side succeeded. Result type: `Result.Result<A, B>`.

```ts
import { Result } from "effect"
import { Flag } from "effect/unstable/cli"

const source = Flag.file("source").pipe(
  Flag.orElseResult(() => Flag.string("source-url"))
)
// existing file => Result.succeed(path) ; otherwise => Result.fail(url string)
```

### `atLeast`

Makes the parameter repeatable and requires at least `min` occurrences,
collecting them into a `ReadonlyArray`. Works on both flags and arguments.

```ts
import { Flag } from "effect/unstable/cli"

const source = Flag.file("source").pipe(Flag.atLeast(2))
// --source a.ts --source b.ts => ["a.ts", "b.ts"] ; fewer than 2 fails
```

### `atMost`

Makes the parameter repeatable and allows at most `max` occurrences (0 to `max`).

```ts
import { Flag } from "effect/unstable/cli"

const warning = Flag.string("warning").pipe(Flag.atMost(3))
// up to 3 --warning flags => ReadonlyArray<string> ; a 4th fails
```

### `between`

Makes the parameter repeatable and bounds occurrences to the `min..max` range
(inclusive).

```ts
import { Flag } from "effect/unstable/cli"

const host = Flag.string("host").pipe(Flag.between(1, 3))
// --host h1 --host h2 => ["h1", "h2"] ; 0 or more than 3 fails
```

### `variadic` *(Argument only)*

Consumes multiple consecutive positional values into a `ReadonlyArray`. With no
options it accepts zero or more; pass `{ min, max }` to bound the count. Because
it greedily consumes the remaining positional input, place a variadic argument
last.

```ts
import { Argument } from "effect/unstable/cli"

const anyFiles = Argument.string("files").pipe(Argument.variadic())
const atLeastOne = Argument.string("files").pipe(Argument.variadic({ min: 1 }))
const upToFive = Argument.string("files").pipe(Argument.variadic({ min: 1, max: 5 }))
// a.txt b.txt c.txt => ["a.txt", "b.txt", "c.txt"]
```

### `withFallbackConfig`

Loads the value from [Configuration](https://effect.plants.sh/configuration/) when the parameter is
missing from the command line. CLI values always win; a missing config preserves
the original missing-parameter error, and a config parse failure becomes an
invalid-value error. Result type: `A | B`.

```ts
import { Config } from "effect"
import { Flag } from "effect/unstable/cli"

const verbose = Flag.boolean("verbose").pipe(
  Flag.withFallbackConfig(Config.boolean("VERBOSE"))
)
// --verbose wins ; otherwise reads the VERBOSE config/env value
```

### `withFallbackPrompt`

Shows an interactive [Prompt](https://effect.plants.sh/cli/prompts/) when a required parameter is missing,
so the CLI can ask the user for the value. Accepts a `Prompt` or an `Effect` that
builds one; invalid values do not prompt, and cancelling re-fails with the
original missing error. Result type: `A | B`.

```ts
import { Flag, Prompt } from "effect/unstable/cli"

const name = Flag.string("name").pipe(
  Flag.withFallbackPrompt(Prompt.text({ message: "Name" }))
)
// --name given => used directly ; absent => prompts "Name" interactively
```
**Note:** `withDefault`, `withFallbackConfig`, and `withFallbackPrompt` are three layered
ways to handle a missing required input: a static default, a configuration
source, or an interactive prompt. They can be combined, and only fire when the
value is genuinely absent (not when it is present but invalid).

---

# The Primitive layer

Underneath every `Flag` and `Argument` constructor is a `Primitive<A>` — the
low-level parser that turns a single raw string token into a typed value (with no
knowledge of names, aliases, or repetition). Most users never touch `Primitive`
directly; reach for it only when building a custom parameter shape.

The module exposes the parsers that back the constructors above:
`Primitive.string`, `Primitive.boolean`, `Primitive.integer`, `Primitive.float`,
`Primitive.date`, `Primitive.choice(pairs)`, `Primitive.path(pathType,
mustExist?)`, `Primitive.redacted`, `Primitive.fileText`,
`Primitive.fileParse(options?)`, `Primitive.fileSchema(schema, options?)`,
`Primitive.keyValuePair`, and the `Primitive.none` sentinel. Each exposes a
`parse(value: string)` effect, and `Primitive.getTypeName` returns the
help-friendly type name.

```ts
import { Effect } from "effect"
import { Primitive } from "effect/unstable/cli"

const program = Effect.gen(function*() {
  const port = yield* Primitive.integer.parse("8080") // => 8080
  const mode = yield* Primitive.choice([
    ["dev", "development"],
    ["prod", "production"]
  ] as const).parse("prod")                            // => "production"
  return { port, mode }
})

Primitive.getTypeName(Primitive.integer)      // => "integer"
Primitive.getTypeName(Primitive.keyValuePair) // => "key=value"
```

Next, see how to combine commands into a tree on the
[Subcommands](https://effect.plants.sh/cli/subcommands/) page.