Skip to content

CLI

The effect/unstable/cli modules let you build command-line applications the same way you build the rest of an Effect program: declaratively, with typed inputs and effectful handlers. You describe a command’s positional arguments and named flags as Param values, attach a handler written with Effect.fn, and Effect takes care of parsing process.argv, validating inputs, generating help text, and reporting friendly errors.

Because handlers are ordinary Effects, everything you already know carries over: you can access services, acquire scoped resources, fail with typed errors, and provide Layers — all from inside a command.

app.ts
import { NodeRuntime, NodeServices } from "@effect/platform-node"
import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"
// A command pairs a typed config (its arguments and flags) with a handler.
const greet = Command.make(
"greet",
{
// A required positional argument: `greet Alice`
name: Argument.string("name").pipe(
Argument.withDescription("Who to greet")
),
// A flag with a default, so `--count` is optional: `greet Alice --count 3`
count: Flag.integer("count").pipe(
Flag.withAlias("c"),
Flag.withDescription("How many times to greet"),
Flag.withDefault(1)
)
},
// The handler receives the parsed, fully-typed config. Writing it with
// `Effect.fn` means it is a normal Effect — it can use services and fail.
Effect.fn(function*({ name, count }) {
for (let i = 0; i < count; i++) {
yield* Console.log(`Hello, ${name}!`)
}
})
)
// `Command.run` turns a command into an Effect that reads argv, parses it, and
// dispatches to the handler. `--help` and `--version` are wired up for free.
greet.pipe(
Command.run({ version: "1.0.0" }),
// Provide the services for your target platform (file system, stdin/stdout…).
Effect.provide(NodeServices.layer),
NodeRuntime.runMain
)

Run it with node app.ts Alice --count 2 and it prints Hello, Alice! twice. Run node app.ts --help and Effect prints generated usage and option docs.

  • Flag — named options like --count 3 or -c 3. Flags are optional by default once you give them a default; otherwise they are required (boolean flags are an exception: a bare boolean flag defaults to false).
  • Argument — positional operands like the Alice above. Order matters and they are matched left to right.
  • Command — bundles a config of flags and arguments with a handler, and can compose child commands into a tree of subcommands.
  • Prompt — interactive input read from the terminal (text, password, confirm, select, multi-select, and more) for when a value was not supplied on the command line.
  • GlobalFlag — the built-in --help, --version, and --completions flags, plus a way to define your own global flags and settings (like --log-level) that apply across the whole command tree.
  • Completions — generates shell completion scripts (bash, zsh, fish) from your command tree, surfaced through the --completions global flag.
  • CliOutput — the formatter that renders help text and error messages; swap it out to customize coloring and layout.

Both Flag and Argument are built on the shared Param type, so the same combinators (withDefault, optional, map, withSchema, …) work on either.

A command moves through three steps, each backed by a Command API:

  • Command.make pairs a config (its flags and arguments) with a handler. Write the handler with Effect.fn so it stays a normal Effect — it can use services, acquire resources, and fail with typed errors. You can also attach the handler later with Command.withHandler.
  • Command.run turns the command into an Effect. It reads the process argument vector from the Stdio service, lexes and parses it, applies global flags like --help/--version, and dispatches to the matching handler.
  • Command.runWith does the same but takes an explicit ReadonlyArray<string> of arguments instead of reading Stdio. This is what you use in tests to exercise a command with specific argv without a real terminal.
import { Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"
const greet = Command.make("greet", {
name: Flag.string("name")
}, ({ name }) => Effect.log(`Hello, ${name}!`))
// In a test: feed argv directly, no Stdio needed.
const run = Command.runWith(greet, { version: "1.0.0" })
// `program` is an Effect; running it logs "Hello, Alice!"
const program = run(["--name", "Alice"])

Arguments & flags

Declare typed positional arguments and flags, give them defaults, make them optional or variadic, and validate their values. Read more

Subcommands

Compose child commands into a single executable, share parent flags, and read parent input from inside a subcommand. Read more

Prompts

Ask for input interactively — text, password, confirm, select, multi-select, and more — when a value was not passed on the command line. Read more

Global flags & completions

The built-in --help, --version, and --completions flags, custom global flags and settings, and generating shell completion scripts. Read more

Output & errors

How help text and parse errors are formatted, and how to customize the output formatter. Read more

Command.run produces a plain Effect. To execute it you provide the platform services it needs (Stdio, FileSystem, …) and hand it to a runtime. On Node.js that is NodeServices.layer plus NodeRuntime.runMain, as shown above. See Platform for other runtimes and what each layer provides.