# Output & Errors

When you run a command with `Command.run`, two kinds of text reach the terminal
that you never wrote by hand: **help output** (printed for `--help`, `--version`,
or when parsing fails) and **error messages** (printed when a flag is missing, a
value is invalid, an unknown subcommand is used, and so on). Both are produced by
a single service, `CliOutput.Formatter`, which the runner reads from context.

This page covers how to **override that formatter** (most commonly to turn colors
off in CI), the `HelpDoc` data model the formatter consumes, and the structured
`CliError` types the parser raises — which you can pattern-match when a command is
embedded inside a larger program.

All three modules ```

## The common case: disable colors in CI

By default the formatter auto-detects color support from `process.stdout.isTTY`
and the `NO_COLOR` environment variable. In CI you often want to force plain text
so logs stay readable. Build a no-color formatter with
`CliOutput.defaultFormatter({ colors: false })` and provide it through
`CliOutput.layer`:

```ts
// app.ts
const greet = Command.make("greet", {
  name: Flag.string("name")
}, (config) => Console.log(`Hello, ${config.name}!`))

// A formatter that never emits ANSI color codes.
const NoColor = CliOutput.layer(
  CliOutput.defaultFormatter({ colors: false })
)

Command.run(greet, { version: "1.0.0" }).pipe(
  // Override the formatter the runner pulls from context.
  Effect.provide(NoColor),
  Effect.provide(NodeServices.layer),
  NodeRuntime.runMain
)
```

Now `greet --help`, `greet --version`, and any parse error render as plain text.
Because `CliOutput.Formatter` is a `Context.Reference` with a default value, you
do **not** have to provide it — the layer simply replaces the default.
**Just want plain output in one run?:** You do not need to write a custom formatter to strip colors — pass `{ colors:
  false }` to the built-in `defaultFormatter`. Set `{ colors: true }` to force
  colors on (e.g. when piping through a pager that understands ANSI).

A fully custom formatter is just an object implementing the `Formatter`
interface — useful for machine-readable output such as JSON:

```ts
const jsonFormatter: CliOutput.Formatter = {
  formatHelpDoc: (doc) => JSON.stringify(doc, null, 2),
  formatCliError: (error) => JSON.stringify({ error: error.message }),
  formatError: (error) => JSON.stringify({ type: "error", message: error.message }),
  formatVersion: (name, version) => JSON.stringify({ name, version }),
  formatErrors: (errors) => JSON.stringify(errors.map((e) => e.message))
}

const JsonLayer = CliOutput.layer(jsonFormatter)
```

## CliOutput reference

The `CliOutput` module is small: an interface, a default implementation, and a
layer to install it.

### CliOutput.Formatter

The service interface describing how help, usage, version, and errors render. It
is exposed as a `Context.Reference`, so it always has a default and is accessed
with `yield* CliOutput.Formatter`. The interface has five methods:

| Method | Signature | Renders |
| --- | --- | --- |
| `formatHelpDoc` | `(doc: HelpDoc) => string` | the full help screen for `--help` |
| `formatVersion` | `(name: string, version: string) => string` | the `--version` line |
| `formatError` | `(error: CliError) => string` | a single error block |
| `formatErrors` | `(errors: ReadonlyArray<CliError>) => string` | several errors, grouped by tag |
| `formatCliError` | `(error: CliError) => string` | the bare error message (no styling) |

```ts
const program = Effect.gen(function*() {
  // Read the formatter the runner would use from context.
  const formatter = yield* CliOutput.Formatter
  return formatter.formatVersion("my-cli", "2.1.0")
})

// With the default (no-color) formatter:
// => "my-cli v2.1.0"
```

### CliOutput.defaultFormatter

`CliOutput.defaultFormatter(options?: { colors?: boolean })` builds the standard
formatter. With no argument it auto-detects color support; pass `colors` to force
it on or off. This is the value behind the `Formatter` reference's default.

```ts
const plain = CliOutput.defaultFormatter({ colors: false })

plain.formatVersion("my-tool", "1.2.3")
// => "my-tool v1.2.3"

plain.formatError(
  new CliError.MissingOption({ option: "name" })
)
// => "\nERROR\n  Missing required flag: --name"

const colored = CliOutput.defaultFormatter({ colors: true })
colored.formatVersion("my-tool", "1.2.3")
// => "\x1b[1mmy-tool\x1b[0m \x1b[2mv\x1b[0m\x1b[1m1.2.3\x1b[0m"
```

The help renderer produces sections in a fixed order — `DESCRIPTION`, `USAGE`,
`ARGUMENTS`, `FLAGS`, `GLOBAL FLAGS`, `SUBCOMMANDS`, and `EXAMPLES` — measuring
visible width after stripping ANSI codes so colored and plain output stay aligned.

### CliOutput.layer

`CliOutput.layer(formatter: Formatter): Layer.Layer<never>` provides a formatter,
replacing the default. Provide it to `Command.run` to change how the whole CLI
renders.

```ts
const cmd = Command.make("hi", { name: Flag.string("name") }, () =>
  Effect.void)

const run = Command.run(cmd, { version: "1.0.0" }).pipe(
  Effect.provide(CliOutput.layer(CliOutput.defaultFormatter({ colors: false })))
)
// => `--help`/`--version`/errors now render without ANSI colors
```

## HelpDoc reference

`HelpDoc` is the **format-agnostic** description of a command's help. The runner
builds a `HelpDoc` from your command tree and hands it to
`formatter.formatHelpDoc`; the formatter decides layout, styling, and alignment.
You only touch these types when writing a custom formatter — building or
inspecting a doc rather than rendering it yourself.

### HelpDoc.HelpDoc

The top-level shape. `description`, `usage`, and `flags` are always present;
`args`, `globalFlags`, `subcommands`, and `examples` are optional and omitted when
empty. `annotations` is a `Context.Context<never>` carrying custom command
metadata.

```ts
const doc: HelpDoc.HelpDoc = {
  description: "Deploy your application",
  usage: "myapp deploy <target>",
  annotations: Context.empty(),
  flags: [],
  args: [
    {
      name: "target",
      type: "string",
      description: Option.some("Where to deploy"),
      required: true,
      variadic: false
    }
  ]
}
// => consumed by formatter.formatHelpDoc(doc)
```

### HelpDoc.FlagDoc

Describes one flag: its primary `name` (without the `--`), `aliases`, a `type`
label like `"string"` / `"boolean"` / `"integer"`, an `Option<string>`
`description`, and whether it is `required`.

```ts
const verbose: HelpDoc.FlagDoc = {
  name: "verbose",
  aliases: ["-v"],
  type: "boolean",
  description: Option.some("Enable verbose output"),
  required: false
}
// => renders as: --verbose, -v   Enable verbose output
```

### HelpDoc.ArgDoc

Describes one positional argument. Like `FlagDoc` but adds `variadic` (accepts
multiple values) and has no aliases.

```ts
const files: HelpDoc.ArgDoc = {
  name: "files",
  type: "file",
  description: Option.some("Files to process"),
  required: false,
  variadic: true
}
// => renders the name as "files..." in the ARGUMENTS table
```

### HelpDoc.SubcommandDoc

Describes a single subcommand row: `name`, an optional `alias`, an optional
`shortDescription` (preferred in listings when present), and a full `description`.

```ts
const deploy: HelpDoc.SubcommandDoc = {
  name: "deploy",
  alias: "d",
  shortDescription: "Deploy app",
  description: "Deploy the application to the cloud"
}
// => renders as: deploy, d   Deploy app
```

### HelpDoc.SubcommandGroupDoc

Groups subcommands under an optional heading. `group: undefined` is the default
ungrouped `SUBCOMMANDS` section; a string becomes a named heading. `commands` is a
non-empty array of `SubcommandDoc`.

```ts
const group: HelpDoc.SubcommandGroupDoc = {
  group: undefined, // ungrouped → "SUBCOMMANDS" heading
  commands: [
    { name: "deploy", alias: "d", shortDescription: undefined, description: "Deploy" }
  ]
}
```

### HelpDoc.ExampleDoc

A concrete invocation example: a `command` string and an optional `description`
rendered as a `# comment` above it in the `EXAMPLES` section.

```ts
const example: HelpDoc.ExampleDoc = {
  command: "myapp deploy production --force",
  description: "Force a production deploy"
}
// => renders as:
//    # Force a production deploy
//    myapp deploy production --force
```

## CliError reference

When parsing fails, the runner accumulates `CliError` values and prints them
through the formatter. Every variant is a `Schema.ErrorClass`, so it carries
**structured fields** (not just a string) and a `_tag` you can match on, and each
computes a human-readable `message` getter.

You rarely construct these yourself; the parser does. But because they are tagged
errors, you can catch them with [`Effect.catchTag`](https://effect.plants.sh/error-management/catching-errors/)
when you embed a command's effect inside a larger program rather than letting
`Command.run` format and exit.
**These are Schema tagged errors:** Each variant extends `Schema.ErrorClass`, the same machinery documented under
  [Tagged Errors](https://effect.plants.sh/error-management/tagged-errors/). That means you can match on
  `error._tag`, narrow by field, and recover with `Effect.catchTag` /
  `Effect.catchTags`.

### CliError.UnrecognizedOption

An unknown flag was passed. Fields: `option`, optional `command` (the command path
it appeared in), and `suggestions` (close matches for a "Did you mean?" hint).

```ts
new CliError.UnrecognizedOption({
  option: "--forc",
  command: ["deploy"],
  suggestions: ["--force"]
}).message
// => "Unrecognized flag: --forc in command deploy
//
//      Did you mean this?
//        --force"
```

### CliError.DuplicateOption

A flag name is defined on both a parent command and one of its subcommands. The
parent always claims the flag, so this is reported as a definition problem. Fields:
`option`, `parentCommand`, `childCommand`.

```ts
new CliError.DuplicateOption({
  option: "--verbose",
  parentCommand: "myapp",
  childCommand: "deploy"
}).message
// => 'Duplicate flag name "--verbose" in parent command "myapp" and subcommand
//     "deploy". Parent will always claim this flag (Mode A semantics)...'
```

### CliError.MissingOption

A required flag was not supplied. Field: `option` (the name, without `--`).

```ts
new CliError.MissingOption({ option: "api-key" }).message
// => "Missing required flag: --api-key"
```

### CliError.MissingArgument

A required positional argument was not supplied. Field: `argument`.

```ts
new CliError.MissingArgument({ argument: "target" }).message
// => "Missing required argument: target"
```

### CliError.InvalidValue

A flag or argument value failed validation (e.g. a non-integer passed to an
integer flag). Fields: `option`, `value`, `expected`, and `kind`
(`"flag"` | `"argument"`), which selects the message phrasing.

```ts
new CliError.InvalidValue({
  option: "port",
  value: "abc",
  expected: "integer between 1 and 65535",
  kind: "flag"
}).message
// => 'Invalid value for flag --port: "abc". Expected: integer between 1 and 65535'

new CliError.InvalidValue({
  option: "count",
  value: "abc",
  expected: "integer",
  kind: "argument"
}).message
// => 'Invalid value for argument <count>: "abc". Expected: integer'
```

### CliError.UnknownSubcommand

A subcommand name did not match any defined child. Fields: `subcommand`, optional
`parent` (the parent command path), and `suggestions`.

```ts
new CliError.UnknownSubcommand({
  subcommand: "deplyo",
  parent: ["myapp"],
  suggestions: ["deploy", "destroy"]
}).message
// => 'Unknown subcommand "deplyo" for "myapp"
//
//      Did you mean this?
//        deploy
//        destroy'
```

### CliError.UserError

Wraps a failure thrown from inside a command handler so it travels in the CLI
error channel. Field: `cause` (the original defect/error). Unlike the parse errors
above, it has no custom `message` getter — inspect `cause` directly.

```ts
const err = new CliError.UserError({
  cause: new Error("Database connection failed")
})
err._tag // => "UserError"
err.cause // => Error: Database connection failed
```

### CliError.ShowHelp

A **control-flow** signal, not a parse failure. It is raised for explicit `--help`
requests and for parse failures that should be shown alongside help. Fields:
`commandPath` (which command's help to render) and `errors` (a possibly-empty list
of the non-help errors below). It exits with code `0` when `errors` is empty and
`1` otherwise.

```ts
const help = new CliError.ShowHelp({
  commandPath: ["myapp", "deploy"],
  errors: [new CliError.MissingOption({ option: "target" })]
})
help.message // => "Help requested"
help.errors.length // => 1  (so it exits with code 1)
```

### CliError.NonShowHelpErrors

The `Schema.Union` of every concrete error **except** `ShowHelp`. `ShowHelp.errors`
is typed as an array of these, so a help screen can carry the underlying failures
without nesting another help-control value. The corresponding type alias is
`CliError.NonShowHelpErrors`.

```ts
// Decode an unknown value into one of the concrete (non-ShowHelp) errors.
CliError.NonShowHelpErrors
// => Schema.Union of UnrecognizedOption | DuplicateOption | MissingOption
//    | MissingArgument | InvalidValue | UnknownSubcommand | UserError
```

### CliError.CliError

The union of every variant: `UnrecognizedOption | DuplicateOption | MissingOption
| MissingArgument | InvalidValue | UnknownSubcommand | ShowHelp | UserError`. This
is the error type that appears in `Command.run`'s signature.

### CliError.isCliError

A type guard, `(u: unknown) => u is CliError`, that checks for the internal CLI
error brand. Handy at the boundary of a larger program where you have caught an
`unknown` and want to distinguish CLI failures from other defects.

```ts
const err = new CliError.MissingOption({ option: "name" })

CliError.isCliError(err) // => true
CliError.isCliError(new Error("boom")) // => false
```

### Catching CLI errors in a larger program

`Command.run` formats these errors and sets the process exit code for you. But if
you embed a command's effect inside a bigger pipeline, the `CliError` surfaces in
the typed error channel and you can recover from specific variants with
`Effect.catchTag`:

```ts
const cmd = Command.make("deploy", { target: Flag.string("target") }, () =>
  Console.log("deploying..."))

const program = Command.run(cmd, { version: "1.0.0" }).pipe(
  // Recover from a missing required flag instead of letting it exit.
  Effect.catchTag("MissingOption", (error) =>
    Console.error(`You forgot --${error.option}`)
  )
)
```

See [Catching Errors](https://effect.plants.sh/error-management/catching-errors/) for the full set of
recovery combinators (`catch`, `catchTag`, `catchTags`, `catchReason`).