@eigenpal/cli 0.0.1 → 0.4.1

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 (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +63 -2
  3. package/dist/cli.js +162159 -0
  4. package/dist/skill/SKILL.md +366 -0
  5. package/dist/skill/examples/hello-world/dataset/examples/sample/expected/output.json +4 -0
  6. package/dist/skill/examples/hello-world/dataset/examples/sample/input/arguments.json +3 -0
  7. package/dist/skill/examples/hello-world/evaluators.yaml +5 -0
  8. package/dist/skill/examples/hello-world/workflow.yaml +28 -0
  9. package/dist/skill/reference/cli/agent.md +7 -0
  10. package/dist/skill/reference/cli/auth.md +75 -0
  11. package/dist/skill/reference/cli/completion.md +13 -0
  12. package/dist/skill/reference/cli/init.md +63 -0
  13. package/dist/skill/reference/cli/skill.md +79 -0
  14. package/dist/skill/reference/cli/status.md +14 -0
  15. package/dist/skill/reference/cli/workflow.md +788 -0
  16. package/dist/skill/reference/dataset-format.md +251 -0
  17. package/dist/skill/reference/debugging.md +280 -0
  18. package/dist/skill/reference/evaluators.md +210 -0
  19. package/dist/skill/reference/step-exec.md +164 -0
  20. package/dist/skill/reference/step-types.md +519 -0
  21. package/dist/skill/reference/workflow-yaml.md +304 -0
  22. package/dist/templates/blank/README.md +22 -0
  23. package/dist/templates/blank/dataset/examples/sample/expected/output.json +4 -0
  24. package/dist/templates/blank/dataset/examples/sample/input/arguments.json +3 -0
  25. package/dist/templates/blank/evaluators.yaml +9 -0
  26. package/dist/templates/blank/workflow.yaml +39 -0
  27. package/dist/templates/pdf-extraction/README.md +20 -0
  28. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/README.md +5 -0
  29. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/expected/output.json +6 -0
  30. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/input/arguments.json +1 -0
  31. package/dist/templates/pdf-extraction/evaluators.yaml +5 -0
  32. package/dist/templates/pdf-extraction/workflow.yaml +38 -0
  33. package/dist/templates/text-classification/README.md +8 -0
  34. package/dist/templates/text-classification/dataset/examples/negative/expected/output.json +3 -0
  35. package/dist/templates/text-classification/dataset/examples/negative/input/arguments.json +3 -0
  36. package/dist/templates/text-classification/dataset/examples/positive/expected/output.json +3 -0
  37. package/dist/templates/text-classification/dataset/examples/positive/input/arguments.json +3 -0
  38. package/dist/templates/text-classification/evaluators.yaml +6 -0
  39. package/dist/templates/text-classification/workflow.yaml +35 -0
  40. package/package.json +35 -15
  41. package/index.js +0 -4
@@ -0,0 +1,519 @@
1
+ # Step types
2
+
3
+ The catalog is generated from `STEP_SCHEMAS` in
4
+ `@eigenpal/types/src/workflow/step-configs.ts`. **Don't memorize fields
5
+ — introspect.** The CLI reads the live schema:
6
+
7
+ ```bash
8
+ # List every step type with a one-line description
9
+ eigenpal workflow step-type list
10
+
11
+ # Filter by keyword
12
+ eigenpal workflow step-type list --search extract
13
+
14
+ # Full JSON Schema for one type's `config` + `output` + behavioral docs
15
+ eigenpal workflow step-type get ai.extract
16
+ ```
17
+
18
+ The output of `step-type get` is the **authoritative source** for which
19
+ fields each step takes, what shapes they accept, and what behavior they
20
+ implement at runtime. Server-side validation errors point you here.
21
+
22
+ ## Categories (high-level map)
23
+
24
+ - `ai.*` — model-backed processing (parse, extract). Cost + latency depend on the model.
25
+ - `transform.*` — deterministic data transforms (set, remove, combine, split, merge,
26
+ script, template, pdf-embed, xlsx-to-json). WASM sandboxed where applicable.
27
+ - `action.*` — external side effects (HTTP, invoke another workflow, website reader).
28
+ - `control.*` — flow control (if, foreach, parallel, parallel_map, wait, approval, block).
29
+
30
+ The full per-type catalog with field tables is auto-generated below from
31
+ `STEP_SCHEMAS`. The high-level map above tells you which family you want;
32
+ the catalog tells you what fields it takes.
33
+
34
+ ## When to reach for what
35
+
36
+ | Use case | Step |
37
+ | ----------------------------- | ------------------------------------------------- |
38
+ | Read a PDF / DOCX / image | `ai.parse` |
39
+ | Pull a typed object from text | `ai.extract` with `config.schema` |
40
+ | Sum / filter / regex | `transform.script` (NOT Liquid) |
41
+ | Render a DOCX template | `transform.template` |
42
+ | Convert XLSX to JSON | `transform.xlsx-to-json` |
43
+ | Merge or combine objects | `transform.combine` / `transform.merge` |
44
+ | Conditional execution | `if:` on the step (Liquid) or `control.if` |
45
+ | Map over an input array | `forEach:` on the step or `control.foreach` |
46
+ | Concurrent map over an array | `control.parallel_map` |
47
+ | Independent parallel branches | `control.parallel` |
48
+ | Pause for human approval | `control.approval` |
49
+ | External HTTP call | `action.http` |
50
+ | Call another workflow | `action.invoke-workflow` |
51
+ | Fetch a webpage as markdown | `action.website-reader` |
52
+
53
+ ## Liquid vs `transform.script`
54
+
55
+ | Logic shape | Use |
56
+ | -------------------------- | -------------------------------- |
57
+ | `steps.x.output.field` | Liquid `{{ ... }}` |
58
+ | `value \| default: 0` | Liquid |
59
+ | `price \| times: quantity` | Liquid |
60
+ | array `reduce` / `filter` | `transform.script` |
61
+ | multi-step calc | `transform.script` |
62
+ | conditional branches | `transform.script` or `if:` |
63
+ | date arithmetic | `transform.script` |
64
+ | regex | `transform.script` |
65
+
66
+ `transform.script` advantages over Liquid:
67
+
68
+ - Explicit `inputs:` declaration → the validator knows the data deps
69
+ - `outputSchema:` enforces the return shape at runtime
70
+ - Real JS — `Array.prototype.reduce`, `JSON.parse`, etc.
71
+ - Sandbox limits prevent infinite loops from blowing up an execution
72
+
73
+ > **Common gotcha — TDZ on shadowed input names.** Each entry in `inputs`
74
+ > becomes a top-level `const` binding inside your script. Re-declaring the
75
+ > same name in the body shadows the binding and triggers a `ReferenceError:
76
+ > Cannot access 'X' before initialization` (Temporal Dead Zone). For example:
77
+ >
78
+ > ```js
79
+ > // ✗ TDZ — `located` is the input AND the inner const
80
+ > const located = located || [];
81
+ >
82
+ > // ✓ Rename the inner variable
83
+ > const safeLocated = located ?? [];
84
+ > ```
85
+ >
86
+ > If you need a default, use `??` / `||` against the binding directly without
87
+ > redeclaring, or rename the local. The same applies to `let` redeclarations.
88
+
89
+ ## Reference / output paths
90
+
91
+ After a step runs, its output is reachable via
92
+ `{{ steps.<name>.output.<field> }}`. The exact field set per type comes
93
+ from `step-type get`'s `outputSchema`. Example for `ai.extract`:
94
+
95
+ ```bash
96
+ $ eigenpal workflow step-type get ai.extract | jq '.outputSchema'
97
+ {
98
+ "type": "object",
99
+ "properties": {
100
+ "extracted": { /* matches the user-supplied config.schema */ },
101
+ "rawResponse": { "type": "string" },
102
+ "tokensUsed": { "type": "number" }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Reference user-supplied schema fields the same way:
108
+ `{{ steps.extract.output.extracted.invoiceNumber }}`.
109
+
110
+ <!-- GENERATED:STEP_CATALOG START -->
111
+ ## Full catalog
112
+
113
+ _Generated from `STEP_SCHEMAS` in `@eigenpal/types/src/workflow/step-configs.ts`. Do not hand-edit between the GENERATED fences — run `bun run --cwd packages/cli generate:skill`._
114
+
115
+ ### AI steps — model-backed processing
116
+
117
+ #### `ai.parse` — Parse Document
118
+
119
+ Extract text from documents (PDF, DOCX, images) using OCR or vision models
120
+
121
+ **Config** (in `step.with`):
122
+
123
+ | Field | Type | Required | Default | Description |
124
+ | --- | --- | --- | --- | --- |
125
+ | `input` | string | yes | | Storage reference or template expression for the document |
126
+ | `ocrModel` | string | no | | OCR provider ID for PDF/image parsing |
127
+ | `llmModel` | string | no | | LLM provider ID for vision-based parsing |
128
+ | `maxConcurrency` | number | no | `3` | Max concurrent VLM batch requests |
129
+ | `pagesPerBatch` | number | no | `5` | Number of page images per VLM request |
130
+ | `prompt` | string | no | | Custom extraction prompt |
131
+ | `languages` | array<string> | no | | OCR language hints |
132
+ | `outputFormat` | `"plain"` \| `"markdown"` \| `"djot"` \| `"html"` | no | `"markdown"` | Format for extracted text. `markdown` (default) keeps structure and is best for LLM extraction; `plain` is unstyled text; `djot`/`html` preserve more layout. Only the native (Kreuzberg) parser respects this — OCR/VLM always emit markdown. |
133
+ | `nativeText` | boolean | no | `false` | Extract native/embedded text from PDFs without OCR/VLM. Faster and uses no credits. Falls back to OCR/VLM if the PDF has no embedded text. |
134
+
135
+ **Output:** `object`
136
+
137
+ | Field | Type | Required | Default | Description |
138
+ | --- | --- | --- | --- | --- |
139
+ | `text` | string | yes | | Extracted text content (combined from all pages) |
140
+ | `pages` | array<object> | no | | Per-page content |
141
+ | `metadata` | record<string, unknown> | no | | Document metadata |
142
+
143
+ #### `ai.extract` — Extract Data
144
+
145
+ Extract structured data from text using AI with a JSON schema
146
+
147
+ **Config** (in `step.with`):
148
+
149
+ | Field | Type | Required | Default | Description |
150
+ | --- | --- | --- | --- | --- |
151
+ | `input` | string | yes | | Text content or template expression |
152
+ | `schema` | object | yes | | JSON Schema defining the structure to extract |
153
+ | `prompt` | string | no | | Custom prompt template for extraction |
154
+ | `provider` | string | no | | Provider ID (e.g., "openai-gpt4o") |
155
+ | `model` | string | no | | Model override |
156
+ | `temperature` | number | no | `0` | Model temperature (0 = deterministic) |
157
+ | `maxInputTokens` | integer | no | | Max input tokens. Truncates input text and logs a warning when exceeded. Omit for no limit. |
158
+
159
+ **Output:** `record<string, unknown>`
160
+
161
+ > Extracted structured data matching the provided schema
162
+
163
+ ### Transform steps — deterministic data transforms
164
+
165
+ #### `transform.set` — Set Value
166
+
167
+ Set key-value pairs in the output object
168
+
169
+ **Config** (in `step.with`):
170
+
171
+ | Field | Type | Required | Default | Description |
172
+ | --- | --- | --- | --- | --- |
173
+ | `fields` | record<string, unknown> | yes | | Key-value pairs to set in output |
174
+ | `input` | record<string, unknown> | no | | Base object to extend |
175
+
176
+ **Output:** `record<string, unknown>`
177
+
178
+ > Object with all fields set
179
+
180
+ #### `transform.remove` — Remove Fields
181
+
182
+ Remove specified fields from an object
183
+
184
+ **Config** (in `step.with`):
185
+
186
+ | Field | Type | Required | Default | Description |
187
+ | --- | --- | --- | --- | --- |
188
+ | `input` | record<string, unknown> | no | | Object to remove fields from |
189
+ | `fields` | array<string> | yes | | Field names to remove |
190
+
191
+ **Output:** `record<string, unknown>`
192
+
193
+ > Object with fields removed
194
+
195
+ #### `transform.combine` — Combine Data
196
+
197
+ Merge multiple objects or concatenate arrays
198
+
199
+ **Config** (in `step.with`):
200
+
201
+ | Field | Type | Required | Default | Description |
202
+ | --- | --- | --- | --- | --- |
203
+ | `sources` | array<string> | yes | | Template expressions for sources to combine |
204
+ | `target` | string | yes | | Target path in output |
205
+ | `mode` | `"merge"` \| `"concat"` \| `"deep"` | no | `"merge"` | |
206
+
207
+ **Output:** `record<string, unknown> \| array<unknown>`
208
+
209
+ > Combined result
210
+
211
+ #### `transform.split` — Split Data
212
+
213
+ Split a string by delimiter or extract keys from an object
214
+
215
+ **Config** (in `step.with`):
216
+
217
+ | Field | Type | Required | Default | Description |
218
+ | --- | --- | --- | --- | --- |
219
+ | `input` | string \| record<string, unknown> | no | | String or object to split |
220
+ | `source` | string | no | | Template expression for source |
221
+ | `by` | string | no | `","` | Delimiter for string splitting |
222
+ | `delimiter` | string | no | | Alias for by |
223
+ | `limit` | number | no | | Max number of splits |
224
+ | `keys` | array<string> | no | | Keys to extract from object |
225
+
226
+ **Output:** `array<string> \| object`
227
+
228
+ > Split result - array or extracted/remaining object
229
+
230
+ #### `transform.merge` — Merge Inputs
231
+
232
+ Merge multiple named inputs into a single output
233
+
234
+ **Config** (in `step.with`):
235
+
236
+ | Field | Type | Required | Default | Description |
237
+ | --- | --- | --- | --- | --- |
238
+ | `preservePortNames` | boolean | no | `true` | |
239
+ | `outputKey` | string | no | | |
240
+
241
+ **Output:** `object`
242
+
243
+ | Field | Type | Required | Default | Description |
244
+ | --- | --- | --- | --- | --- |
245
+ | `items` | array<unknown> | yes | | |
246
+ | `count` | number | yes | | |
247
+ | `merged` | record<string, unknown> | no | | |
248
+
249
+ #### `transform.template` — Fill Template
250
+
251
+ Fill a DOCX template with data. Use list_templates tool to get template IDs and their placeholder schemas.
252
+
253
+ **Config** (in `step.with`):
254
+
255
+ | Field | Type | Required | Default | Description |
256
+ | --- | --- | --- | --- | --- |
257
+ | `templateId` | string | yes | | ID of the template from templates table |
258
+ | `data` | record<string, unknown> | yes | | Data object to merge into template. Each key must be explicitly defined - cannot pass a whole object as single expression |
259
+ | `outputFilename` | string | no | | Output filename - supports {{field}} syntax |
260
+ | `highlightNotFound` | boolean | no | `true` | Highlight missing variables with red-colored text in the output document |
261
+ | `notFoundText` | string | no | `"NOT FOUND"` | Text to display for missing variables when highlightNotFound is enabled |
262
+
263
+ **Output:** `object`
264
+
265
+ | Field | Type | Required | Default | Description |
266
+ | --- | --- | --- | --- | --- |
267
+ | `fileId` | string | yes | | File ID from files table |
268
+
269
+ #### `transform.pdf-embed` — Embed PDF Text
270
+
271
+ Embed OCR text layer into scanned PDFs/images to make them searchable
272
+
273
+ **Config** (in `step.with`):
274
+
275
+ | Field | Type | Required | Default | Description |
276
+ | --- | --- | --- | --- | --- |
277
+ | `input` | string | yes | | File input - template expression e.g. {{input.document}} |
278
+ | `parseResult` | string | yes | | Parse result - template expression e.g. {{steps.parse.output}} |
279
+ | `outputFilename` | string | no | | Output filename - supports {{filename}} syntax |
280
+ | `confidenceThreshold` | number | no | `0.7` | Minimum OCR confidence (0-1) to include a word |
281
+
282
+ **Output:** `object`
283
+
284
+ | Field | Type | Required | Default | Description |
285
+ | --- | --- | --- | --- | --- |
286
+ | `fileId` | string | yes | | File ID from files table |
287
+ | `pageCount` | number | yes | | Number of pages in the output PDF |
288
+ | `wordCount` | number | yes | | Number of words embedded |
289
+ | `text` | string | yes | | Extracted text from the document |
290
+
291
+ #### `transform.xlsx-to-json` — XLSX to JSON
292
+
293
+ Convert XLSX spreadsheet to JSON array of row objects for use in scripts or downstream steps
294
+
295
+ **Config** (in `step.with`):
296
+
297
+ | Field | Type | Required | Default | Description |
298
+ | --- | --- | --- | --- | --- |
299
+ | `input` | string | yes | | File input - template expression e.g. {{input.document}} resolving to fileId or file path descriptor |
300
+ | `sheet` | integer \| string | no | | Sheet to read: 0-based index or sheet name. Omit for first sheet. |
301
+ | `outputCsv` | boolean | no | `false` | If true, also write CSV to storage and include fileId in output |
302
+ | `outputFilename` | string | no | | Output CSV filename when outputCsv is true - supports LiquidJS e.g. {{filename}}.csv |
303
+
304
+ **Output:** `object`
305
+
306
+ | Field | Type | Required | Default | Description |
307
+ | --- | --- | --- | --- | --- |
308
+ | `rows` | array<record<string, unknown>> | yes | | Array of row objects (first row = headers as keys) |
309
+ | `fileId` | string | no | | File ID of stored CSV when outputCsv is true |
310
+
311
+ #### `transform.script` — Script
312
+
313
+ Execute JavaScript code in a secure sandbox. Input keys become TOP-LEVEL variables (use "items" not "inputs.items"). Must return a value.
314
+
315
+ **Config** (in `step.with`):
316
+
317
+ | Field | Type | Required | Default | Description |
318
+ | --- | --- | --- | --- | --- |
319
+ | `inputs` | record<string, string> | no | | Named inputs mapped from template expressions. IMPORTANT: Keys become TOP-LEVEL variables in code (e.g., if key is "items", use "items" not "inputs.items"). |
320
+ | `code` | string | yes | | JavaScript code. Input keys are available as top-level variables (NOT as inputs.key). Must return a value. |
321
+ | `outputSchema` | record<string, unknown> | no | | JSON Schema describing the expected return value. Used for validation and type hints. |
322
+ | `timeout` | number | no | `5000` | Max execution time in milliseconds (default: 5000) |
323
+ | `memoryLimit` | number | no | `10485760` | Max memory in bytes (default: 10MB) |
324
+
325
+ **Output:** `unknown`
326
+
327
+ > Value returned from script (validated against outputSchema if provided)
328
+
329
+ ### Action steps — external side effects
330
+
331
+ #### `action.http` — HTTP Request
332
+
333
+ Make an HTTP request to an external API
334
+
335
+ **Config** (in `step.with`):
336
+
337
+ | Field | Type | Required | Default | Description |
338
+ | --- | --- | --- | --- | --- |
339
+ | `url` | string | yes | | Request URL (supports template expressions) |
340
+ | `method` | `"GET"` \| `"POST"` \| `"PUT"` \| `"PATCH"` \| `"DELETE"` | no | `"GET"` | |
341
+ | `headers` | record<string, string> | no | | HTTP headers |
342
+ | `body` | unknown | no | | Request body (JSON or string) |
343
+ | `timeout` | number | no | `30000` | Timeout in milliseconds |
344
+ | `insecureSkipTlsVerify` | boolean | no | `false` | If true, skip TLS certificate verification (use only for read-only public endpoints with bad/expired certs) |
345
+
346
+ **Output:** `object`
347
+
348
+ | Field | Type | Required | Default | Description |
349
+ | --- | --- | --- | --- | --- |
350
+ | `status` | number | yes | | HTTP status code |
351
+ | `statusText` | string | yes | | |
352
+ | `headers` | record<string, string> | yes | | |
353
+ | `body` | unknown | yes | | Response body (parsed JSON or string) |
354
+ | `responseCharset` | string | yes | | Charset used to decode the response body (e.g. utf-8, windows-1250) |
355
+
356
+ #### `action.invoke-workflow` — Invoke Workflow
357
+
358
+ Execute another workflow and return its output
359
+
360
+ **Config** (in `step.with`):
361
+
362
+ | Field | Type | Required | Default | Description |
363
+ | --- | --- | --- | --- | --- |
364
+ | `workflowId` | string | yes | | ID of workflow to invoke |
365
+ | `input` | record<string, unknown> | no | | Input to pass to workflow |
366
+
367
+ **Output:** `record<string, unknown>`
368
+
369
+ > Output from invoked workflow
370
+
371
+ #### `action.website-reader` — Website Reader
372
+
373
+ Fetch a webpage and convert content to markdown
374
+
375
+ **Config** (in `step.with`):
376
+
377
+ | Field | Type | Required | Default | Description |
378
+ | --- | --- | --- | --- | --- |
379
+ | `url` | string | yes | | Website URL to fetch (supports template expressions) |
380
+ | `timeout` | number | no | `30000` | Timeout in milliseconds |
381
+ | `encoding` | `"auto"` \| `"utf-8"` \| `"latin1"` \| `"windows-1250"` \| `"windows-1252"` \| `"iso-8859-2"` | no | `"auto"` | Response encoding. Auto detects from Content-Type header and HTML meta tags. |
382
+
383
+ **Output:** `object`
384
+
385
+ | Field | Type | Required | Default | Description |
386
+ | --- | --- | --- | --- | --- |
387
+ | `markdown` | string | yes | | Page content converted to markdown |
388
+ | `title` | string \| null | yes | | Page title |
389
+ | `excerpt` | string \| null | yes | | Page excerpt/description |
390
+ | `byline` | string \| null | yes | | Author information |
391
+ | `siteName` | string \| null | yes | | Site name |
392
+ | `length` | number | yes | | Content length in characters |
393
+ | `url` | string | yes | | Final URL after redirects |
394
+
395
+ ### Control steps — flow control
396
+
397
+ #### `control.if` — Condition
398
+
399
+ Branch execution based on a condition expression
400
+
401
+ **Config** (at step level):
402
+
403
+ | Field | Type | Required | Default | Description |
404
+ | --- | --- | --- | --- | --- |
405
+ | `condition` | string | yes | | LiquidJS expression that evaluates to boolean |
406
+
407
+ **Output:** `object`
408
+
409
+ | Field | Type | Required | Default | Description |
410
+ | --- | --- | --- | --- | --- |
411
+ | `condition` | boolean | yes | | Evaluated condition result |
412
+ | `branch` | `"then"` \| `"else"` | yes | | Which branch was executed |
413
+ | `result` | unknown | yes | | Output from executed branch |
414
+
415
+ #### `control.foreach` — For Each
416
+
417
+ Loop over an array and execute steps for each item
418
+
419
+ **Config** (at step level):
420
+
421
+ | Field | Type | Required | Default | Description |
422
+ | --- | --- | --- | --- | --- |
423
+ | `items` | string | yes | | Expression resolving to array |
424
+ | `as` | string | yes | | Variable name for current item |
425
+ | `indexAs` | string | no | | Variable name for current index |
426
+
427
+ **Output:** `object`
428
+
429
+ | Field | Type | Required | Default | Description |
430
+ | --- | --- | --- | --- | --- |
431
+ | `items` | array<unknown> | yes | | Results from each iteration |
432
+ | `count` | number | yes | | Number of completed iterations |
433
+ | `totalIterations` | number | yes | | Total iterations |
434
+
435
+ #### `control.parallel_map` — Parallel Map
436
+
437
+ Iterate over an array with concurrent execution up to a limit
438
+
439
+ **Config** (at step level):
440
+
441
+ | Field | Type | Required | Default | Description |
442
+ | --- | --- | --- | --- | --- |
443
+ | `items` | string | yes | | Expression resolving to array |
444
+ | `as` | string | yes | | Variable name for current item |
445
+ | `indexAs` | string | no | | Variable name for current index |
446
+ | `concurrency` | integer | no | `5` | Maximum concurrent executions (1-50, default 5) |
447
+
448
+ **Output:** `object`
449
+
450
+ | Field | Type | Required | Default | Description |
451
+ | --- | --- | --- | --- | --- |
452
+ | `items` | array<unknown> | yes | | Results from each iteration (maintains original order) |
453
+ | `count` | number | yes | | Number of completed iterations |
454
+ | `totalIterations` | number | yes | | Total iterations |
455
+
456
+ #### `control.parallel` — Parallel
457
+
458
+ Execute multiple branches concurrently
459
+
460
+ **Config** (at step level):
461
+
462
+ _No fields._
463
+
464
+ **Output:** `record<string, unknown>`
465
+
466
+ > Results keyed by branch name
467
+
468
+ #### `control.wait` — Wait
469
+
470
+ Pause workflow execution for a specified duration
471
+
472
+ **Config** (at step level):
473
+
474
+ | Field | Type | Required | Default | Description |
475
+ | --- | --- | --- | --- | --- |
476
+ | `duration` | number | yes | | Duration in milliseconds |
477
+
478
+ **Output:** `object`
479
+
480
+ | Field | Type | Required | Default | Description |
481
+ | --- | --- | --- | --- | --- |
482
+ | `waited` | number | yes | | Actual milliseconds waited |
483
+
484
+ #### `control.approval` — Approval
485
+
486
+ Pause workflow for human approval before continuing
487
+
488
+ **Config** (at step level):
489
+
490
+ | Field | Type | Required | Default | Description |
491
+ | --- | --- | --- | --- | --- |
492
+ | `message` | string | no | | Message to display for approval |
493
+ | `timeoutMinutes` | number | no | `1440` | Auto-reject after timeout |
494
+ | `notifyEmail` | string | no | | Email to notify |
495
+
496
+ **Output:** `object`
497
+
498
+ | Field | Type | Required | Default | Description |
499
+ | --- | --- | --- | --- | --- |
500
+ | `approved` | boolean | yes | | Whether the request was approved |
501
+ | `approvedBy` | string | no | | Who approved |
502
+ | `approvedAt` | string | no | | When approved |
503
+ | `comment` | string | no | | Approver comment |
504
+
505
+ #### `control.block` — Block
506
+
507
+ Execute a reusable block workflow inline with input/output mapping
508
+
509
+ **Config** (at step level):
510
+
511
+ | Field | Type | Required | Default | Description |
512
+ | --- | --- | --- | --- | --- |
513
+ | `blockName` | string | yes | | Name of the block to execute |
514
+ | `inputs` | record<string, unknown> | no | | Input mapping for the block |
515
+
516
+ **Output:** `record<string, unknown>`
517
+
518
+ > Output from block's declared output mapping
519
+ <!-- GENERATED:STEP_CATALOG END -->