Loading...
Loading...
Building CLI applications with @effect/cli. Use when defining commands, options, args, or prompts for a CLI tool using effect.
npx skill4agent add tstelzer/skills effect-cliCommand.run(command, { name, version })
├── Command (parent)
│ ├── Options (named flags: --verbose, --depth 5)
│ ├── Args (positional: <repo> <path>)
│ └── Command.withSubcommands([...])
│ ├── Command (child — can yield* parent for its config)
│ └── Command (child)import { Command, Options, Args } from "@effect/cli"
import { NodeContext, NodeRuntime } from "@effect/platform-node"
import { Console, Effect } from "effect"
const greet = Command.make(
"greet",
{
name: Args.text({ name: "name" }),
loud: Options.boolean("loud").pipe(Options.withAlias("l"))
},
({ name, loud }) =>
Console.log(loud ? name.toUpperCase() + "!" : `Hello, ${name}!`)
)
const cli = Command.run(greet, { name: "Greeter", version: "1.0.0" })
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)// No config
const root = Command.make("app")
// With config (record of Options + Args, arbitrarily nested)
const cmd = Command.make("cmd", {
verbose: Options.boolean("verbose"),
file: Args.text({ name: "file" })
}, ({ verbose, file }) => Console.log(file))
// Attach handler later
const cmd2 = Command.make("cmd", { n: Args.integer({ name: "n" }) }).pipe(
Command.withHandler(({ n }) => Console.log(n))
)
// Description
const cmd3 = cmd.pipe(Command.withDescription("Does something useful"))const git = Command.make("git", {
verbose: Options.boolean("verbose").pipe(Options.withAlias("v"))
})
const clone = Command.make("clone", { repo: Args.text({ name: "repo" }) },
({ repo }) =>
Effect.gen(function* () {
const { verbose } = yield* git // access parent's parsed config
yield* Console.log(`Cloning ${repo}, verbose=${verbose}`)
})
)
const add = Command.make("add", { path: Args.text({ name: "path" }) },
({ path }) => Console.log(`Adding ${path}`)
)
const command = git.pipe(Command.withSubcommands([clone, add]))yield* parentCommandCommandEffectcmd.pipe(Command.provide(MyService.layer))
cmd.pipe(Command.provide((config) => MyService.layer(config.path))) // config-dependent
cmd.pipe(Command.provideEffect(MyService, (_config) => Effect.succeed(impl)))
cmd.pipe(Command.provideSync(MyService, impl))
cmd.pipe(Command.provideEffectDiscard((_config) => Effect.log("Starting...")))
// Transform handler
cmd.pipe(Command.transformHandler((effect, config) =>
Effect.provideService(effect, MyService, impl)
))| Constructor | CLI syntax | Type |
|---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
Options.text("name").pipe(
Options.withAlias("n"), // -n shorthand
Options.withDescription("Your name"),
Options.optional, // Option<string>
// or: Options.withDefault("world"), // string with default
)
// Repetition
Options.text("tag").pipe(Options.repeated) // Array<string>
Options.text("tag").pipe(Options.atLeast(1)) // NonEmptyArray<string>
// Schema validation
Options.text("balance").pipe(Options.withSchema(Schema.BigDecimal))
// Fallback to env var (via Effect Config)
Options.integer("port").pipe(Options.withFallbackConfig(Config.integer("PORT")))
// Fallback to interactive prompt
Options.text("name").pipe(
Options.withFallbackPrompt(Prompt.text({ message: "Enter name:" }))
)| Constructor | Type |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Args.text({ name: "dir" }).pipe(
Args.optional, // Option<string>
// or: Args.withDefault("/"), // string with default
// or: Args.repeated, // Array<string>
// or: Args.atLeast(1), // NonEmptyArray<string>
)
Args.text({ name: "n" }).pipe(Args.withSchema(Schema.NumberFromString))
Args.text({ name: "r" }).pipe(Args.withFallbackConfig(Config.string("REPO")))
Args.text({ name: "r" }).pipe(Args.withDescription("The repository URL"))import { Prompt } from "@effect/cli"
Prompt.text({ message: "Name:", default: "Alice" }) // string
Prompt.password({ message: "Password:" }) // Redacted
Prompt.integer({ message: "Age:", min: 0, max: 150 }) // number
Prompt.confirm({ message: "Sure?" }) // boolean
Prompt.toggle({ message: "Enable?", active: "on", inactive: "off" }) // boolean
Prompt.select({
message: "Pick env:",
choices: [
{ title: "Production", value: "prod" },
{ title: "Staging", value: "staging" },
{ title: "Dev", value: "dev" }
]
}) // string
Prompt.list({ message: "Tags:", delimiter: "," }) // Array<string>
// Combine prompts
Prompt.all({ name: namePrompt, age: agePrompt })const favorites = Command.prompt(
"favorites",
Prompt.all([
Prompt.select({ message: "Color?", choices: [...] }),
Prompt.confirm({ message: "Continue?" })
]),
([color, confirmed]) => Console.log(`Color: ${color}`)
)const cli = Command.run(command, {
name: "My App",
version: "1.0.0",
// summary: Span.text("Short summary"),
// footer: HelpDoc.p("Footer text"),
})
// cli: (args: ReadonlyArray<string>) => Effect<void, ...>
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)process.argv| Flag | Effect |
|---|---|
| Print auto-generated help |
| Print version |
| Interactive wizard for all options/args |
| Shell completion scripts |
| Set log level |
import { ConfigFile } from "@effect/cli"
Effect.suspend(() => cli(process.argv)).pipe(
Effect.provide(
Layer.mergeAll(
NodeContext.layer,
ConfigFile.layer("myapp") // reads myapp.json, myapp.yaml, etc.
)
),
NodeRuntime.runMain
)withFallbackConfigwithFallbackConfigwithFallbackPromptwithDefault