# Interactive Prompts

The `Prompt` module (`effect/unstable/cli`) builds interactive terminal
questions: text fields, password masks, confirmations, single- and multi-select
menus, autocomplete, numbers, dates, and file pickers. A prompt renders frames,
reads keyboard input, validates the response, and produces a value.

The key idea is that **a `Prompt<A>` _is_ an `Effect`**:

```ts
interface Prompt<Output>
  extends Effect.Effect<Output, Terminal.QuitError, Prompt.Environment> {}
```

So you run a prompt by `yield*`-ing it inside any handler, exactly like any
other effect. It may fail with `Terminal.QuitError` (the user pressed Ctrl-C or
quit), and it requires the prompt `Environment` — `Terminal`, `FileSystem`, and
`Path` — which `NodeServices.layer` provides.

## Common case: prompting inside a command

The typical use is a [command](https://effect.plants.sh/cli/) handler that asks for input it does not
already have on the command line, then uses the result:

```ts
// init.ts
import { NodeRuntime, NodeServices } from "@effect/platform-node"
import { Console, Effect } from "effect"
import { Command, Prompt } from "effect/unstable/cli"

const init = Command.make("init", {}, () =>
  Effect.gen(function*() {
    // A prompt is just an Effect — yield* it to get its value.
    const name = yield* Prompt.text({
      message: "Project name:"
    })

    const private_ = yield* Prompt.confirm({
      message: "Make the repository private?",
      initial: false
    })

    yield* Console.log(
      `Creating ${private_ ? "private" : "public"} project "${name}"…`
    )
  })
)

init.pipe(
  Command.run({ version: "1.0.0" }),
  // NodeServices.layer provides Terminal + FileSystem + Path, which every
  // prompt needs.
  Effect.provide(NodeServices.layer),
  NodeRuntime.runMain
)
```
**Note:** Prompts read the terminal, so they only run where a `Terminal` is available.
The `NodeServices.layer` you already provide to `Command.run` includes it. See
[Platform / Terminal](https://effect.plants.sh/platform/terminal/) for the underlying service and what
`Terminal.QuitError` means.

### Tying a prompt to a missing flag

Instead of writing the prompt by hand in the handler, you can attach it to a
flag or argument with `withFallbackPrompt`: if the value is missing on the
command line, the prompt runs to fill it in. This keeps the handler unaware of
where the value came from. See [Arguments & flags](https://effect.plants.sh/cli/arguments-and-flags/).

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

// `--name` if provided, otherwise prompt for it.
const name = Flag.string("name").pipe(
  Flag.withFallbackPrompt(Prompt.text({ message: "Project name:" }))
)
```

The fallback only triggers for *missing* required input — an invalid value still
fails normally, and cancelling the prompt re-fails with the original missing
error.

### Running a prompt outside a command

Any prompt is an effect, so you can also run it directly (`Prompt.run` is the
explicit form, but `yield*` works because prompts are `Effect`s):

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

const program = Effect.gen(function*() {
  const answer = yield* Prompt.text({ message: "Your name:" })
  // ...use answer
})

// equivalent explicit execution
const program2 = Prompt.run(Prompt.text({ message: "Your name:" }))
```

## Prompt reference

Every constructor below returns a `Prompt<A>`. Run them with `yield*`.

### Prompt.text

A single-line text field that echoes input. `TextOptions`: `message`,
`default?`, and an effectful `validate?` that returns the cleaned value or fails
with a string error message shown to the user.

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

const slug = Prompt.text({
  message: "Package slug:",
  default: "my-app",
  validate: (value) =>
    /^[a-z0-9-]+$/.test(value)
      ? Effect.succeed(value)
      : Effect.fail("Use only lowercase letters, digits, and dashes")
})
// => Prompt<string>
```

### Prompt.password

A text field that masks typed characters and returns the value wrapped in
`Redacted`, so the secret is not accidentally logged. Same `TextOptions` as
`Prompt.text`.

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

const secret = Prompt.password({ message: "API token:" })
// => Prompt<Redacted>

// unwrap only at the boundary that needs the secret:
// const raw = Redacted.value(yield* secret)
```

### Prompt.hidden

Like `Prompt.password` but shows nothing at all as you type (no mask
characters). Also returns a `Redacted`.

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

const passphrase = Prompt.hidden({ message: "Passphrase:" })
// => Prompt<Redacted>
```

### Prompt.confirm

A yes/no question submitted with a single keypress. `ConfirmOptions`: `message`,
`initial?` (defaults to `false`), `label?` (the `{ confirm, deny }` text shown
after answering — defaults `"yes"` / `"no"`), and `placeholder?`
(`{ defaultConfirm, defaultDeny }`, defaults `"(Y/n)"` / `"(y/N)"`).

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

const overwrite = Prompt.confirm({
  message: "File exists. Overwrite?",
  initial: false,
  label: { confirm: "overwrite", deny: "keep" }
})
// => Prompt<boolean>
```

### Prompt.toggle

An interactive on/off switch you flip with the arrow keys, then submit with
Enter — unlike `confirm`, which submits immediately. `ToggleOptions`: `message`,
`initial?` (`false`), `active?` (`"on"`), `inactive?` (`"off"`).

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

const analytics = Prompt.toggle({
  message: "Telemetry:",
  initial: true,
  active: "enabled",
  inactive: "disabled"
})
// => Prompt<boolean>
```

### Prompt.select

Pick one value from a list. `SelectOptions<A>`: `message`, `choices`
(`SelectChoice<A>[]`), and `maxPerPage?` (defaults to `10`). Each
`SelectChoice<A>` has `{ title, value, description?, disabled?, selected? }`;
at most one choice may set `selected: true` as the default highlight.

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

const license = Prompt.select({
  message: "Choose a license:",
  choices: [
    { title: "MIT", value: "mit", description: "Permissive", selected: true },
    { title: "Apache 2.0", value: "apache-2.0" },
    { title: "GPL 3.0", value: "gpl-3.0" },
    { title: "Proprietary", value: "none", disabled: true }
  ]
})
// => Prompt<"mit" | "apache-2.0" | "gpl-3.0" | "none">
```

### Prompt.multiSelect

Pick several values; returns them as an array. Takes `SelectOptions<A>` plus
`MultiSelectOptions`: `selectAll?` / `selectNone?` / `inverseSelection?` (bulk
command labels) and `min?` / `max?` selection counts. Mark defaults with
`selected: true` on individual choices.

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

const features = Prompt.multiSelect({
  message: "Enable features:",
  min: 1,
  choices: [
    { title: "TypeScript", value: "ts", selected: true },
    { title: "ESLint", value: "eslint" },
    { title: "Prettier", value: "prettier" }
  ]
})
// => Prompt<Array<"ts" | "eslint" | "prettier">>
```

### Prompt.autoComplete

A select menu the user can filter by typing. Extends `SelectOptions<A>` with
`filterLabel?` (`"filter"`), `filterPlaceholder?` (`"type to filter"`), and
`emptyMessage?` (`"No matches"`).

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

const language = Prompt.autoComplete({
  message: "Primary language:",
  filterPlaceholder: "start typing…",
  choices: [
    { title: "TypeScript", value: "ts" },
    { title: "Rust", value: "rs" },
    { title: "Kotlin", value: "kt" }
  ]
})
// => Prompt<"ts" | "rs" | "kt">
```

### Prompt.integer

Whole-number entry with arrow-key stepping. `IntegerOptions`: `message`,
`default?` (`0`), `min?` / `max?` bounds, `incrementBy?` / `decrementBy?` (arrow
step, default `1`), and an effectful `validate?`. Values outside `min`/`max` are
rejected automatically.

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

const port = Prompt.integer({
  message: "Port:",
  default: 3000,
  min: 1,
  max: 65535
})
// => Prompt<number>
```

### Prompt.float

Decimal entry. Extends `IntegerOptions` with `precision?` (number of decimal
places, default `2`).

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

const ratio = Prompt.float({
  message: "Sample rate (0–1):",
  default: 0.5,
  min: 0,
  max: 1,
  precision: 3
})
// => Prompt<number>
```

### Prompt.date

Edit a formatted date/time field part by part. `DateOptions`: `message`,
`initial?` (defaults to the current `Date`), `dateMask?` (defaults to
`"YYYY-MM-DD HH:mm:ss"`), effectful `validate?`, and `locales?` for custom month
and weekday names.

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

const when = Prompt.date({
  message: "Release date:",
  dateMask: "YYYY-MM-DD"
})
// => Prompt<Date>
```
**Caution:** A supplied `initial` `Date` is edited in place, and date edits use JavaScript
`Date` setters, so out-of-range typed values may normalize before `validate`
runs. Make sure `dateMask` contains at least one editable token.

### Prompt.list

A text field whose submitted value is split into an array of strings. Extends
`TextOptions` with `delimiter?` (defaults to `","`).

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

const tags = Prompt.list({
  message: "Tags (comma-separated):",
  delimiter: ","
})
// => Prompt<Array<string>>  e.g. "a, b" => ["a", " b"]
```

### Prompt.file

A file-system browser that returns the selected path. `FileOptions` (all
optional): `type?` (`"file"` | `"directory"` | `"either"`, default `"file"`),
`message?` (`"Choose a file"`), `startingPath?` (default: cwd), `default?`,
`maxPerPage?` (`10`), and `filter?` — a predicate or effect that keeps an entry
when it returns `true`.

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

const config = Prompt.file({
  message: "Pick a config file:",
  type: "file",
  filter: (file) => file.endsWith(".json")
})
// => Prompt<string>  e.g. "/home/me/project/tsconfig.json"
```

## Combinators

Because prompts compose, you can transform results, chain dependent prompts, and
collect several at once.

### Prompt.map

Transform the output value of a prompt.

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

const upper = Prompt.text({ message: "Name:" }).pipe(
  Prompt.map((name) => name.toUpperCase())
)
// => Prompt<string>
```

### Prompt.flatMap

Use one prompt's output to build the next one — a wizard where later questions
depend on earlier answers.

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

const driver = Prompt.select({
  message: "Database:",
  choices: [
    { title: "PostgreSQL", value: "pg" as const },
    { title: "SQLite", value: "sqlite" as const }
  ]
}).pipe(
  Prompt.flatMap((db) =>
    db === "pg"
      ? Prompt.text({ message: "Connection URL:" })
      : Prompt.file({ message: "Database file:", type: "file" })
  )
)
// => Prompt<string>
```

### Prompt.all

Run several prompts in sequence and collect their results, preserving the input
shape — a tuple or a struct.

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

// As a struct -> Prompt<{ username: string; password: Redacted }>
const credentials = Prompt.all({
  username: Prompt.text({ message: "Username:" }),
  password: Prompt.password({ message: "Password:" })
})

// As a tuple -> Prompt<[string, boolean]>
const pair = Prompt.all([
  Prompt.text({ message: "Name:" }),
  Prompt.confirm({ message: "Continue?" })
])
```

### Prompt.succeed

A constant prompt that immediately yields a value without touching the terminal.
Useful as a branch in `flatMap` or `Prompt.all` when no input is needed.

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

const noop = Prompt.succeed(42)
// => Prompt<number>, resolves to 42 with no rendering
```

### Prompt.run

Execute a prompt, producing `Effect<Output, Terminal.QuitError, Environment>`.
Equivalent to `yield*`-ing the prompt; use it when you want the effect
explicitly (e.g. to pass it somewhere expecting an `Effect`).

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

const program = Prompt.run(Prompt.text({ message: "Name:" }))
// => Effect<string, Terminal.QuitError, Prompt.Environment>
```

### Prompt.isPrompt

Type guard that returns `true` if a value is a `Prompt`.

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

Prompt.isPrompt(Prompt.text({ message: "x" })) // => true
Prompt.isPrompt(42) // => false
```

### Prompt.custom

Build a bespoke prompt from an initial state (a value or an effect computing it)
and a set of `Handlers<State, Output>`. The prompt runs as a render loop:
`render(state, action)` returns ANSI output for the current frame, the terminal
reads a `Terminal.UserInput`, `process(input, state)` returns the next
`Prompt.Action`, and `clear(state, action)` returns ANSI output that erases the
previous frame.

`Action<State, Output>` is a `Data.TaggedEnum` with three cases: `Beep` (reject
input with a bell), `NextFrame({ state })` (advance to a new state), and
`Submit({ value })` (finish with an output value).

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

// Sketch: most fields render/clear ANSI strings and inspect input.key.name.
declare const handlers: Prompt.Handlers<number, number>

const counter: Prompt.Prompt<number> = Prompt.custom(0, handlers)
// => Prompt<number>
```
**Tip:** Reach for `Prompt.custom` only when none of the built-in constructors fit; the
constructors above are themselves built on top of it. For most CLIs, composing
the built-ins with `map`, `flatMap`, and `all` is all you need.

## Related
**Arguments & flags**
Attach a prompt to a flag or argument with `withFallbackPrompt` so it only
  runs when the value is missing.
  [Read more](https://effect.plants.sh/cli/arguments-and-flags/)