1/**
2 * @title Getting started with Effect CLI modules
3 *
4 * Build a command-line app with typed arguments and flags, then wire subcommand
5 * handlers into a single executable command.
6 */
7import { NodeRuntime, NodeServices } from "@effect/platform-node"
8import { Console, Effect, Option, Schema } from "effect"
9import { Argument, Command, Flag } from "effect/cli"
10
11// You can define flags outside of commands and reuse them across multiple
12// commands.
13const workspace = Flag.String("workspace").pipe(
14 Flag.withAlias("w"),
15 Flag.withDescription("Workspace to operate on"),
16 Flag.withDefault("personal")
17)
18
19// Start with a root command and explicitly share the parent flags that should
20// be available to all subcommands.
21const tasks = Command.make("tasks").pipe(
22 Command.withSharedFlags({
23 workspace,
24 verbose: Flag.Boolean("verbose").pipe(
25 Flag.withAlias("v"),
26 Flag.withDescription("Print diagnostic output"),
27 Flag.withDefault(false)
28 )
29 }),
30 Command.withDescription("Track and manage tasks")
31)
32
33// Arguments and flags parse plain strings; use `withSchema` to validate or
34// transform the parsed value with any schema.
35const Email = Schema.String.pipe(
36 Schema.check(Schema.isPattern(/^[^\s@]+@[^\s@]+\.[^\s@]+$/, {
37 message: "Expected a valid email address"
38 }))
39)
40
41const create = Command.make(
42 "create",
43 {
44 title: Argument.String("title").pipe(
45 Argument.withDescription("Task title"),
46 // Reject empty titles at parse time, so the handler only ever sees
47 // valid input
48 Argument.withSchema(Schema.NonEmptyString)
49 ),
50 priority: Flag.Literals("priority", ["low", "normal", "high"]).pipe(
51 Flag.withDescription("Priority for the new task"),
52 Flag.withDefault("normal")
53 ),
54 assignee: Flag.String("assignee").pipe(
55 Flag.withDescription("Email address of the person to assign"),
56 Flag.withSchema(Email),
57 Flag.optional
58 )
59 },
60 Effect.fn(function*({ assignee, priority, title }) {
61 // Subcommands can read parent command input by yielding the parent command.
62 const root = yield* tasks
63
64 if (root.verbose) {
65 yield* Console.log(`workspace=${root.workspace} action=create`)
66 }
67
68 yield* Console.log(`Created "${title}" in ${root.workspace} with ${priority} priority`)
69
70 if (Option.isSome(assignee)) {
71 yield* Console.log(`Assigned to ${assignee.value}`)
72 }
73 })
74).pipe(
75 Command.withDescription("Create a task"),
76 Command.withExamples([
77 {
78 command: "tasks create \"Ship 4.0\" --priority high",
79 description: "Create a high-priority task"
80 },
81 {
82 command: "tasks create \"Ship 4.0\" --assignee dev@acme.com",
83 description: "Create a task assigned to a team member"
84 }
85 ])
86)
87
88const list = Command.make(
89 "list",
90 {
91 status: Flag.Literals("status", ["open", "done", "all"]).pipe(
92 Flag.withDescription("Filter tasks by status"),
93 Flag.withDefault("open")
94 ),
95 json: Flag.Boolean("json").pipe(
96 Flag.withDescription("Print machine-readable output"),
97 Flag.withDefault(false)
98 )
99 },
100 Effect.fn(function*({ status, json }) {
101 const root = yield* tasks
102 const items = [
103 { title: "Ship 4.0", status: "open" },
104 { title: "Update onboarding guide", status: "done" }
105 ] as const
106 const filtered = status === "all"
107 ? items
108 : items.filter((item) => item.status === status)
109
110 if (root.verbose) {
111 yield* Console.log(`workspace=${root.workspace} action=list`)
112 }
113
114 if (json) {
115 yield* Console.log(JSON.stringify(
116 {
117 workspace: root.workspace,
118 status,
119 items: filtered
120 },
121 null,
122 2
123 ))
124 return
125 }
126
127 yield* Console.log(`Listing ${status} tasks in ${root.workspace}`)
128 if (filtered.length === 0) {
129 yield* Console.log("- No tasks found")
130 return
131 }
132
133 for (const item of filtered) {
134 yield* Console.log(`- ${item.title}`)
135 }
136 })
137).pipe(
138 Command.withDescription("List tasks"),
139 Command.withAlias("ls"),
140 Command.withExamples([
141 {
142 command: "tasks --workspace team-a list --status open",
143 description: "List open tasks in a specific workspace"
144 },
145 {
146 command: "tasks --workspace team-b ls --status open",
147 description: "List open tasks in another workspace"
148 }
149 ])
150)
151
152// Finally, compose the subcommands into a single command and then run it.
153tasks.pipe(
154 Command.withSubcommands([create, list]),
155 Command.run({
156 version: "1.0.0"
157 }),
158 // Provide the services for the platform you are targeting. In this case,
159 // Node.js
160 Effect.provide(NodeServices.layer),
161 NodeRuntime.runMain
162)