opencode-effect-enforcer 0.2.4 → 0.2.6
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.
- package/README.md +42 -140
- package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
- package/docs/effect-4.0.0-rc.116.md +102 -0
- package/guidance/effect-first-development.md +23 -14
- package/guidance/progressive-disclosure-guidance.md +3 -3
- package/package.json +3 -3
- package/patterns/avoid-direct-tag-checks.md +1 -1
- package/patterns/avoid-process-env.md +4 -4
- package/patterns/context-tag-extends.md +4 -4
- package/patterns/prefer-redacted-config.md +10 -10
- package/patterns/require-effect-concurrency.md +1 -1
- package/skills/effect-ai-chat/SKILL.md +2 -2
- package/skills/effect-ai-language-model/SKILL.md +36 -4
- package/skills/effect-ai-prompt/SKILL.md +1 -1
- package/skills/effect-ai-provider/SKILL.md +23 -12
- package/skills/effect-ai-tool/SKILL.md +13 -0
- package/skills/effect-atom-rpc/SKILL.md +7 -1
- package/skills/effect-atom-state/SKILL.md +8 -2
- package/skills/effect-cache/SKILL.md +10 -1
- package/skills/effect-cli/SKILL.md +105 -94
- package/skills/effect-command-executor/SKILL.md +7 -1
- package/skills/effect-config/SKILL.md +67 -44
- package/skills/effect-domain-modeling/SKILL.md +3 -3
- package/skills/effect-error-handling/SKILL.md +2 -2
- package/skills/effect-fiber/SKILL.md +2 -2
- package/skills/effect-filesystem/SKILL.md +34 -4
- package/skills/effect-http-api/SKILL.md +17 -2
- package/skills/effect-http-client/SKILL.md +11 -2
- package/skills/effect-http-server/SKILL.md +28 -11
- package/skills/effect-layer-design/SKILL.md +6 -2
- package/skills/effect-mcp-server/SKILL.md +21 -4
- package/skills/effect-observability/SKILL.md +2 -2
- package/skills/effect-optics/SKILL.md +1 -1
- package/skills/effect-parallelization/SKILL.md +2 -2
- package/skills/effect-pattern-matching/SKILL.md +1 -1
- package/skills/effect-platform-abstraction/SKILL.md +3 -3
- package/skills/effect-rpc-api/SKILL.md +3 -3
- package/skills/effect-rpc-client/SKILL.md +14 -13
- package/skills/effect-rpc-cluster/SKILL.md +15 -12
- package/skills/effect-rpc-server/SKILL.md +11 -13
- package/skills/effect-scheduling/SKILL.md +7 -0
- package/skills/effect-schema-composition/SKILL.md +12 -4
- package/skills/effect-schema-v4/SKILL.md +75 -20
- package/skills/effect-scope/SKILL.md +4 -4
- package/skills/effect-socket/SKILL.md +161 -658
- package/skills/effect-sql/SKILL.md +50 -12
- package/skills/effect-stream/SKILL.md +19 -24
- package/skills/effect-testing/SKILL.md +35 -22
- package/skills/effect-workflow/SKILL.md +13 -2
- package/src/enforcer.ts +1 -1
- package/src/index.ts +1 -1
- package/src/skills.ts +4 -4
|
@@ -7,7 +7,7 @@ description: Implement reactive state management with Effect Atom for React appl
|
|
|
7
7
|
|
|
8
8
|
Effect Atom is a reactive state management library for Effect that seamlessly integrates with React.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
`@effect/atom-react` supports React `>=19.0.0 <20.0.0` (the peer range
|
|
11
11
|
was relaxed). This does not add React 18 support. Keep the adapter aligned with
|
|
12
12
|
the Effect release; core atoms still live in `effect/unstable/reactivity`, and
|
|
13
13
|
React bindings live in `@effect/atom-react` (`packages/atom/react` upstream).
|
|
@@ -27,6 +27,12 @@ Reference this for:
|
|
|
27
27
|
|
|
28
28
|
### Atoms as References
|
|
29
29
|
|
|
30
|
+
`Reactivity.Reactivity` is the branded service interface. Custom implementations
|
|
31
|
+
include its exported `[TypeId]: TypeId`; prefer the supplied constructor/layer.
|
|
32
|
+
Registry dehydration skips unencodable values while preserving other atoms.
|
|
33
|
+
`Atom.withFallback` forwards writes to the primary atom. Keep encoding contracts
|
|
34
|
+
explicit for state that must survive SSR dehydration.
|
|
35
|
+
|
|
30
36
|
Atoms work **by reference** - they are stable containers for reactive state:
|
|
31
37
|
|
|
32
38
|
```typescript
|
|
@@ -301,7 +307,7 @@ export const notifications = Atom.make(
|
|
|
301
307
|
Stream.fromEventListener(window, 'notification').pipe(
|
|
302
308
|
Stream.map(parseNotification),
|
|
303
309
|
Stream.filter(isValid),
|
|
304
|
-
Stream.scan([], (acc, n) => [...acc, n].slice(-10))
|
|
310
|
+
Stream.scan(() => [], (acc, n) => [...acc, n].slice(-10))
|
|
305
311
|
)
|
|
306
312
|
);
|
|
307
313
|
```
|
|
@@ -7,6 +7,15 @@ You are an Effect TypeScript expert specializing in in-memory caching with `Cach
|
|
|
7
7
|
|
|
8
8
|
## Effect Source Reference
|
|
9
9
|
|
|
10
|
+
`Effect.cachedWithTTL` accepts either a fixed Duration input or an Exit-dependent
|
|
11
|
+
TTL function, so success and failure retention can differ. Allocate the cache
|
|
12
|
+
once in its owning layer. A zero TTL is explicit, not omission.
|
|
13
|
+
For `LayerMap.Service({ preload: true, ... })`, preserve acquisition errors on
|
|
14
|
+
the yielded service and `get` / `contextEffect` / `contextEffectOption`: an entry
|
|
15
|
+
can fail when reacquired after successful preloading. Invalidating an active
|
|
16
|
+
RcMap/LayerMap entry releases it after its last borrower closes; a replacement
|
|
17
|
+
entry remains independently owned.
|
|
18
|
+
|
|
10
19
|
The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
|
|
11
20
|
Browse and read files there directly to look up APIs, types, and implementations.
|
|
12
21
|
|
|
@@ -363,7 +372,7 @@ The full method surface (`get`, `getOption`, `getSuccess`, `set`, `has`, `invali
|
|
|
363
372
|
|
|
364
373
|
## 9. Services in Lookups (requireServicesAt)
|
|
365
374
|
|
|
366
|
-
### Retain an existing keyed resource
|
|
375
|
+
### Retain an existing keyed resource
|
|
367
376
|
|
|
368
377
|
`RcMap.getOption(map, key)` atomically retains a cached entry for the caller's
|
|
369
378
|
`Scope` before awaiting it. It returns `Option.none()` if the entry is missing or
|
|
@@ -7,6 +7,17 @@ description: Build type-safe CLI applications using Effect CLI module for argume
|
|
|
7
7
|
|
|
8
8
|
Build type-safe command-line applications with typed arguments, flags, subcommands, and dependency injection.
|
|
9
9
|
|
|
10
|
+
Scalar/control constructors are PascalCase; combinators and factories
|
|
11
|
+
such as `Command.make`, `Prompt.succeed`, and `Flag.withDefault` retain their names.
|
|
12
|
+
`Primitive.Choice` differs from `Param` / `Flag` / `Argument.Literals`.
|
|
13
|
+
Sentinels are `Never`; numeric constructors are `Int` / `Finite`, while prompts
|
|
14
|
+
use `Prompt.Int` / `Prompt.Number`. Global flags use `GlobalFlag.Action` / `Setting`.
|
|
15
|
+
Public primitive/completion tags are `Int`, `Finite`, and (for primitives) `Never`.
|
|
16
|
+
|
|
17
|
+
`Prompt.Select` / `MultiSelect` may omit `message`; `AutoComplete` requires it.
|
|
18
|
+
`KeyValuePair` preserves `=` inside values. YAML config parsing rejects plain
|
|
19
|
+
scalars containing colon-whitespace or a trailing colon: quote those values.
|
|
20
|
+
|
|
10
21
|
## Import Pattern
|
|
11
22
|
|
|
12
23
|
```typescript
|
|
@@ -21,33 +32,33 @@ import { NodeRuntime, NodeServices } from '@effect/platform-node';
|
|
|
21
32
|
|
|
22
33
|
## Positional Arguments (Argument)
|
|
23
34
|
|
|
24
|
-
Positional arguments are parsed in order. `
|
|
35
|
+
Positional arguments are parsed in order. Use `Flag.Boolean` for toggles or `Argument.Literals("name", ["true", "false"])` for positional boolean spellings.
|
|
25
36
|
|
|
26
37
|
### Constructors
|
|
27
38
|
|
|
28
39
|
```typescript
|
|
29
40
|
import { Argument } from 'effect/unstable/cli';
|
|
30
41
|
|
|
31
|
-
Argument.
|
|
32
|
-
Argument.
|
|
33
|
-
Argument.
|
|
34
|
-
Argument.
|
|
35
|
-
Argument.
|
|
36
|
-
Argument.
|
|
37
|
-
Argument.
|
|
38
|
-
Argument.
|
|
39
|
-
Argument.
|
|
40
|
-
Argument.
|
|
41
|
-
Argument.
|
|
42
|
+
Argument.String('name'); // string
|
|
43
|
+
Argument.Int('count'); // number (integer)
|
|
44
|
+
Argument.Finite('ratio'); // finite number
|
|
45
|
+
Argument.Date('deadline'); // Date
|
|
46
|
+
Argument.File('input'); // file path (string)
|
|
47
|
+
Argument.File('input', { mustExist: true }); // file path that must exist
|
|
48
|
+
Argument.Directory('dir'); // directory path (string)
|
|
49
|
+
Argument.Directory('dir', { mustExist: true }); // directory that must exist
|
|
50
|
+
Argument.Path('target'); // any path (string)
|
|
51
|
+
Argument.Literals('env', ['dev', 'staging', 'prod']); // constrained string union
|
|
52
|
+
Argument.ChoiceWithValue('level', [
|
|
42
53
|
// choice with mapped values
|
|
43
54
|
['debug', 0],
|
|
44
55
|
['info', 1],
|
|
45
56
|
['error', 3]
|
|
46
57
|
]);
|
|
47
|
-
Argument.
|
|
48
|
-
Argument.
|
|
49
|
-
Argument.
|
|
50
|
-
Argument.
|
|
58
|
+
Argument.Redacted('secret'); // Redacted<string>
|
|
59
|
+
Argument.FileText('config'); // reads file content as string
|
|
60
|
+
Argument.FileParse('config'); // reads and parses file (auto-detects format)
|
|
61
|
+
Argument.FileSchema('config', MySchema); // reads and validates file via Schema
|
|
51
62
|
```
|
|
52
63
|
|
|
53
64
|
### Combinators
|
|
@@ -56,49 +67,49 @@ Argument.fileSchema('config', MySchema); // reads and validates file via Schema
|
|
|
56
67
|
import { Argument } from 'effect/unstable/cli';
|
|
57
68
|
|
|
58
69
|
// Description for help text
|
|
59
|
-
Argument.
|
|
70
|
+
Argument.String('file').pipe(Argument.withDescription('Input file'));
|
|
60
71
|
|
|
61
72
|
// Default value
|
|
62
|
-
Argument.
|
|
73
|
+
Argument.Int('port').pipe(Argument.withDefault(8080));
|
|
63
74
|
|
|
64
75
|
// Optional (returns Option<T>)
|
|
65
|
-
Argument.
|
|
76
|
+
Argument.String('config').pipe(Argument.optional);
|
|
66
77
|
|
|
67
78
|
// Variadic (returns ReadonlyArray<T>)
|
|
68
|
-
Argument.
|
|
69
|
-
Argument.
|
|
70
|
-
Argument.
|
|
79
|
+
Argument.String('files').pipe(Argument.variadic);
|
|
80
|
+
Argument.String('files').pipe(Argument.variadic({ min: 1 }));
|
|
81
|
+
Argument.String('files').pipe(Argument.variadic({ min: 1, max: 5 }));
|
|
71
82
|
|
|
72
83
|
// Direct variadic form is also supported
|
|
73
|
-
Argument.variadic(Argument.
|
|
74
|
-
Argument.variadic(Argument.
|
|
84
|
+
Argument.variadic(Argument.String('files'));
|
|
85
|
+
Argument.variadic(Argument.String('files'), { min: 1 });
|
|
75
86
|
|
|
76
87
|
// Cardinality shortcuts
|
|
77
|
-
Argument.
|
|
78
|
-
Argument.
|
|
79
|
-
Argument.
|
|
88
|
+
Argument.String('files').pipe(Argument.atLeast(1));
|
|
89
|
+
Argument.String('files').pipe(Argument.atMost(5));
|
|
90
|
+
Argument.String('files').pipe(Argument.between(1, 5));
|
|
80
91
|
|
|
81
92
|
// Transform
|
|
82
|
-
Argument.
|
|
93
|
+
Argument.Int('port').pipe(Argument.map((p) => `http://localhost:${p}`));
|
|
83
94
|
|
|
84
95
|
// Validate with Schema
|
|
85
|
-
Argument.
|
|
96
|
+
Argument.String('input').pipe(Argument.withSchema(Schema.NonEmptyString));
|
|
86
97
|
|
|
87
98
|
// Fallback from env config
|
|
88
|
-
Argument.
|
|
89
|
-
Argument.withFallbackConfig(Config.
|
|
99
|
+
Argument.String('repo').pipe(
|
|
100
|
+
Argument.withFallbackConfig(Config.String('REPOSITORY'))
|
|
90
101
|
);
|
|
91
102
|
|
|
92
103
|
// Fallback interactive prompt
|
|
93
|
-
Argument.
|
|
94
|
-
Argument.withFallbackPrompt(Prompt.
|
|
104
|
+
Argument.String('name').pipe(
|
|
105
|
+
Argument.withFallbackPrompt(Prompt.String({ message: 'Name' }))
|
|
95
106
|
);
|
|
96
107
|
|
|
97
108
|
// Custom metavar for help text
|
|
98
|
-
Argument.
|
|
109
|
+
Argument.Int('port').pipe(Argument.withMetavar('PORT'));
|
|
99
110
|
|
|
100
111
|
// Filter with error message
|
|
101
|
-
Argument.
|
|
112
|
+
Argument.Int('count').pipe(
|
|
102
113
|
Argument.filter(
|
|
103
114
|
(n) => n > 0,
|
|
104
115
|
(n) => `Expected positive, got ${n}`
|
|
@@ -115,27 +126,27 @@ Flags are named options with `--name` or `-alias` syntax.
|
|
|
115
126
|
```typescript
|
|
116
127
|
import { Flag } from 'effect/unstable/cli';
|
|
117
128
|
|
|
118
|
-
Flag.
|
|
119
|
-
Flag.
|
|
120
|
-
Flag.
|
|
121
|
-
Flag.
|
|
122
|
-
Flag.
|
|
123
|
-
Flag.
|
|
124
|
-
Flag.
|
|
125
|
-
Flag.
|
|
126
|
-
Flag.
|
|
127
|
-
Flag.
|
|
128
|
-
Flag.
|
|
129
|
+
Flag.Boolean('verbose'); // required: --verbose / --no-verbose; omission fails
|
|
130
|
+
Flag.String('config'); // --config value
|
|
131
|
+
Flag.Int('port'); // --port 8080
|
|
132
|
+
Flag.Finite('rate'); // --rate 3.14
|
|
133
|
+
Flag.Date('since'); // --since 2024-01-01
|
|
134
|
+
Flag.File('input'); // --input file.txt
|
|
135
|
+
Flag.File('input', { mustExist: true }); // file must exist
|
|
136
|
+
Flag.Directory('output'); // --output ./dist
|
|
137
|
+
Flag.Path('config-path'); // --config-path /etc/app
|
|
138
|
+
Flag.Literals('env', ['dev', 'staging', 'prod']); // --env dev
|
|
139
|
+
Flag.ChoiceWithValue('log-level', [
|
|
129
140
|
// choice with mapped values
|
|
130
141
|
['debug', 'Debug' as const],
|
|
131
142
|
['info', 'Info' as const],
|
|
132
143
|
['error', 'Error' as const]
|
|
133
144
|
]);
|
|
134
|
-
Flag.
|
|
135
|
-
Flag.
|
|
136
|
-
Flag.
|
|
137
|
-
Flag.
|
|
138
|
-
Flag.
|
|
145
|
+
Flag.Redacted('password'); // Redacted<string>
|
|
146
|
+
Flag.FileText('config-file'); // reads file content
|
|
147
|
+
Flag.FileParse('config'); // reads and parses file (auto-detects format)
|
|
148
|
+
Flag.FileSchema('config', MySchema); // reads and validates via Schema
|
|
149
|
+
Flag.KeyValuePair('env'); // --env FOO=bar → Record<string, string>
|
|
139
150
|
```
|
|
140
151
|
|
|
141
152
|
### Combinators
|
|
@@ -144,42 +155,42 @@ Flag.keyValuePair('env'); // --env FOO=bar → Record<string, string>
|
|
|
144
155
|
import { Flag } from 'effect/unstable/cli';
|
|
145
156
|
|
|
146
157
|
// Alias
|
|
147
|
-
Flag.
|
|
158
|
+
Flag.Boolean('verbose').pipe(
|
|
148
159
|
Flag.withAlias('v'),
|
|
149
160
|
Flag.withDefault(false)
|
|
150
161
|
); // switch semantics: omission is false
|
|
151
162
|
|
|
152
163
|
// Hidden from help, completions, and typo suggestions, but still parsed
|
|
153
|
-
Flag.
|
|
164
|
+
Flag.Boolean('experimental-foo').pipe(
|
|
154
165
|
Flag.withHidden,
|
|
155
166
|
Flag.withDefault(false)
|
|
156
167
|
);
|
|
157
168
|
|
|
158
169
|
// Description
|
|
159
|
-
Flag.
|
|
170
|
+
Flag.String('config').pipe(Flag.withDescription('Path to config file'));
|
|
160
171
|
|
|
161
172
|
// Default value (makes flag optional with fallback)
|
|
162
|
-
Flag.
|
|
173
|
+
Flag.Int('port').pipe(Flag.withDefault(3000));
|
|
163
174
|
|
|
164
175
|
// Optional (returns Option<T>)
|
|
165
|
-
Flag.
|
|
176
|
+
Flag.String('token').pipe(Flag.optional);
|
|
166
177
|
|
|
167
178
|
// Custom metavar for help
|
|
168
|
-
Flag.
|
|
179
|
+
Flag.String('db-url').pipe(Flag.withMetavar('URL')); // --db-url URL
|
|
169
180
|
|
|
170
181
|
// Repetition
|
|
171
|
-
Flag.
|
|
172
|
-
Flag.
|
|
173
|
-
Flag.
|
|
182
|
+
Flag.String('tag').pipe(Flag.atLeast(1)); // --tag a --tag b
|
|
183
|
+
Flag.String('warning').pipe(Flag.atMost(3));
|
|
184
|
+
Flag.String('host').pipe(Flag.between(1, 3));
|
|
174
185
|
|
|
175
186
|
// Transform
|
|
176
|
-
Flag.
|
|
187
|
+
Flag.Int('port').pipe(Flag.map((p) => `http://localhost:${p}`));
|
|
177
188
|
|
|
178
189
|
// Validate with Schema
|
|
179
|
-
Flag.
|
|
190
|
+
Flag.String('email').pipe(Flag.withSchema(Email));
|
|
180
191
|
|
|
181
192
|
// Filter
|
|
182
|
-
Flag.
|
|
193
|
+
Flag.Int('port').pipe(
|
|
183
194
|
Flag.filter(
|
|
184
195
|
(p) => p >= 1 && p <= 65535,
|
|
185
196
|
(p) => `Port ${p} out of range`
|
|
@@ -187,13 +198,13 @@ Flag.integer('port').pipe(
|
|
|
187
198
|
);
|
|
188
199
|
|
|
189
200
|
// Fallback from env config
|
|
190
|
-
Flag.
|
|
191
|
-
Flag.withFallbackConfig(Config.
|
|
201
|
+
Flag.Boolean('verbose').pipe(
|
|
202
|
+
Flag.withFallbackConfig(Config.Boolean('VERBOSE'))
|
|
192
203
|
);
|
|
193
204
|
|
|
194
205
|
// Fallback interactive prompt
|
|
195
|
-
Flag.
|
|
196
|
-
Flag.withFallbackPrompt(Prompt.
|
|
206
|
+
Flag.String('name').pipe(
|
|
207
|
+
Flag.withFallbackPrompt(Prompt.String({ message: 'Name' }))
|
|
197
208
|
);
|
|
198
209
|
```
|
|
199
210
|
|
|
@@ -208,11 +219,11 @@ Bare boolean flags are required. `--verbose` produces `true`, `--no-verbose` pro
|
|
|
208
219
|
import { Effect } from 'effect';
|
|
209
220
|
import { Prompt } from 'effect/unstable/cli';
|
|
210
221
|
|
|
211
|
-
Prompt.
|
|
212
|
-
Prompt.
|
|
222
|
+
Prompt.Int({ message: 'Count', default: 42 });
|
|
223
|
+
Prompt.File({ message: 'Pick file', default: '/workspace/config.json' });
|
|
213
224
|
|
|
214
225
|
// A local override merges over the context theme.
|
|
215
|
-
const name = Prompt.
|
|
226
|
+
const name = Prompt.String({ message: 'Name', theme: { prefix: '>' } });
|
|
216
227
|
|
|
217
228
|
// Provide once to theme all prompts in a command or application.
|
|
218
229
|
const themed = name.pipe(
|
|
@@ -220,9 +231,9 @@ const themed = name.pipe(
|
|
|
220
231
|
);
|
|
221
232
|
```
|
|
222
233
|
|
|
223
|
-
Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.
|
|
234
|
+
Integer prompt defaults are editable and Enter submits the default if unchanged. `Prompt.File` resolves/selects the default as the initial path.
|
|
224
235
|
|
|
225
|
-
|
|
236
|
+
Per-prompt appearance is configured with `theme?: Partial<Prompt.Theme>`.
|
|
226
237
|
`Prompt.Theme` is a context reference with platform defaults; `Prompt.makeTheme`
|
|
227
238
|
builds a complete theme. Local fields override the context theme. Theme fields
|
|
228
239
|
include prompt symbols, `passwordMask`, and ANSI color values. The default
|
|
@@ -249,19 +260,19 @@ const version = Command.make('version');
|
|
|
249
260
|
|
|
250
261
|
// Command with config (no handler yet)
|
|
251
262
|
const deploy = Command.make('deploy', {
|
|
252
|
-
env: Flag.
|
|
253
|
-
force: Flag.
|
|
254
|
-
files: Argument.
|
|
263
|
+
env: Flag.String('env'),
|
|
264
|
+
force: Flag.Boolean('force').pipe(Flag.withDefault(false)),
|
|
265
|
+
files: Argument.String('files').pipe(Argument.variadic)
|
|
255
266
|
});
|
|
256
267
|
|
|
257
268
|
// Command with config and inline handler
|
|
258
269
|
const greet = Command.make(
|
|
259
270
|
'greet',
|
|
260
271
|
{
|
|
261
|
-
name: Argument.
|
|
272
|
+
name: Argument.String('name').pipe(
|
|
262
273
|
Argument.withDescription('Person to greet')
|
|
263
274
|
),
|
|
264
|
-
times: Flag.
|
|
275
|
+
times: Flag.Int('times').pipe(Flag.withDefault(1))
|
|
265
276
|
},
|
|
266
277
|
Effect.fn(function* ({ name, times }) {
|
|
267
278
|
for (let i = 0; i < times; i++) {
|
|
@@ -279,8 +290,8 @@ Handlers use `Effect.fn` with a generator that destructures the config:
|
|
|
279
290
|
const cmd = Command.make(
|
|
280
291
|
'deploy',
|
|
281
292
|
{
|
|
282
|
-
env: Flag.
|
|
283
|
-
dryRun: Flag.
|
|
293
|
+
env: Flag.Literals('env', ['dev', 'staging', 'prod']),
|
|
294
|
+
dryRun: Flag.Boolean('dry-run').pipe(Flag.withDefault(false))
|
|
284
295
|
},
|
|
285
296
|
Effect.fn(function* ({ env, dryRun }) {
|
|
286
297
|
if (dryRun) {
|
|
@@ -296,7 +307,7 @@ Alternatively, add a handler later with `Command.withHandler`:
|
|
|
296
307
|
|
|
297
308
|
```typescript
|
|
298
309
|
const cmd = Command.make('greet', {
|
|
299
|
-
name: Flag.
|
|
310
|
+
name: Flag.String('name')
|
|
300
311
|
}).pipe(Command.withHandler(({ name }) => Console.log(`Hello, ${name}!`)));
|
|
301
312
|
```
|
|
302
313
|
|
|
@@ -326,12 +337,12 @@ Config objects can be nested for organization:
|
|
|
326
337
|
|
|
327
338
|
```typescript
|
|
328
339
|
const deploy = Command.make('deploy', {
|
|
329
|
-
environment: Flag.
|
|
340
|
+
environment: Flag.String('env'),
|
|
330
341
|
server: {
|
|
331
|
-
host: Flag.
|
|
332
|
-
port: Flag.
|
|
342
|
+
host: Flag.String('host').pipe(Flag.withDefault('localhost')),
|
|
343
|
+
port: Flag.Int('port').pipe(Flag.withDefault(3000))
|
|
333
344
|
},
|
|
334
|
-
files: Argument.
|
|
345
|
+
files: Argument.String('files').pipe(Argument.variadic)
|
|
335
346
|
});
|
|
336
347
|
// Handler receives: { environment: string, server: { host: string, port: number }, files: ReadonlyArray<string> }
|
|
337
348
|
```
|
|
@@ -354,7 +365,7 @@ const init = Command.make(
|
|
|
354
365
|
const build = Command.make(
|
|
355
366
|
'build',
|
|
356
367
|
{
|
|
357
|
-
target: Flag.
|
|
368
|
+
target: Flag.Literals('target', ['web', 'node'])
|
|
358
369
|
},
|
|
359
370
|
Effect.fn(function* ({ target }) {
|
|
360
371
|
yield* Console.log(`Building for ${target}`);
|
|
@@ -377,11 +388,11 @@ Use `Command.withSharedFlags` to define flags on a parent that are available to
|
|
|
377
388
|
```typescript
|
|
378
389
|
const tasks = Command.make('tasks').pipe(
|
|
379
390
|
Command.withSharedFlags({
|
|
380
|
-
workspace: Flag.
|
|
391
|
+
workspace: Flag.String('workspace').pipe(
|
|
381
392
|
Flag.withAlias('w'),
|
|
382
393
|
Flag.withDefault('personal')
|
|
383
394
|
),
|
|
384
|
-
verbose: Flag.
|
|
395
|
+
verbose: Flag.Boolean('verbose').pipe(
|
|
385
396
|
Flag.withAlias('v'),
|
|
386
397
|
Flag.withDefault(false)
|
|
387
398
|
)
|
|
@@ -391,8 +402,8 @@ const tasks = Command.make('tasks').pipe(
|
|
|
391
402
|
const create = Command.make(
|
|
392
403
|
'create',
|
|
393
404
|
{
|
|
394
|
-
title: Argument.
|
|
395
|
-
priority: Flag.
|
|
405
|
+
title: Argument.String('title'),
|
|
406
|
+
priority: Flag.Literals('priority', ['low', 'normal', 'high']).pipe(
|
|
396
407
|
Flag.withDefault('normal')
|
|
397
408
|
)
|
|
398
409
|
},
|
|
@@ -419,10 +430,10 @@ const create = Command.make(
|
|
|
419
430
|
const list = Command.make(
|
|
420
431
|
'list',
|
|
421
432
|
{
|
|
422
|
-
status: Flag.
|
|
433
|
+
status: Flag.Literals('status', ['open', 'done', 'all']).pipe(
|
|
423
434
|
Flag.withDefault('open')
|
|
424
435
|
),
|
|
425
|
-
json: Flag.
|
|
436
|
+
json: Flag.Boolean('json').pipe(Flag.withDefault(false))
|
|
426
437
|
},
|
|
427
438
|
Effect.fn(function* ({ status, json }) {
|
|
428
439
|
const root = yield* tasks;
|
|
@@ -467,7 +478,7 @@ app.pipe(
|
|
|
467
478
|
const deploy = Command.make(
|
|
468
479
|
'deploy',
|
|
469
480
|
{
|
|
470
|
-
env: Flag.
|
|
481
|
+
env: Flag.String('env')
|
|
471
482
|
},
|
|
472
483
|
Effect.fn(function* ({ env }) {
|
|
473
484
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -503,7 +514,7 @@ import { Command, Flag } from 'effect/unstable/cli';
|
|
|
503
514
|
const myCommand = Command.make(
|
|
504
515
|
'myapp',
|
|
505
516
|
{
|
|
506
|
-
name: Flag.
|
|
517
|
+
name: Flag.String('name')
|
|
507
518
|
},
|
|
508
519
|
Effect.fn(function* ({ name }) {
|
|
509
520
|
yield* Console.log(`Hello, ${name}!`);
|
|
@@ -533,7 +544,7 @@ const run = Command.runWith(myCommand, { version: '1.0.0' });
|
|
|
533
544
|
|
|
534
545
|
## Key Patterns
|
|
535
546
|
|
|
536
|
-
1. **`Argument` = positional, `Flag` = named** —
|
|
547
|
+
1. **`Argument` = positional, `Flag` = named** — Use `Flag.Boolean` for toggles, and add `Flag.withDefault(false)` when omission should mean `false`
|
|
537
548
|
2. **Handlers use `Effect.fn`** — `Effect.fn(function*({ ...config }) { ... })`
|
|
538
549
|
3. **Parent access via yield** — `const root = yield* parentCommand` inside subcommand handlers
|
|
539
550
|
4. **Shared flags** — `Command.withSharedFlags` on parent; only flags allowed (no arguments)
|
|
@@ -7,6 +7,12 @@ description: Spawn and manage child processes using Effect's ChildProcess module
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
+
Node process-group cleanup waits for the leader and descendants. Without
|
|
11
|
+
`forceKillAfter`, cleanup waits up to one second without escalating. With it,
|
|
12
|
+
the group receives SIGKILL at the deadline, then a final bounded wait. Native
|
|
13
|
+
timers drive escalation even under TestClock. `exitCode` / `isRunning` describe
|
|
14
|
+
the leader, not all descendants; do not use virtual time alone to prove OS cleanup.
|
|
15
|
+
|
|
10
16
|
The `ChildProcess` module provides type-safe, composable process execution with automatic resource cleanup via `Scope`. Commands are AST values — built first with `make` and `pipeTo`, then executed via the `ChildProcessSpawner` service.
|
|
11
17
|
|
|
12
18
|
**When to use this skill:**
|
|
@@ -131,7 +137,7 @@ const cmd = ChildProcess.make('node', ['script.js'], {
|
|
|
131
137
|
|
|
132
138
|
`extendEnv` defaults to `false`. If `env` is supplied without `extendEnv: true`, it replaces the inherited child environment rather than merging with it.
|
|
133
139
|
|
|
134
|
-
The `fd3`/`fd4` names
|
|
140
|
+
The `fd3`/`fd4` names configure child-process stdio channels. Process handles expose `getInputFd(number)` and `getOutputFd(number)` for configured additional descriptors; use FileSystem's scoped handle operations for files.
|
|
135
141
|
|
|
136
142
|
### Combinators
|
|
137
143
|
|