Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/effect-TS/effect-smol/llms.txt

Use this file to discover all available pages before exploring further.

The CLI module is marked as unstable, meaning its APIs may change in minor version releases. Use caution when upgrading Effect versions.

Overview

The effect/unstable/cli module provides powerful tools for building type-safe command-line applications with Effect. It offers composable primitives for defining commands, flags, arguments, and prompts with built-in parsing, validation, and help generation.

Installation

npm install effect

Key Modules

Command

The core building block for CLI applications. Commands define the structure, configuration, and behavior of CLI operations.
import { Console } from "effect"
import { Command, Flag, Argument } from "effect/unstable/cli"

// Simple command
const version = Command.make("version")

// Command with flags and arguments
const deploy = Command.make("deploy", {
  env: Flag.string("env"),
  force: Flag.boolean("force"),
  files: Argument.string("files").pipe(Argument.variadic())
})

// Command with handler
const greet = Command.make(
  "greet",
  {
    name: Flag.string("name")
  },
  (config) => Console.log(`Hello, ${config.name}!`)
)
Key Functions:
  • Command.make(name, config?, handler?) - Create a new command
  • Command.withSubcommands(parent, subcommands) - Add subcommands for hierarchical CLIs
  • Command.run(command, args) - Execute a command with arguments
  • Command.provide(command, layer) - Provide services to command handler

Flag

Define command-line flags (options) with various types and behaviors.
import { Flag } from "effect/unstable/cli"

// Boolean flag: --verbose or -v
const verbose = Flag.boolean("verbose").pipe(
  Flag.withAlias("v")
)

// String flag with default: --config=path/to/file
const config = Flag.string("config").pipe(
  Flag.withDefault("./config.json")
)

// Number flag: --port=3000
const port = Flag.number("port")

// Optional flag
const optional = Flag.string("optional").pipe(
  Flag.optional
)

// Flag with validation
const validated = Flag.number("threads").pipe(
  Flag.withDefault(4),
  Flag.mapEffect((n) => 
    n > 0 && n <= 16
      ? Effect.succeed(n)
      : Effect.fail("Threads must be between 1 and 16")
  )
)
Flag Types:
  • Flag.boolean(name) - Boolean flag
  • Flag.string(name) - String flag
  • Flag.number(name) - Numeric flag
  • Flag.integer(name) - Integer flag
  • Flag.date(name) - Date flag
  • Flag.choice(name, choices) - Enum flag
Flag Modifiers:
  • Flag.withAlias(alias) - Add short alias (e.g., -v for —verbose)
  • Flag.withDefault(value) - Provide default value
  • Flag.optional - Make flag optional
  • Flag.repeated - Allow multiple occurrences
  • Flag.withDescription(desc) - Add help description

Argument

Define positional command-line arguments.
import { Argument } from "effect/unstable/cli"

// Single string argument
const filename = Argument.string("filename")

// Multiple arguments (variadic)
const files = Argument.string("files").pipe(
  Argument.variadic()
)

// Optional argument
const output = Argument.string("output").pipe(
  Argument.optional
)

// Argument with validation
const port = Argument.integer("port").pipe(
  Argument.mapEffect((p) =>
    p >= 1024 && p <= 65535
      ? Effect.succeed(p)
      : Effect.fail("Port must be between 1024 and 65535")
  )
)
Argument Types:
  • Argument.string(name) - String argument
  • Argument.number(name) - Numeric argument
  • Argument.integer(name) - Integer argument
  • Argument.boolean(name) - Boolean argument
  • Argument.date(name) - Date argument
Argument Modifiers:
  • Argument.variadic() - Accept multiple values
  • Argument.optional - Make argument optional
  • Argument.withDefault(value) - Provide default value
  • Argument.withDescription(desc) - Add help description

GlobalFlag

Define flags that apply to all commands in a CLI application.
import { Command, GlobalFlag } from "effect/unstable/cli"

// Global verbose flag available to all commands
const verbose = GlobalFlag.boolean("verbose").pipe(
  GlobalFlag.withAlias("v"),
  GlobalFlag.withDescription("Enable verbose output")
)

const cli = Command.make("mycli")
  .pipe(Command.withGlobalFlags({ verbose }))

Prompt

Interactive user prompts for CLI applications.
import { Effect } from "effect"
import { Prompt } from "effect/unstable/cli"

// Text input prompt
const getName = Effect.gen(function*() {
  const name = yield* Prompt.text({
    message: "What is your name?",
    default: "User"
  })
  return name
})

// Password prompt (hidden input)
const getPassword = Prompt.password({
  message: "Enter password:",
  validate: (pwd) => pwd.length >= 8 || "Password must be at least 8 characters"
})

// Confirmation prompt
const confirm = Prompt.confirm({
  message: "Are you sure?",
  default: false
})

// Select from list
const selectOption = Prompt.select({
  message: "Choose an option:",
  choices: [
    { title: "Option 1", value: "opt1" },
    { title: "Option 2", value: "opt2" },
    { title: "Option 3", value: "opt3" }
  ]
})

// Multi-select
const multiSelect = Prompt.multiSelect({
  message: "Select features:",
  choices: [
    { title: "Feature A", value: "a" },
    { title: "Feature B", value: "b" },
    { title: "Feature C", value: "c" }
  ]
})

HelpDoc

Automatically generate help documentation for commands.
import { Command, HelpDoc } from "effect/unstable/cli"

// Help is automatically generated from command structure
const command = Command.make(
  "deploy",
  {
    env: Flag.string("env").pipe(
      Flag.withDescription("Deployment environment")
    ),
    force: Flag.boolean("force").pipe(
      Flag.withDescription("Force deployment")
    )
  }
).pipe(
  Command.withDescription("Deploy the application")
)

// Users can run: mycli deploy --help
// to see generated documentation

CliError

Type-safe error handling for CLI operations.
import { Effect } from "effect"
import { CliError } from "effect/unstable/cli"

// Handle CLI parsing errors
const handleError = (error: CliError.CliError) => {
  switch (error._tag) {
    case "ValidationError":
      return Effect.logError(`Validation failed: ${error.message}`)
    case "MissingValue":
      return Effect.logError(`Missing required value: ${error.name}`)
    case "InvalidArgument":
      return Effect.logError(`Invalid argument: ${error.message}`)
    default:
      return Effect.logError(`CLI error: ${error.message}`)
  }
}

Complete Example

Here’s a complete CLI application with subcommands:
import { Console, Effect } from "effect"
import { Command, Flag, Argument } from "effect/unstable/cli"

// Define subcommands
const init = Command.make(
  "init",
  {
    name: Argument.string("name"),
    typescript: Flag.boolean("typescript").pipe(
      Flag.withAlias("ts"),
      Flag.withDefault(false)
    )
  },
  ({ name, typescript }) =>
    Console.log(`Initializing project "${name}" with TypeScript: ${typescript}`)
).pipe(
  Command.withDescription("Initialize a new project")
)

const build = Command.make(
  "build",
  {
    watch: Flag.boolean("watch").pipe(
      Flag.withAlias("w"),
      Flag.withDefault(false)
    ),
    outDir: Flag.string("outDir").pipe(
      Flag.withDefault("./dist")
    )
  },
  ({ watch, outDir }) =>
    Console.log(`Building to ${outDir}${watch ? " (watch mode)" : ""}`)
).pipe(
  Command.withDescription("Build the project")
)

const test = Command.make(
  "test",
  {
    coverage: Flag.boolean("coverage").pipe(
      Flag.withDefault(false)
    ),
    files: Argument.string("files").pipe(
      Argument.variadic(),
      Argument.optional
    )
  },
  ({ coverage, files }) =>
    Console.log(`Running tests${coverage ? " with coverage" : ""}${files ? ` for: ${files.join(", ")}` : ""}`)
).pipe(
  Command.withDescription("Run tests")
)

// Create main CLI with subcommands
const cli = Command.make("mycli").pipe(
  Command.withSubcommands([init, build, test]),
  Command.withDescription("My CLI application")
)

// Run the CLI
const program = Command.run(cli, process.argv.slice(2))

// Execute
Effect.runPromise(program)

Command-Line Completions

The CLI module supports shell completions for bash, zsh, and fish:
import { Command } from "effect/unstable/cli"

// Add completion command to your CLI
const cli = Command.make("mycli")
  .pipe(
    Command.withSubcommands([init, build, test]),
    Command.withCompletions() // Adds 'completions' subcommand
  )

// Users can then run:
// mycli completions bash > /etc/bash_completion.d/mycli
// mycli completions zsh > ~/.zsh/completions/_mycli
// mycli completions fish > ~/.config/fish/completions/mycli.fish

Best Practices

  1. Type Safety - Leverage TypeScript’s type inference for command configs
  2. Descriptions - Always add descriptions to commands, flags, and arguments
  3. Validation - Use mapEffect to validate inputs with Effect
  4. Defaults - Provide sensible defaults for optional flags
  5. Subcommands - Organize complex CLIs with subcommands
  6. Error Handling - Handle CLI errors gracefully with proper messages
  7. Prompts - Use interactive prompts for better UX when appropriate
  • AI - AI and LLM integration
  • Process - Child process management
  • Cluster - Distributed computing