Skip to content
// EFFECT DAYS 2026

CliOutput unstable

Formats and presents CLI help, errors, and version information.

The Formatter service turns help documents, errors, and version information into strings. The Presenter service decides where the built-in help, version, and invalid-invocation output of Command.runWith is written, and can be replaced to suppress or redirect it.

10 exports Since v4.0.0 Source

Constructors

Since v4.0.0

Creates a default formatter with configurable options.

Details

  • colors: force ANSI colors on or off. Defaults to terminal detection.
  • inheritedFlags: how subcommand help renders flags inherited from ancestor commands. "full" (default) lists them under INHERITED FLAGS, "summary" replaces the section with a pointer to the parent command's help, and "none" omits it.
  • globalFlags: the same choice for GLOBAL FLAGS, with the summary pointing at the help of the nearest command that declares global flags (the root command for built-in flags). Commands that declare global flags always list them in full.

Gotchas

Per-flag filtering in a custom formatter matches names, not flag identities. Renaming a flag without updating the filter silently stops it from matching.

Example (Hiding one inherited flag)

Filter only inheritedFlags to keep --profile visible on its declaring command while hiding it on descendants. Other inherited flags still render.

Signature

declare function defaultFormatter(options?: {
readonly colors?: boolean;
readonly globalFlags?: "none" | "full" | "summary";
readonly inheritedFlags?: "none" | "full" | "summary";
}): Formatter

Example Creating default formatters

import { CliError, CliOutput } from "effect/cli"
// Create a formatter without colors for tests or CI environments
const noColorFormatter = CliOutput.defaultFormatter({ colors: false })
// Create a formatter with colors forced on
const colorFormatter = CliOutput.defaultFormatter({ colors: true })
// Auto-detect colors based on terminal support (default behavior)
const autoFormatter = CliOutput.defaultFormatter()
// Point subcommand help at the declaring scope for global flags
const compactFormatter = CliOutput.defaultFormatter({ globalFlags: "summary" })
const error = new CliError.InvalidValue({
option: "foo",
value: "bar",
expected: "baz",
kind: "flag"
})
noColorFormatter.formatError(error).includes("Invalid value") // => true
colorFormatter.formatVersion("my-tool", "1.2.3").includes("my-tool") // => true
autoFormatter.formatVersion("my-tool", "1.2.3").includes("1.2.3") // => true
compactFormatter.formatVersion("my-tool", "1.2.3").includes("my-tool") // => true

Example Hiding one inherited flag

import { Context, Effect, Option } from "effect"
import { CliOutput } from "effect/cli"
const base = CliOutput.defaultFormatter({ colors: false })
const formatter: CliOutput.Formatter = {
...base,
formatHelpDoc: (doc) =>
base.formatHelpDoc({
...doc,
inheritedFlags: doc.inheritedFlags?.filter((flag) => flag.name !== "profile")
})
}
const program = Effect.gen(function*() {
const output = yield* CliOutput.Formatter
return output.formatHelpDoc({
description: "Deploy the application",
usage: "app deploy",
annotations: Context.empty(),
flags: [],
inheritedFlags: ["profile", "region"].map((name) => ({
name,
aliases: [],
type: "string",
description: Option.none(),
required: false
}))
})
}).pipe(Effect.provide(CliOutput.layer(formatter)))
const help = await Effect.runPromise(program)
help.includes("--profile") // => false
help.includes("--region") // => true

Defaults

Since v4.0.0

Default presenter that renders events with the current Formatter and writes them with Console.

Details

Help and version output goes to Console.log. Invalid invocations write help to Console.log and errors to Console.error. When no Formatter is installed, the default formatter colors output when TerminalCapabilities says it can.

See

Signature

declare const defaultPresenter: Presenter

Layers

layer

unstable
Since v4.0.0

Creates a Layer that provides a custom Formatter implementation.

Signature

declare function layer(formatter: Formatter): Layer<never>

Example Providing a custom formatter

import { Effect } from "effect"
import { CliOutput } from "effect/cli"
// Create a custom formatter without colors
const noColorFormatter = CliOutput.defaultFormatter({ colors: false })
const NoColorLayer = CliOutput.layer(noColorFormatter)
// Create a program that uses the custom formatter
const program = Effect.gen(function*() {
const formatter = yield* CliOutput.Formatter
return formatter.formatVersion("my-cli", "1.0.0")
}).pipe(
Effect.provide(NoColorLayer)
)
await Effect.runPromise(program) // => "my-cli v1.0.0"
Since v4.0.0

Creates a Layer that provides a custom Presenter implementation.

See

  • Presenter for the events a presenter receives

Signature

declare function layerPresenter(presenter: Presenter): Layer<never>

Measuring

Since v4.0.3

Measures the number of terminal cells a string occupies.

Details

Uses the same measurement as built-in help and table rendering. ANSI styling is stripped before measuring graphemes: zero-width graphemes count as 0, emoji presentation sequences and fullwidth characters as 2, and other graphemes as 1.

Signature

declare function displayWidth(text: string): number

Example Measuring terminal-cell widths

import { CliOutput } from "effect/cli"
CliOutput.displayWidth("file") // => 4
CliOutput.displayWidth("ใƒ•ใ‚กใ‚คใƒซ") // => 8
CliOutput.displayWidth("รฉ") // => 1
CliOutput.displayWidth("1๏ธโƒฃ") // => 2
CliOutput.displayWidth("\u001B[1mbold\u001B[0m") // => 4

Models

Formatter

interface unstable
Since v4.0.0

Defines the service interface for formatting CLI output including help, errors, and version info. This allows customization of output formatting, including color support.

Signature

interface Formatter {
readonly formatCliError: (error: CliError) => string;
readonly formatError: (error: CliError) => string;
readonly formatErrors: (errors: readonly Array<CliError>) => string;
readonly formatHelpDoc: (doc: HelpDoc) => string;
readonly formatVersion: (name: string, version: string) => string;
}

Example Customizing CLI output formatting

import { Effect } from "effect"
import { CliOutput } from "effect/cli"
// Create a custom formatter implementation
const customFormatter: CliOutput.Formatter = {
formatHelpDoc: (doc) => `Custom Help: ${doc.usage}`,
formatCliError: (error) => `Error: ${error.message}`,
formatError: (error) => `[ERROR] ${error.message}`,
formatVersion: (name, version) => `${name} (${version})`,
formatErrors: (errors) => errors.map((error) => error.message).join("\\n")
}
// Use the custom formatter in a program
const program = Effect.gen(function*() {
const formatter = yield* CliOutput.Formatter
return formatter.formatVersion("myapp", "1.0.0")
}).pipe(
Effect.provide(CliOutput.layer(customFormatter))
)
await Effect.runPromise(program) // => "myapp (1.0.0)"

Presenter

interface unstable
Since v4.0.0

Presents the built-in help, version, and invalid-invocation output of Command.runWith.

When to use

Use to suppress, redirect, or replace that output, for example to keep stdout clean for machine-readable results.

Details

Events carry the help document and errors rather than rendered text, so a presenter can still render them with the Formatter service. Presenting an event does not change the result of the run: implicit help and invalid invocations still fail with CliError.ShowHelp.

Gotchas

--completions scripts, wizard mode output, and handler failures rendered through CliError.userMessage do not go through the presenter.

See

Signature

interface Presenter {
readonly present: (event: Event) => Effect<void>;
}

Example Writing invalid-invocation output to stderr

import { Console, Effect } from "effect"
import { CliOutput } from "effect/cli"
const StderrPresenter = CliOutput.layerPresenter({
present: (event) =>
event._tag === "InvalidInvocation"
? Effect.gen(function*() {
const formatter = yield* CliOutput.Formatter
yield* Console.error(formatter.formatHelpDoc(event.helpDoc))
yield* Console.error(formatter.formatErrors(event.errors))
})
: CliOutput.defaultPresenter.present(event)
})

Other

Presenter

unstable
Since v4.0.0

Types used by the Presenter service.

Services

Formatter

unstable
Since v4.0.0

Service reference for the CLI output formatter. Provides a default implementation that can be overridden for custom formatting or testing.

Signature

declare const Formatter: Reference<Formatter>

Example Accessing the output formatter

import { Effect } from "effect"
import { CliOutput } from "effect/cli"
// Access the formatter service
const program = Effect.gen(function*() {
const formatter = yield* CliOutput.Formatter
// Format version information
return formatter.formatVersion("my-cli", "2.1.0")
})
// Run with default formatter
await Effect.runPromise(program.pipe(
Effect.provide(CliOutput.layer(CliOutput.defaultFormatter({ colors: false })))
)) // => "my-cli v2.1.0"

Presenter

unstable
Since v4.0.0

Service reference for the CLI output presenter. Defaults to defaultPresenter.

See

Signature

declare const Presenter: Reference<Presenter>