Skip to content

Global Flags & Completions

A global flag is a flag that applies across the whole command tree rather than to a single command. There are two kinds:

  • Action flags run an effect and short-circuit the program — the command handler never runs. --help and --version work this way.
  • Setting flags parse a value and make it available to every handler through the Effect context. --log-level works this way.

You never wire up the built-ins yourself: Command.run (and Command.runWith) prepend GlobalFlag.Help, GlobalFlag.Version, GlobalFlag.Completions, and GlobalFlag.LogLevel automatically. The moment you call Command.run, your CLI already supports --help, --version, --completions, and --log-level.

app.ts
import { NodeRuntime, NodeServices } from "@effect/platform-node"
import { Console, Effect } from "effect"
import { Command, Flag } from "effect/unstable/cli"
const greet = Command.make("greet", {
name: Flag.string("name")
}, (config) => Console.log(`Hello, ${config.name}!`))
// Command.run wires up --help / --version / --completions / --log-level for free.
Command.run(greet, { version: "1.0.0" }).pipe(
Effect.provide(NodeServices.layer),
NodeRuntime.runMain
)
Terminal window
$ greet --version # => greet 1.0.0 (Version action, exits)
$ greet --help # => prints help doc (Help action, exits)
$ greet --name Alice # => Hello, Alice!
$ greet --log-level debug --name Alice # => sets minimum log level, then runs

Use Command.withGlobalFlags to attach custom global flags to a command. They apply to that command and all of its descendants, which makes them the right tool for cross-cutting concerns like --region, --config, or --verbose.

GlobalFlag.setting(id)({ flag }) returns a value that is both the global flag definition and a Context.Service you yield* inside a handler to read the parsed value. Attaching it with withGlobalFlags removes its service requirement from the command’s R channel, so the program stays fully provided.

import { Console, Effect, Option } from "effect"
import { Command, Flag, GlobalFlag } from "effect/unstable/cli"
// The returned `Region` is a global flag AND a Context.Service.
const Region = GlobalFlag.setting("region")({
flag: Flag.string("region").pipe(Flag.withDefault("us-east-1"))
})
const deploy = Command.make("deploy", {}, () =>
Effect.gen(function* () {
// Read the parsed --region value from context.
const region = yield* Region // => "us-east-1" (default) or the passed value
yield* Console.log(`Deploying to ${region}`)
})
).pipe(
// Attaching the flag discharges `Region` from the command's requirements.
Command.withGlobalFlags([Region])
)
// $ deploy => Deploying to us-east-1
// $ deploy --region eu-west-1 => Deploying to eu-west-1

If you use Flag.optional instead of Flag.withDefault, the service value is an Option:

const Region = GlobalFlag.setting("region")({
flag: Flag.string("region").pipe(Flag.optional)
})
const deploy = Command.make("deploy", {}, () =>
Effect.gen(function* () {
const region = yield* Region
// => Option.some("eu-west-1") when passed, Option.none() otherwise
yield* Console.log(Option.getOrElse(region, () => "default region"))
})
).pipe(Command.withGlobalFlags([Region]))

GlobalFlag.action({ flag, run }) runs run(value, context) when the flag is present and then exits — the handler is skipped. The second argument is a HandlerContext carrying the resolved command, commandPath, and version.

import { Console, Effect } from "effect"
import { Command, Flag, GlobalFlag } from "effect/unstable/cli"
const License = GlobalFlag.action({
flag: Flag.boolean("license").pipe(
Flag.withDescription("Print license information")
),
run: (_value, ctx) =>
Console.log(`${ctx.command.name} ${ctx.version} — MIT License`)
})
const app = Command.make("tool", {}, () => Console.log("running")).pipe(
Command.withGlobalFlags([License])
)
// $ tool --license => tool 1.0.0 — MIT License (handler is NOT run)
// $ tool => running
  • Built-ins are processed in the order of GlobalFlag.BuiltIns: Help, Version, Completions, LogLevel. The first present action runs and the program exits.
  • withGlobalFlags flags apply to the attached command and every descendant. A global flag passed outside the scope where it was declared is rejected with a help error.
  • Each call to GlobalFlag.setting allocates a distinct context service. Define the setting once and reuse the exported value wherever a handler needs to read it.

The built-in --completions <shell> action prints a shell completion script to stdout. Effect derives the completion descriptor from your command tree automatically, so no extra wiring is needed.

Terminal window
$ tool --completions bash # prints a bash completion script
$ tool --completions zsh # prints a zsh completion script
$ tool --completions fish # prints a fish completion script
$ tool --completions sh # `sh` is normalized to `bash`

Either evaluate the output in your current shell session, or write it to your shell’s completion directory so it loads on every new shell.

Terminal window
# Try it in the current session:
eval "$(tool --completions bash)"
# Install persistently (paths vary by OS / shell config):
tool --completions bash > /etc/bash_completion.d/tool
tool --completions zsh > "${fpath[1]}/_tool"
tool --completions fish > ~/.config/fish/completions/tool.fish

Completion scripts complete subcommand names, flag names and aliases, finite choice values, and file/directory paths — all derived from the same Param definitions that drive parsing and help.


Attaching global flags: Command.withGlobalFlags

Section titled “Attaching global flags: Command.withGlobalFlags”

Command.withGlobalFlags(flags) adds global flags to a command scope. For every setting flag in the array, the matching Context.Service requirement is removed from the command’s R channel (Exclude<R, ...Identifier>), so the program is fully provided once attached. Available both data-first and data-last (pipeable).

import { Console, Effect } from "effect"
import { Command, Flag, GlobalFlag } from "effect/unstable/cli"
const Verbose = GlobalFlag.setting("verbose")({
flag: Flag.boolean("verbose").pipe(Flag.withDefault(false))
})
// Data-last (pipe) form:
const a = Command.make("deploy", {}, () =>
Effect.gen(function* () {
if (yield* Verbose) yield* Console.log("verbose mode on")
})
).pipe(Command.withGlobalFlags([Verbose]))
// Data-first form:
const b = Command.withGlobalFlags(a, [Verbose])

See Subcommands for how flags attached to a parent command flow down to children.


The GlobalFlag module (effect/unstable/cli) defines global flags and the built-ins. Import it as a namespace:

import { GlobalFlag } from "effect/unstable/cli"

Creates an action global flag from { flag, run }. run(value, context) runs when the flag is present and the program exits afterward; value is the parsed flag value and context is a HandlerContext.

import { Console } from "effect"
import { Flag, GlobalFlag } from "effect/unstable/cli"
const WhoAmI = GlobalFlag.action({
flag: Flag.boolean("whoami"),
run: (_value, ctx) => Console.log(ctx.command.name)
})
// WhoAmI._tag => "Action"

GlobalFlag.setting(id)({ flag }) returns a value that is both a setting global flag and a Context.Service for the parsed value. Reading it in a handler is yield* <theSetting>.

import { Flag, GlobalFlag } from "effect/unstable/cli"
const Format = GlobalFlag.setting("format")({
flag: Flag.string("format").pipe(Flag.withDefault("text"))
})
// Format._tag => "Setting"
// Format.id => "format"
// In a handler: const fmt = yield* Format // => "text" or the passed value

The context passed as the second argument to an action’s run. It exposes the resolved command, the active command path, and the configured version.

import { GlobalFlag } from "effect/unstable/cli"
declare const ctx: GlobalFlag.HandlerContext
ctx.command // => the Command.Any that was matched
ctx.commandPath // => ReadonlyArray<string>, e.g. ["tool", "deploy"]
ctx.version // => "1.0.0" (the version passed to Command.run)

The two members of the GlobalFlag<A> discriminated union, distinguished by _tag. Action<A> carries { _tag: "Action", flag, run }; Setting<Id, A> extends Context.Service and carries { _tag: "Setting", id, flag }. You rarely construct these directly — use action / setting.

import { GlobalFlag } from "effect/unstable/cli"
declare const f: GlobalFlag.GlobalFlag<unknown>
f._tag // => "Action" | "Setting"

The built-in --help / -h action flag. Prints help documentation for the active command path and exits.

import { GlobalFlag } from "effect/unstable/cli"
GlobalFlag.Help._tag // => "Action"
// Triggered by: $ tool --help or $ tool -h

The built-in --version action flag. Prints <name> <version> using the version passed to Command.run and exits.

import { GlobalFlag } from "effect/unstable/cli"
GlobalFlag.Version._tag // => "Action"
// $ tool --version => tool 1.0.0

The built-in --completions <shell> action flag. Its value is an Option<"bash" | "zsh" | "fish"> (the choice also accepts sh, normalized to bash). When present, it derives a CommandDescriptor from the command and prints the generated script.

import { GlobalFlag } from "effect/unstable/cli"
GlobalFlag.Completions._tag // => "Action"
// $ tool --completions zsh => prints a zsh completion script

The built-in --log-level <level> setting flag (id "log-level"). Accepts all, trace, debug, info, warn/warning, error, fatal, none. When set, Command.run provides it as the minimum log level for the handler; you can also read the parsed Option<LogLevel> directly.

import { Effect, Option } from "effect"
import { Command, GlobalFlag } from "effect/unstable/cli"
const app = Command.make("tool", {}, () =>
Effect.gen(function* () {
const level = yield* GlobalFlag.LogLevel
// => Option.some("Debug") when run with --log-level debug, else Option.none()
yield* Effect.logDebug("debug line will show when --log-level debug")
return Option.getOrElse(level, () => "Info")
})
)

See Logging for how log levels filter output.

The array of built-in global flags in precedence order: [Help, Version, Completions, LogLevel]. Command.runWith prepends these when collecting and parsing global flags.

import { GlobalFlag } from "effect/unstable/cli"
GlobalFlag.BuiltIns.map((f) => f._tag)
// => ["Action", "Action", "Action", "Setting"]

Type-level helper for the service identifier used by setting flags: `effect/unstable/cli/GlobalFlag/${Id}`. GlobalFlag.BuiltInSettingContext is Setting.Identifier<"log-level">, the requirement that Command.run and Command.make automatically discharge.

import type { GlobalFlag } from "effect/unstable/cli"
type RegionId = GlobalFlag.Setting.Identifier<"region">
// => "effect/unstable/cli/GlobalFlag/region"

The Completions module (effect/unstable/cli) is the lower-level script generation surface. The built-in --completions flag uses it for you; reach for it directly only when you build descriptors yourself.

import { Completions } from "effect/unstable/cli"

Completions.generate(executableName, shell, descriptor) returns a completion script as a string, dispatching by shell to the Bash, Zsh, or Fish generator. It consumes an already-built CommandDescriptor — it does not inspect a Command directly.

import { Completions } from "effect/unstable/cli"
const descriptor: Completions.CommandDescriptor = {
name: "greet",
description: "Greet someone",
flags: [
{
name: "name",
aliases: ["n"],
description: "Who to greet",
type: { _tag: "String" }
}
],
arguments: [],
subcommands: []
}
const script = Completions.generate("greet", "bash", descriptor)
// => a bash completion script string; printing/installing is up to you

The supported shell names for script generation: "bash" | "zsh" | "fish".

import type { Completions } from "effect/unstable/cli"
const shell: Completions.Shell = "zsh" // "bash" | "zsh" | "fish"

A shell-agnostic tree describing a command: its name, optional description, its flags, positional arguments, and nested subcommands. This is the shape generate consumes. When using --completions, Effect derives it from your command automatically.

import type { Completions } from "effect/unstable/cli"
const root: Completions.CommandDescriptor = {
name: "tool",
description: undefined,
flags: [],
arguments: [],
subcommands: [
{ name: "deploy", description: undefined, flags: [], arguments: [], subcommands: [] }
]
}
// root.subcommands[0].name => "deploy"

Describes one flag for completion: its name, aliases, optional description, and value type (a FlagType). Filling in descriptions preserves help text in the generated completions.

import type { Completions } from "effect/unstable/cli"
const flag: Completions.FlagDescriptor = {
name: "format",
aliases: ["f"],
description: "Output format",
type: { _tag: "Choice", values: ["json", "text"] }
}

The value shape of a flag, a tagged union: Boolean, String, Integer, Float, Date, Choice (with values), or Path (with pathType of "file" | "directory" | "either"). Shells use this to complete choices and file paths.

import type { Completions } from "effect/unstable/cli"
const boolean: Completions.FlagType = { _tag: "Boolean" }
const choice: Completions.FlagType = { _tag: "Choice", values: ["a", "b"] }
const path: Completions.FlagType = { _tag: "Path", pathType: "file" }

Describes a positional argument: its name, optional description, whether it is required, whether it is variadic, and its value type (an ArgumentType).

import type { Completions } from "effect/unstable/cli"
const arg: Completions.ArgumentDescriptor = {
name: "file",
description: "File to process",
required: true,
variadic: false,
type: { _tag: "Path", pathType: "file" }
}

The value shape of a positional argument, a tagged union: String, Integer, Float, Date, Choice (with values), or Path (with pathType). Same shapes as FlagType minus Boolean.

import type { Completions } from "effect/unstable/cli"
const str: Completions.ArgumentType = { _tag: "String" }
const dir: Completions.ArgumentType = { _tag: "Path", pathType: "directory" }