opencode-effect-enforcer 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,523 @@
1
+ ---
2
+ name: effect-cli
3
+ description: Build type-safe CLI applications using Effect CLI module for argument parsing, options, commands, and dependency injection.
4
+ ---
5
+
6
+ # Effect CLI (v4)
7
+
8
+ Build type-safe command-line applications with typed arguments, flags, subcommands, and dependency injection.
9
+
10
+ ## Import Pattern
11
+
12
+ ```typescript
13
+ import { Argument, Command, Flag, Prompt } from 'effect/unstable/cli';
14
+ ```
15
+
16
+ Platform services and runtime for the entry point:
17
+
18
+ ```typescript
19
+ import { NodeRuntime, NodeServices } from '@effect/platform-node';
20
+ ```
21
+
22
+ ## Positional Arguments (Argument)
23
+
24
+ Positional arguments are parsed in order. `Argument.boolean` intentionally does not exist — use `Flag.boolean` or `Argument.choice("name", ["true", "false"])` instead.
25
+
26
+ ### Constructors
27
+
28
+ ```typescript
29
+ import { Argument } from 'effect/unstable/cli';
30
+
31
+ Argument.string('name'); // string
32
+ Argument.integer('count'); // number (integer)
33
+ Argument.float('ratio'); // number (float)
34
+ Argument.date('deadline'); // Date
35
+ Argument.file('input'); // file path (string)
36
+ Argument.file('input', { mustExist: true }); // file path that must exist
37
+ Argument.directory('dir'); // directory path (string)
38
+ Argument.directory('dir', { mustExist: true }); // directory that must exist
39
+ Argument.path('target'); // any path (string)
40
+ Argument.choice('env', ['dev', 'staging', 'prod']); // constrained string union
41
+ Argument.choiceWithValue('level', [
42
+ // choice with mapped values
43
+ ['debug', 0],
44
+ ['info', 1],
45
+ ['error', 3]
46
+ ]);
47
+ Argument.redacted('secret'); // Redacted<string>
48
+ Argument.fileText('config'); // reads file content as string
49
+ Argument.fileParse('config'); // reads and parses file (auto-detects format)
50
+ Argument.fileSchema('config', MySchema); // reads and validates file via Schema
51
+ ```
52
+
53
+ ### Combinators
54
+
55
+ ```typescript
56
+ import { Argument } from 'effect/unstable/cli';
57
+
58
+ // Description for help text
59
+ Argument.string('file').pipe(Argument.withDescription('Input file'));
60
+
61
+ // Default value
62
+ Argument.integer('port').pipe(Argument.withDefault(8080));
63
+
64
+ // Optional (returns Option<T>)
65
+ Argument.string('config').pipe(Argument.optional);
66
+
67
+ // Variadic (returns ReadonlyArray<T>)
68
+ Argument.string('files').pipe(Argument.variadic);
69
+ Argument.string('files').pipe(Argument.variadic({ min: 1 }));
70
+ Argument.string('files').pipe(Argument.variadic({ min: 1, max: 5 }));
71
+
72
+ // Direct variadic form is also supported
73
+ Argument.variadic(Argument.string('files'));
74
+ Argument.variadic(Argument.string('files'), { min: 1 });
75
+
76
+ // Cardinality shortcuts
77
+ Argument.string('files').pipe(Argument.atLeast(1));
78
+ Argument.string('files').pipe(Argument.atMost(5));
79
+ Argument.string('files').pipe(Argument.between(1, 5));
80
+
81
+ // Transform
82
+ Argument.integer('port').pipe(Argument.map((p) => `http://localhost:${p}`));
83
+
84
+ // Validate with Schema
85
+ Argument.string('input').pipe(Argument.withSchema(Schema.NonEmptyString));
86
+
87
+ // Fallback from env config
88
+ Argument.string('repo').pipe(
89
+ Argument.withFallbackConfig(Config.string('REPOSITORY'))
90
+ );
91
+
92
+ // Fallback interactive prompt
93
+ Argument.string('name').pipe(
94
+ Argument.withFallbackPrompt(Prompt.text({ message: 'Name' }))
95
+ );
96
+
97
+ // Custom metavar for help text
98
+ Argument.integer('port').pipe(Argument.withMetavar('PORT'));
99
+
100
+ // Filter with error message
101
+ Argument.integer('count').pipe(
102
+ Argument.filter(
103
+ (n) => n > 0,
104
+ (n) => `Expected positive, got ${n}`
105
+ )
106
+ );
107
+ ```
108
+
109
+ ## Named Flags (Flag)
110
+
111
+ Flags are named options with `--name` or `-alias` syntax.
112
+
113
+ ### Constructors
114
+
115
+ ```typescript
116
+ import { Flag } from 'effect/unstable/cli';
117
+
118
+ Flag.boolean('verbose'); // required: --verbose / --no-verbose; omission fails
119
+ Flag.string('config'); // --config value
120
+ Flag.integer('port'); // --port 8080
121
+ Flag.float('rate'); // --rate 3.14
122
+ Flag.date('since'); // --since 2024-01-01
123
+ Flag.file('input'); // --input file.txt
124
+ Flag.file('input', { mustExist: true }); // file must exist
125
+ Flag.directory('output'); // --output ./dist
126
+ Flag.path('config-path'); // --config-path /etc/app
127
+ Flag.choice('env', ['dev', 'staging', 'prod']); // --env dev
128
+ Flag.choiceWithValue('log-level', [
129
+ // choice with mapped values
130
+ ['debug', 'Debug' as const],
131
+ ['info', 'Info' as const],
132
+ ['error', 'Error' as const]
133
+ ]);
134
+ Flag.redacted('password'); // Redacted<string>
135
+ Flag.fileText('config-file'); // reads file content
136
+ Flag.fileParse('config'); // reads and parses file (auto-detects format)
137
+ Flag.fileSchema('config', MySchema); // reads and validates via Schema
138
+ Flag.keyValuePair('env'); // --env FOO=bar → Record<string, string>
139
+ ```
140
+
141
+ ### Combinators
142
+
143
+ ```typescript
144
+ import { Flag } from 'effect/unstable/cli';
145
+
146
+ // Alias
147
+ Flag.boolean('verbose').pipe(
148
+ Flag.withAlias('v'),
149
+ Flag.withDefault(false)
150
+ ); // switch semantics: omission is false
151
+
152
+ // Hidden from help, completions, and typo suggestions, but still parsed
153
+ Flag.boolean('experimental-foo').pipe(
154
+ Flag.withHidden,
155
+ Flag.withDefault(false)
156
+ );
157
+
158
+ // Description
159
+ Flag.string('config').pipe(Flag.withDescription('Path to config file'));
160
+
161
+ // Default value (makes flag optional with fallback)
162
+ Flag.integer('port').pipe(Flag.withDefault(3000));
163
+
164
+ // Optional (returns Option<T>)
165
+ Flag.string('token').pipe(Flag.optional);
166
+
167
+ // Custom metavar for help
168
+ Flag.string('db-url').pipe(Flag.withMetavar('URL')); // --db-url URL
169
+
170
+ // Repetition
171
+ Flag.string('tag').pipe(Flag.atLeast(1)); // --tag a --tag b
172
+ Flag.string('warning').pipe(Flag.atMost(3));
173
+ Flag.string('host').pipe(Flag.between(1, 3));
174
+
175
+ // Transform
176
+ Flag.integer('port').pipe(Flag.map((p) => `http://localhost:${p}`));
177
+
178
+ // Validate with Schema
179
+ Flag.string('email').pipe(Flag.withSchema(EmailSchema));
180
+
181
+ // Filter
182
+ Flag.integer('port').pipe(
183
+ Flag.filter(
184
+ (p) => p >= 1 && p <= 65535,
185
+ (p) => `Port ${p} out of range`
186
+ )
187
+ );
188
+
189
+ // Fallback from env config
190
+ Flag.boolean('verbose').pipe(
191
+ Flag.withFallbackConfig(Config.boolean('VERBOSE'))
192
+ );
193
+
194
+ // Fallback interactive prompt
195
+ Flag.string('name').pipe(
196
+ Flag.withFallbackPrompt(Prompt.text({ message: 'Name' }))
197
+ );
198
+ ```
199
+
200
+ Hidden flags parse normally, but generated help, shell completions, and typo suggestions omit them.
201
+
202
+ Bare boolean flags are required. `--verbose` produces `true`, `--no-verbose` produces `false`, and omission produces `CliError.MissingOption`. Add `Flag.withDefault(false)` for ordinary opt-in switch behavior, or use `Flag.optional` / a config or prompt fallback when absence has separate meaning.
203
+
204
+ ### Prompt Defaults and Prefixes
205
+
206
+ ```typescript
207
+ import { Prompt } from 'effect/unstable/cli';
208
+
209
+ Prompt.integer({ message: 'Count', default: 42 });
210
+ Prompt.file({ message: 'Pick file', default: '/workspace/config.json' });
211
+
212
+ // The default prefix is "?"; an empty string omits it.
213
+ Prompt.text({ message: 'Name', prefix: '>' });
214
+ ```
215
+
216
+ Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.file` resolves/selects the default as the initial path.
217
+
218
+ ## Commands
219
+
220
+ ### Creating Commands
221
+
222
+ `Command.make` accepts a name, optional config object, and optional handler:
223
+
224
+ ```typescript
225
+ import { Console, Effect } from 'effect';
226
+ import { Argument, Command, Flag } from 'effect/unstable/cli';
227
+
228
+ // Simple command (no config, no handler)
229
+ const version = Command.make('version');
230
+
231
+ // Command with config (no handler yet)
232
+ const deploy = Command.make('deploy', {
233
+ env: Flag.string('env'),
234
+ force: Flag.boolean('force').pipe(Flag.withDefault(false)),
235
+ files: Argument.string('files').pipe(Argument.variadic)
236
+ });
237
+
238
+ // Command with config and inline handler
239
+ const greet = Command.make(
240
+ 'greet',
241
+ {
242
+ name: Argument.string('name').pipe(
243
+ Argument.withDescription('Person to greet')
244
+ ),
245
+ times: Flag.integer('times').pipe(Flag.withDefault(1))
246
+ },
247
+ Effect.fn(function* ({ name, times }) {
248
+ for (let i = 0; i < times; i++) {
249
+ yield* Console.log(`Hello, ${name}!`);
250
+ }
251
+ })
252
+ );
253
+ ```
254
+
255
+ ### Handler Pattern
256
+
257
+ Handlers use `Effect.fn` with a generator that destructures the config:
258
+
259
+ ```typescript
260
+ const cmd = Command.make(
261
+ 'deploy',
262
+ {
263
+ env: Flag.choice('env', ['dev', 'staging', 'prod']),
264
+ dryRun: Flag.boolean('dry-run').pipe(Flag.withDefault(false))
265
+ },
266
+ Effect.fn(function* ({ env, dryRun }) {
267
+ if (dryRun) {
268
+ yield* Console.log(`Would deploy to ${env}`);
269
+ } else {
270
+ yield* Console.log(`Deploying to ${env}...`);
271
+ }
272
+ })
273
+ );
274
+ ```
275
+
276
+ Alternatively, add a handler later with `Command.withHandler`:
277
+
278
+ ```typescript
279
+ const cmd = Command.make('greet', {
280
+ name: Flag.string('name')
281
+ }).pipe(Command.withHandler(({ name }) => Console.log(`Hello, ${name}!`)));
282
+ ```
283
+
284
+ ### Command Metadata
285
+
286
+ ```typescript
287
+ Command.make('deploy', config, handler).pipe(
288
+ Command.withDescription('Deploy the application'),
289
+ Command.withShortDescription('Deploy app'), // used in subcommand listings
290
+ Command.withAlias('d'), // alternate name
291
+ Command.unlisted, // omit internal/experimental subcommands from discovery
292
+ Command.withExamples([
293
+ {
294
+ command: 'myapp deploy --env prod',
295
+ description: 'Deploy to production'
296
+ },
297
+ { command: 'myapp deploy --env dev --dry-run', description: 'Dry run' }
298
+ ])
299
+ );
300
+ ```
301
+
302
+ `Command.unlisted` keeps a subcommand invocable by exact name while omitting it from parent help output, shell completions, and "did you mean?" suggestions. In Effect v4, this replaces `Command.withHidden`; the command metadata property is `unlisted`. `Flag.withHidden` remains the correct combinator for flags.
303
+
304
+ ### Nested Config
305
+
306
+ Config objects can be nested for organization:
307
+
308
+ ```typescript
309
+ const deploy = Command.make('deploy', {
310
+ environment: Flag.string('env'),
311
+ server: {
312
+ host: Flag.string('host').pipe(Flag.withDefault('localhost')),
313
+ port: Flag.integer('port').pipe(Flag.withDefault(3000))
314
+ },
315
+ files: Argument.string('files').pipe(Argument.variadic)
316
+ });
317
+ // Handler receives: { environment: string, server: { host: string, port: number }, files: ReadonlyArray<string> }
318
+ ```
319
+
320
+ ## Subcommands
321
+
322
+ ### Basic Subcommands
323
+
324
+ ```typescript
325
+ const app = Command.make('app');
326
+
327
+ const init = Command.make(
328
+ 'init',
329
+ {},
330
+ Effect.fn(function* () {
331
+ yield* Console.log('Initializing...');
332
+ })
333
+ );
334
+
335
+ const build = Command.make(
336
+ 'build',
337
+ {
338
+ target: Flag.choice('target', ['web', 'node'])
339
+ },
340
+ Effect.fn(function* ({ target }) {
341
+ yield* Console.log(`Building for ${target}`);
342
+ })
343
+ );
344
+
345
+ app.pipe(
346
+ Command.withSubcommands([init, build]),
347
+ Command.run({ version: '1.0.0' }),
348
+ Effect.provide(NodeServices.layer),
349
+ NodeRuntime.runMain
350
+ );
351
+ // Usage: app init | app build --target web
352
+ ```
353
+
354
+ ### Shared Parent Flags
355
+
356
+ Use `Command.withSharedFlags` to define flags on a parent that are available to all subcommands. Subcommands access parent config by yielding the parent command:
357
+
358
+ ```typescript
359
+ const tasks = Command.make('tasks').pipe(
360
+ Command.withSharedFlags({
361
+ workspace: Flag.string('workspace').pipe(
362
+ Flag.withAlias('w'),
363
+ Flag.withDefault('personal')
364
+ ),
365
+ verbose: Flag.boolean('verbose').pipe(
366
+ Flag.withAlias('v'),
367
+ Flag.withDefault(false)
368
+ )
369
+ })
370
+ );
371
+
372
+ const create = Command.make(
373
+ 'create',
374
+ {
375
+ title: Argument.string('title'),
376
+ priority: Flag.choice('priority', ['low', 'normal', 'high']).pipe(
377
+ Flag.withDefault('normal')
378
+ )
379
+ },
380
+ Effect.fn(function* ({ title, priority }) {
381
+ // Access parent config by yielding the parent command
382
+ const root = yield* tasks;
383
+ if (root.verbose) {
384
+ yield* Console.log(`workspace=${root.workspace} action=create`);
385
+ }
386
+ yield* Console.log(
387
+ `Created "${title}" in ${root.workspace} with ${priority} priority`
388
+ );
389
+ })
390
+ ).pipe(
391
+ Command.withDescription('Create a task'),
392
+ Command.withExamples([
393
+ {
394
+ command: 'tasks create "Ship 4.0" --priority high',
395
+ description: 'Create a high-priority task'
396
+ }
397
+ ])
398
+ );
399
+
400
+ const list = Command.make(
401
+ 'list',
402
+ {
403
+ status: Flag.choice('status', ['open', 'done', 'all']).pipe(
404
+ Flag.withDefault('open')
405
+ ),
406
+ json: Flag.boolean('json').pipe(Flag.withDefault(false))
407
+ },
408
+ Effect.fn(function* ({ status, json }) {
409
+ const root = yield* tasks;
410
+ if (json) {
411
+ yield* Console.log(
412
+ JSON.stringify({ workspace: root.workspace, status }, null, 2)
413
+ );
414
+ } else {
415
+ yield* Console.log(`Listing ${status} tasks in ${root.workspace}`);
416
+ }
417
+ })
418
+ ).pipe(Command.withDescription('List tasks'), Command.withAlias('ls'));
419
+
420
+ tasks.pipe(
421
+ Command.withSubcommands([create, list]),
422
+ Command.run({ version: '1.0.0' }),
423
+ Effect.provide(NodeServices.layer),
424
+ NodeRuntime.runMain
425
+ );
426
+ // Usage: tasks --workspace team-a list --status open
427
+ // Usage: tasks create "Ship 4.0" --priority high
428
+ // Usage: tasks ls --json
429
+ ```
430
+
431
+ ### Grouped Subcommands
432
+
433
+ ```typescript
434
+ app.pipe(
435
+ Command.withSubcommands([
436
+ init,
437
+ { group: 'Development', commands: [build, test] },
438
+ { group: 'Deployment', commands: [deploy, rollback] }
439
+ ])
440
+ );
441
+ ```
442
+
443
+ ## Dependency Injection
444
+
445
+ ### Provide a Layer
446
+
447
+ ```typescript
448
+ const deploy = Command.make(
449
+ 'deploy',
450
+ {
451
+ env: Flag.string('env')
452
+ },
453
+ Effect.fn(function* ({ env }) {
454
+ const fs = yield* FileSystem.FileSystem;
455
+ // ...
456
+ })
457
+ ).pipe(Command.provide(FileSystemLive));
458
+
459
+ // Layer can depend on parsed input
460
+ Command.provide((config) =>
461
+ config.env === 'local' ? LocalFsLayer : RemoteFsLayer
462
+ );
463
+ ```
464
+
465
+ ### Provide a Service
466
+
467
+ ```typescript
468
+ Command.provideSync(MyService, makeMyService());
469
+ Command.provideEffect(MyService, Effect.succeed(makeMyService()));
470
+
471
+ // Can depend on parsed input
472
+ Command.provideSync(MyService, (config) => makeMyService(config.env));
473
+ ```
474
+
475
+ ## Running Commands
476
+
477
+ `Command.run` is a **pipeable combinator** that reads args from `Stdio`. The resulting effect requires `FileSystem`, `Path`, `Terminal`, `Stdio`, and `ChildProcessSpawner`; provide platform services and execute with the runtime:
478
+
479
+ ```typescript
480
+ import { NodeRuntime, NodeServices } from '@effect/platform-node';
481
+ import { Effect } from 'effect';
482
+ import { Command, Flag } from 'effect/unstable/cli';
483
+
484
+ const myCommand = Command.make(
485
+ 'myapp',
486
+ {
487
+ name: Flag.string('name')
488
+ },
489
+ Effect.fn(function* ({ name }) {
490
+ yield* Console.log(`Hello, ${name}!`);
491
+ })
492
+ );
493
+
494
+ // Entry point pattern
495
+ myCommand.pipe(
496
+ Command.run({ version: '1.0.0' }),
497
+ Effect.provide(NodeServices.layer),
498
+ NodeRuntime.runMain
499
+ );
500
+ ```
501
+
502
+ Auto-generates `--help` and `--version` flags.
503
+
504
+ Built-in/global flags (`--help`, `--version`, `--completions`, `--log-level`, plus custom globals from `Command.withGlobalFlags`) are parsed for the active command path. A local flag on the selected command can intentionally reuse/override a global flag name or alias. Shared parent flags from `Command.withSharedFlags` remain command context and may be accepted before or after a subcommand.
505
+
506
+ ### Testing with Explicit Args
507
+
508
+ Use `Command.runWith` to pass args directly (useful in tests):
509
+
510
+ ```typescript
511
+ const run = Command.runWith(myCommand, { version: '1.0.0' });
512
+ // run(["--name", "Alice"]) => Effect<void, ...>
513
+ ```
514
+
515
+ ## Key Patterns
516
+
517
+ 1. **`Argument` = positional, `Flag` = named** — No `Argument.boolean`; use `Flag.boolean` for toggles, and add `Flag.withDefault(false)` when omission should mean `false`
518
+ 2. **Handlers use `Effect.fn`** — `Effect.fn(function*({ ...config }) { ... })`
519
+ 3. **Parent access via yield** — `const root = yield* parentCommand` inside subcommand handlers
520
+ 4. **Shared flags** — `Command.withSharedFlags` on parent; only flags allowed (no arguments)
521
+ 5. **Pipeable `Command.run`** — `command.pipe(Command.run({version}), Effect.provide(NodeServices.layer), NodeRuntime.runMain)`
522
+ 6. **Platform services required** — `Command.run` requires `FileSystem`, `Path`, `Terminal`, `Stdio`, and `ChildProcessSpawner`; provide via `NodeServices.layer` / `BunServices.layer`
523
+ 7. **All combinators are dual** — Work both as `pipe(Flag.withAlias("v"))` and `Flag.withAlias(flag, "v")`