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.

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

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

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.