# Subcommands & Command Combinators

Real CLIs are usually a tree: `git commit`, `git push`, `docker run`, `npm
install`. In Effect you build that tree by defining each child as its own
`Command`, then attaching them to a parent with `Command.withSubcommands`. The
parent acts as a namespace and a place to declare flags shared by all children.

The example below builds a `tasks` tool with `create` and `list` subcommands.
The parent declares **shared flags** (`--workspace`, `--verbose`) that every
subcommand can read, and each subcommand reads them by `yield*`-ing the parent
command inside its handler.

```ts
// tasks.ts
import { NodeRuntime, NodeServices } from "@effect/platform-node"
import { Console, Effect } from "effect"
import { Argument, Command, Flag } from "effect/unstable/cli"

// Flags can be defined once and reused across commands.
const workspace = Flag.string("workspace").pipe(
  Flag.withAlias("w"),
  Flag.withDescription("Workspace to operate on"),
  Flag.withDefault("personal")
)

// The root command. `withSharedFlags` makes these flags available to the root
// handler *and* to every descendant — and accepts them before or after a
// subcommand name (npm-style: `tasks --workspace x list` or `tasks list -w x`).
const tasks = Command.make("tasks").pipe(
  Command.withSharedFlags({
    workspace,
    verbose: Flag.boolean("verbose").pipe(
      Flag.withAlias("v"),
      Flag.withDescription("Print diagnostic output")
    )
  }),
  Command.withDescription("Track and manage tasks")
)

const create = Command.make(
  "create",
  {
    title: Argument.string("title").pipe(Argument.withDescription("Task title")),
    priority: Flag.choice("priority", ["low", "normal", "high"]).pipe(
      Flag.withDescription("Priority for the new task"),
      Flag.withDefault("normal")
    )
  },
  Effect.fn(function*({ title, priority }) {
    // Read the parent's shared flags by yielding the parent command. `root` is
    // typed as `{ workspace: string; verbose: boolean }`.
    const root = yield* tasks

    if (root.verbose) {
      yield* Console.log(`workspace=${root.workspace} action=create`)
    }
    yield* Console.log(
      `Created "${title}" in ${root.workspace} with ${priority} priority`
    )
  })
).pipe(
  Command.withDescription("Create a task"),
  Command.withExamples([
    {
      command: 'tasks create "Ship 4.0" --priority high',
      description: "Create a high-priority task"
    }
  ])
)

const list = Command.make(
  "list",
  {
    status: Flag.choice("status", ["open", "done", "all"]).pipe(
      Flag.withDescription("Filter tasks by status"),
      Flag.withDefault("open")
    )
  },
  Effect.fn(function*({ status }) {
    const root = yield* tasks
    yield* Console.log(`Listing ${status} tasks in ${root.workspace}`)
  })
).pipe(
  Command.withDescription("List tasks"),
  // Give the command a shorter alternative name: `tasks ls`.
  Command.withAlias("ls")
)

// Attach the children, then run the whole tree as one executable.
tasks.pipe(
  Command.withSubcommands([create, list]),
  Command.run({ version: "1.0.0" }),
  Effect.provide(NodeServices.layer),
  NodeRuntime.runMain
)
```

A few invocations and what they do:

- `tasks --workspace team-a create "Fix bug" --priority high` — runs `create`
  with `workspace: "team-a"`.
- `tasks create "Fix bug" -w team-a` — identical: shared flags are accepted
  after the subcommand name too.
- `tasks ls --status done` — runs `list` via its alias.
- `tasks --help` and `tasks create --help` — generated help for the tree and for
  a single subcommand.

## How subcommands read parent input

A `Command` is itself an `Effect` whose success value is the parent's parsed
input. So inside a child handler, `yield* parentCommand` gives you back the
parent's shared flags, fully typed. This is type-safe in both directions:

- Only flags declared with `withSharedFlags` are visible to children. Plain
  config on the parent stays **local** and is *not* inherited — a child cannot
  accidentally depend on it.
- Effect tracks the dependency at the type level, so if a child yields a parent
  that was never wired up as its ancestor, the types won't line up.
**Note:** `withSharedFlags` shares **flags** (named options), not positional arguments —
sharing positionals across a command tree would be ambiguous. Shared boolean
flags like `--verbose` are the typical use case.

## Composing larger trees

`withSubcommands` takes an array, and the result is just another `Command`, so
you can nest arbitrarily deep — attach subcommands to a command that is itself a
subcommand of something else. Effect handles selection (only the first
non-flag token opens a subcommand), `--` to stop option parsing, and friendly
"did you mean…?" suggestions for misspelled subcommands automatically.

Because every handler is an ordinary Effect, a subcommand can do everything any
Effect can: depend on [services](https://effect.plants.sh/services-and-layers/), acquire
[scoped resources](https://effect.plants.sh/resource-management/), fail with
[typed errors](https://effect.plants.sh/error-management/), and read
[configuration](https://effect.plants.sh/configuration/). Provide those dependencies on the final
`Command.run` Effect, or per-command with `Command.provide`.

## Testing a command tree

`Command.runWith` is the same as `Command.run` but takes an explicit argument
array instead of reading `process.argv`. That makes commands easy to exercise in
[tests](https://effect.plants.sh/testing/) without spawning a process.

```ts
import { Effect } from "effect"
import { Command } from "effect/unstable/cli"

const program = Effect.gen(function*() {
  const run = Command.runWith(
    tasks.pipe(Command.withSubcommands([create, list])),
    { version: "1.0.0" }
  )

  // Drive the CLI with explicit argv-style arrays.
  yield* run(["--workspace", "team-a", "create", "Ship 4.0", "--priority", "high"])
  yield* run(["list", "--status", "done"])
  yield* run(["--help"])
})
```

Provide the platform services (`NodeServices.layer`, or a test-friendly `Stdio`
layer) when you run `program`, just as you would in production.

## Parsing semantics

The parser is permissive in the npm/commander tradition: flexible option
placement, shared parent flags usable on either side of a subcommand, strict
`--` handling, and single-shot subcommand selection. The user-facing rules:

- **Shared parent flags work before or after the subcommand.** Both
  `tool --global install --pkg cowsay` and `tool install --pkg cowsay --global`
  are valid when `--global` is declared with `withSharedFlags`.
- **Local parent flags are not inherited.** A flag declared as plain config on
  the parent cannot appear on a subcommand path:
  `tool --workspace docs chat` fails if `--workspace` is local to `tool`.
- **Only the first value token opens a subcommand.** In
  `tool install pkg1 pkg2`, `install` is the subcommand and `pkg1 pkg2` are
  operands passed to it.
- **`--` stops option parsing.** Everything after `--` is an operand:
  `tool -- child --value x` treats `child --value x` as operands and does not
  enter the `child` subcommand.
- **Options may appear before, after, or between operands** (relaxed POSIX
  Guideline 9): `tool copy --recursive src dest`, `tool copy src dest --recursive`,
  and `tool copy --recursive src dest --force` are all equivalent.
- **Boolean flags default to `true` when present** and support the canonical
  `--no-<flag>` negation: `--verbose` is `true`, `--no-verbose` is `false`. An
  optional boolean (`Flag.optional(Flag.boolean("verbose"))`) yields
  `Option.none()` when omitted, distinguishing it from an explicit `false`.
- **Repeated `key=value` flags merge into one map.**
  `tool --env foo=bar --env cool=dude` parses to `{ foo: "bar", cool: "dude" }`.
- **Unknown subcommands and options emit "did you mean?" suggestions.** `tool cpy`
  suggests `copy`; `tool --debugs` suggests a near match. Commands marked with
  `withHidden` are excluded from these suggestions.
- **`--version` and `--help` take global precedence.** They print and exit
  regardless of position or selected subcommand, so `tool --version copy src dest`
  prints the version and never runs `copy`.

## Command combinator reference

Every combinator below takes a `Command` and returns a new one, so they all chain
inside `.pipe(...)`. They are pure: the command is only executed by
[`Command.run`](#commandrun) / [`Command.runWith`](#commandrunwith).

### Command.make

Creates a `Command` from a name, an optional `Config` of flags/arguments, and an
optional handler. With no second argument it is an empty namespace command;
with a config but no handler it parses input but has nothing to run yet; with a
handler it is immediately runnable.

```ts
import { Console } from "effect"
import { Command, Flag } from "effect/unstable/cli"

// Name only — a namespace for subcommands.
const root = Command.make("app")

// Name + config — parses `--name` but has no handler yet.
const greet = Command.make("greet", { name: Flag.string("name") })

// Name + config + handler — runnable.
const hello = Command.make(
  "hello",
  { name: Flag.string("name") },
  ({ name }) => Console.log(`Hello, ${name}!`)
)
// => `app hello --name Alice` logs "Hello, Alice!"
```

The config may be nested for organization; the handler receives the inferred
shape (e.g. `{ server: { host: string } }`).

### Command.withHandler

Attaches (or replaces) the handler on a command created without one. Useful when
the command and its handler are defined in different places.

```ts
import { Console } from "effect"
import { Command, Flag } from "effect/unstable/cli"

const greet = Command.make("greet", { name: Flag.string("name") })

const greetCmd = greet.pipe(
  Command.withHandler(({ name }) => Console.log(`Hello, ${name}!`))
)
// => `greet --name Bob` logs "Hello, Bob!"
```

### Command.withSubcommands

Attaches child commands, producing a hierarchical tree. Children may be passed as
a flat array, or grouped via `{ group, commands }` entries that the help
formatter renders under a labeled heading. Duplicate flags across parent/child
scopes are rejected.

```ts
import { Command } from "effect/unstable/cli"

const add = Command.make("add")
const remove = Command.make("remove")
const status = Command.make("status")

const git = Command.make("git").pipe(
  Command.withSubcommands([
    // Grouped entry — appears under "porcelain" in help output.
    { group: "porcelain", commands: [add, status] },
    // Bare command — ungrouped.
    remove
  ])
)
// => `git add`, `git status`, `git remove`
```

### Command.withSharedFlags

Adds flags that are inherited by every descendant. Unlike plain config, shared
flags may be written **before or after** the subcommand name (npm-style) and are
read inside child handlers via `yield* parentCommand`. Only flags are
allowed — never positional arguments.

```ts
import { Console, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"

const app = Command.make("app").pipe(
  Command.withSharedFlags({ verbose: Flag.boolean("verbose") })
)

const build = Command.make("build", {}, () =>
  Effect.gen(function*() {
    const { verbose } = yield* app // read the shared parent flag
    if (verbose) yield* Console.log("verbose build")
  }))

const cli = app.pipe(Command.withSubcommands([build]))
// => `app --verbose build` and `app build --verbose` both enable verbose
```

### Command.withGlobalFlags

Attaches `GlobalFlag` values that apply to the command and all of its
descendants — for example a `--log-level` setting or a custom action flag.
Settings are removed from the handler's requirements once attached. See
[Global flags & completions](https://effect.plants.sh/cli/global-flags-and-completions/) for defining
them.

```ts
import { Command, GlobalFlag } from "effect/unstable/cli"

// The built-in log-level setting flag, scoped to this command tree.
const app = Command.make("app").pipe(
  Command.withGlobalFlags([GlobalFlag.LogLevel])
)
// => `app --log-level Debug ...` raises the minimum log level for the run
```
**Note:** `--help` and `--version` are wired in automatically by `Command.run`, so you do
not need to add them via `withGlobalFlags`.

### Command.withDescription

Sets the long description shown in the command's own `--help` output.

```ts
import { Command } from "effect/unstable/cli"

const deploy = Command.make("deploy").pipe(
  Command.withDescription("Deploy the application to a target environment")
)
// => shown at the top of `deploy --help`
```

### Command.withShortDescription

Sets the one-line summary used when the command is **listed** among its parent's
subcommands and in shell completions. Falls back to the full `description` if not
set.

```ts
import { Command } from "effect/unstable/cli"

const deploy = Command.make("deploy").pipe(
  Command.withDescription("Deploy the application to a target environment, running migrations first"),
  Command.withShortDescription("Deploy the app")
)
// => the parent's help lists: "deploy   Deploy the app"
```

### Command.withAlias

Adds an alternate name accepted during parsing and rendered as `name, alias` in
help.

```ts
import { Command } from "effect/unstable/cli"

const list = Command.make("list").pipe(Command.withAlias("ls"))
// => both `app list` and `app ls` select the command
```

### Command.withHidden

Hides a subcommand from parent help, shell completions, and "did you mean?"
suggestions, while keeping it fully runnable by its exact name. Use for
experimental or internal commands.

```ts
import { Command } from "effect/unstable/cli"

const experimental = Command.make("experimental").pipe(Command.withHidden)

const root = Command.make("app").pipe(
  Command.withSubcommands([experimental])
)
// => `app experimental` runs, but it is absent from `app --help`
```

### Command.withExamples

Attaches usage examples (`{ command, description }`) rendered in an `EXAMPLES`
section of the command's help.

```ts
import { Command } from "effect/unstable/cli"

const login = Command.make("login").pipe(
  Command.withExamples([
    { command: "app login", description: "Log in with browser OAuth" },
    { command: "app login --token sbp_abc123", description: "Log in with a token" }
  ])
)
// => the two lines appear under EXAMPLES in `login --help`
```

### Command.annotate

Attaches a single piece of command-scoped metadata under a `Context.Key`. Useful
for custom help formatters or other tooling. Re-adding the same key replaces the
previous value.

```ts
import { Context } from "effect"
import { Command } from "effect/unstable/cli"

// A key for a custom "category" annotation, with a default value.
const Category = Context.Reference<string>("app/Category", {
  defaultValue: () => "general"
})

const deploy = Command.make("deploy").pipe(
  Command.annotate(Category, "ops")
)
// => deploy.annotations now carries Category = "ops"
```

### Command.annotateMerge

Merges an already-built `Context.Context` of annotations into a command. On key
conflicts, the incoming context wins.

```ts
import { Context } from "effect"
import { Command } from "effect/unstable/cli"

const Category = Context.Reference<string>("app/Category", {
  defaultValue: () => "general"
})

const extra = Context.make(Category, "ops")

const deploy = Command.make("deploy").pipe(
  Command.annotateMerge(extra)
)
// => merges all entries of `extra` onto the command at once
```

### Command.provide

Supplies a `Layer` to a single command and its descendants, optionally built from
the parsed input. The provided services are removed from the command's
requirement type. See [services & layers](https://effect.plants.sh/services-and-layers/).

```ts
import { Effect, FileSystem } from "effect"
import { Command, Flag } from "effect/unstable/cli"

const deploy = Command.make("deploy", { env: Flag.string("env") }, () =>
  Effect.gen(function*() {
    const fs = yield* FileSystem.FileSystem
    // ...use fs
  })).pipe(
    // The layer can depend on the parsed input.
    Command.provide((config) =>
      config.env === "local"
        ? FileSystem.layerNoop({})
        : FileSystem.layerNoop({})
    )
  )
// => FileSystem is satisfied per-command, chosen from --env
```

### Command.provideSync

Provides a service synchronously, either as a constant value or computed from the
parsed input.

```ts
import { Context, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"

class Region extends Context.Service<Region, string>()("app/Region") {}

const deploy = Command.make("deploy", { region: Flag.string("region") }, () =>
  Effect.gen(function*() {
    const region = yield* Region
    // ...
  })).pipe(
    Command.provideSync(Region, (config) => config.region)
  )
// => Region service is set from the --region flag
```

### Command.provideEffect

Acquires a service effectfully (optionally from the parsed input) before running
the handler. The effect's errors and requirements join the command's.

```ts
import { Context, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"

class Token extends Context.Service<Token, string>()("app/Token") {}

const deploy = Command.make("deploy", { env: Flag.string("env") }, () =>
  Effect.gen(function*() {
    const token = yield* Token
    // ...
  })).pipe(
    Command.provideEffect(Token, (config) =>
      Effect.succeed(`token-for-${config.env}`))
  )
// => Token is computed effectfully each run, before the handler
```

### Command.provideEffectDiscard

Runs an effect (optionally from the parsed input) **before** the handler without
providing any service — handy for setup, validation, or logging.

```ts
import { Console, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"

const deploy = Command.make("deploy", { env: Flag.string("env") }, () =>
  Console.log("deploying")).pipe(
    Command.provideEffectDiscard((config) =>
      Console.log(`Preparing to deploy to ${config.env}`))
  )
// => logs the "Preparing..." line first, then runs the handler
```

### Command.run

Runs a command using the arguments from the `Stdio` service (i.e. `process.argv`
on Node). Pass a `version` string to wire up the built-in `--version` flag. This
is the normal application entry point.

```ts
import { Console } from "effect"
import { Command, Flag } from "effect/unstable/cli"

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

const program = greet.pipe(Command.run({ version: "1.0.0" }))
// => an Effect; run it with NodeRuntime.runMain and NodeServices.layer
```

### Command.runWith

Like `run`, but takes an explicit `ReadonlyArray<string>` of arguments instead of
reading `Stdio`. This is the preferred entry point for [tests](https://effect.plants.sh/testing/).

```ts
import { Console, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"

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

const test = Effect.gen(function*() {
  const run = Command.runWith(greet, { version: "1.0.0" })
  yield* run(["--name", "Alice"]) // logs "Hello, Alice!"
  yield* run(["--help"]) // prints help
  yield* run(["--version"]) // prints "1.0.0"
})
```

### Command.isCommand

A type guard that returns `true` if a value is a `Command`. It checks for the
command type-id, not the full shape.

```ts
import { Command } from "effect/unstable/cli"

Command.isCommand(Command.make("app")) // => true
Command.isCommand({ name: "app" }) // => false
```