@eigenpal/cli 0.5.10 → 0.5.12

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.
@@ -3,7 +3,7 @@ version: 1.0.0
3
3
  description: Smallest possible workflow — echoes the input back through transform.script.
4
4
 
5
5
  triggerMethods:
6
- - api
6
+ - type: api
7
7
 
8
8
  inputs:
9
9
  - name: text
@@ -27,19 +27,28 @@ dataset/
27
27
  │ │ ├── Contract_2026.pdf
28
28
  │ │ └── Appendix.pdf
29
29
  │ ├── expected/
30
- │ │ ├── output.json OPTIONAL — raw ground truth JSON
30
+ │ │ ├── output.json OPTIONAL — raw ground truth JSON (success-expected)
31
31
  │ │ └── generated_invoice/ OPTIONAL — expected file output
32
32
  │ │ └── Invoice.docx
33
33
  │ └── meta.json OPTIONAL — { rowOrder?, annotation?, overrides? }
34
34
  │
35
- └── invoice-bar/
36
- └── input/
37
- └── arguments.json { amount: 42 }
35
+ ├── invoice-bar/
36
+ │ └── input/
37
+ │ └── arguments.json { amount: 42 }
38
+ │
39
+ └── unsupported-format/
40
+ ├── input/
41
+ │ └── arguments.json
42
+ └── expected/
43
+ └── error.json OPTIONAL — failure-expected, mutually exclusive with output.json
38
44
  ```
39
45
 
40
- The `expected/` subfolder mirrors `input/` exactly — `output.json` is
46
+ The `expected/` subfolder mirrors `input/` exactly. `output.json` is
41
47
  the scalar/object data ground truth, and any expected file outputs go
42
48
  in `expected/<docKey>/<file>` folders (one folder per output path).
49
+ `error.json` is the failure-expected counterpart (see "Failure-expected
50
+ examples" below) and cannot coexist with `output.json` for the same
51
+ example.
43
52
 
44
53
  ## Rules (every one is enforced server-side at import)
45
54
 
@@ -66,9 +75,29 @@ in `expected/<docKey>/<file>` folders (one folder per output path).
66
75
  `{"contract": "..."}` AND `input/contract/file.pdf` exists, import
67
76
  fails with `code: argument_name_collision`.
68
77
  - **`expected/output.json`** is OPTIONAL. When present, it must be a
69
- JSON object that mirrors your workflow's `output:` shape 1:1 — no
78
+ JSON object that mirrors your workflow's `output:` shape 1:1, no
70
79
  envelope, no wrapper. A scalar/array at the top level is rejected
71
- with `code: invalid_expected_output`.
80
+ with `code: invalid_expected_output`. Mutually exclusive with
81
+ `expected/error.json` (see below).
82
+ - **`expected/error.json`** is OPTIONAL and marks the example as
83
+ failure-expected (the workflow is supposed to terminate via
84
+ `control.fail`). Must be a JSON object with at least one of
85
+ `{ code, messageContains, step }`; an empty object is rejected (it
86
+ would match any failure). When set, the scorer matches the example's
87
+ terminal `executions.error` envelope against the fields you specify
88
+ and ignores `expected/output.json` entirely. `code` is the HTTP-style
89
+ status code from `control.fail`'s `statusCode`; `messageContains` is a
90
+ case-sensitive substring of the failure message; `step` is the name
91
+ of the `control.fail` step expected to fire.
92
+
93
+ ```json
94
+ // expected/error.json
95
+ {
96
+ "code": 422,
97
+ "messageContains": "unsupported document type",
98
+ "step": "reject-unsupported"
99
+ }
100
+ ```
72
101
 
73
102
  > **Note:** the importer is idempotent and accepts both bare objects and
74
103
  > pre-wrapped `{ data: { ... } }` forms; bare is preferred.
@@ -145,9 +174,34 @@ The server materializes:
145
174
 
146
175
  Each file is uploaded to S3 with a fresh `fileId`; the original
147
176
  filename is preserved on the `files` row and shows up in the dashboard.
148
- The `expectedOutput` envelope (`{ data?, expectedDocuments? }`) is
177
+ The `expectedOutput` envelope (`{ data?, expectedDocuments?, error? }`) is
149
178
  internal storage shape — your dataset folder stays clean (raw
150
- `output.json` + per-docKey folders).
179
+ `output.json` or `error.json`, plus per-docKey folders).
180
+
181
+ ## Failure-expected examples
182
+
183
+ When a workflow uses `control.fail` to reject bad inputs, you usually
184
+ want to test both paths: happy-path examples carry `expected/output.json`
185
+ and assert the workflow completed; rejection examples carry
186
+ `expected/error.json` and assert the workflow failed with the right
187
+ status code.
188
+
189
+ ```
190
+ examples/
191
+ ├── valid-invoice/
192
+ │ ├── input/...
193
+ │ └── expected/output.json # success-expected
194
+ └── unsupported-format/
195
+ ├── input/...
196
+ └── expected/error.json # failure-expected
197
+ # { "code": 422, "step": "reject-unsupported" }
198
+ ```
199
+
200
+ The two files are mutually exclusive for the same example (an example
201
+ is either a success-case or a failure-case, never both). The `exact-diff`
202
+ evaluator branches on the envelope shape and reports a clean
203
+ `expectedFailureButSucceeded` or `actualWasUntypedError` reason when
204
+ the actual run doesn't match the expectation.
151
205
 
152
206
  ## Validate before pushing
153
207
 
@@ -54,7 +54,7 @@ finance) who never see the YAML or the judge prompt. Write it for them:
54
54
 
55
55
  ## Evaluator types
56
56
 
57
- ### `exact-diff` — JSON deep-diff against `expected/output.json`
57
+ ### `exact-diff` — JSON deep-diff against `expected/output.json` or `expected/error.json`
58
58
 
59
59
  ```yaml
60
60
  - name: invoice-fields
@@ -70,6 +70,13 @@ finance) who never see the YAML or the judge prompt. Write it for them:
70
70
  When set, only those paths are diffed. When absent, the whole object
71
71
  is compared.
72
72
 
73
+ `exact-diff` branches on the example's expected shape: success-expected
74
+ examples (`expected/output.json`) are diffed against the workflow's actual
75
+ output, and failure-expected examples (`expected/error.json`, see
76
+ `reference/dataset-format.md`) are matched against the `control.fail`
77
+ envelope persisted to `executions.error`. The same evaluator handles both
78
+ paths; you do not need a separate evaluator for failure cases.
79
+
73
80
  ### `llm-judge` — LLM-as-judge scoring
74
81
 
75
82
  ```yaml
@@ -21,11 +21,11 @@ implement at runtime. Server-side validation errors point you here.
21
21
 
22
22
  ## Categories (high-level map)
23
23
 
24
- - `ai.*` — model-backed processing (parse, extract). Cost + latency depend on the model.
24
+ - `ai.*` — model-backed processing (parse, extract, classify). Cost + latency depend on the model.
25
25
  - `transform.*` — deterministic data transforms (set, remove, combine, split, merge,
26
26
  script, template, pdf-embed, xlsx-to-json). WASM sandboxed where applicable.
27
27
  - `action.*` — external side effects (HTTP, invoke another workflow, website reader).
28
- - `control.*` — flow control (if, foreach, parallel, parallel_map, wait, approval, block).
28
+ - `control.*` — flow control (if, foreach, parallel, parallel_map, wait, approval, block, fail).
29
29
 
30
30
  The full per-type catalog with field tables is auto-generated below from
31
31
  `STEP_SCHEMAS`. The high-level map above tells you which family you want;
@@ -33,22 +33,24 @@ the catalog tells you what fields it takes.
33
33
 
34
34
  ## When to reach for what
35
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` |
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
+ | Pick one label from a fixed set | `ai.classify` with `config.labels` |
41
+ | Reject bad inputs with a 4xx code | `control.fail` (often after `ai.classify`) |
42
+ | Sum / filter / regex | `transform.script` (NOT Liquid) |
43
+ | Render a DOCX template | `transform.template` |
44
+ | Convert XLSX to JSON | `transform.xlsx-to-json` |
45
+ | Merge or combine objects | `transform.combine` / `transform.merge` |
46
+ | Conditional execution | `if:` on the step (Liquid) or `control.if` |
47
+ | Map over an input array | `forEach:` on the step or `control.foreach` |
48
+ | Concurrent map over an array | `control.parallel_map` |
49
+ | Independent parallel branches | `control.parallel` |
50
+ | Pause for human approval | `control.approval` |
51
+ | External HTTP call | `action.http` |
52
+ | Call another workflow | `action.invoke-workflow` |
53
+ | Fetch a webpage as markdown | `action.website-reader` |
52
54
 
53
55
  ## Liquid vs `transform.script`
54
56
 
@@ -101,6 +103,247 @@ $ eigenpal workflow step-type get ai.extract | jq '.outputSchema'
101
103
  Reference user-supplied schema fields the same way:
102
104
  `{{ steps.extract.output.extracted.invoiceNumber }}`.
103
105
 
106
+ ## Control containers — nested step shape and scoping
107
+
108
+ `control.parallel`, `control.parallel_map`, `control.foreach`, `control.if`,
109
+ and `control.block` contain nested steps. The auto-generated catalog below
110
+ can't render that shape, so the YAML form lives here. Same for the scoping
111
+ rules — important because the runtime treats nested steps differently from
112
+ top-level ones, and getting the access path wrong silently returns
113
+ `undefined` (no error).
114
+
115
+ ### `control.parallel` — independent branches
116
+
117
+ Runs each branch concurrently. Use when several pieces of work share input
118
+ but don't depend on each other.
119
+
120
+ ```yaml
121
+ - name: enrich
122
+ type: control.parallel
123
+ branches:
124
+ - name: legal
125
+ steps:
126
+ - name: extract-clauses
127
+ type: ai.extract
128
+ with:
129
+ input: '{{ steps.parse.output.markdown }}'
130
+ schema: { clauses: { type: array, items: { type: string } } }
131
+ - name: financial
132
+ steps:
133
+ - name: extract-totals
134
+ type: ai.extract
135
+ with:
136
+ input: '{{ steps.parse.output.markdown }}'
137
+ schema: { total: { type: number } }
138
+ ```
139
+
140
+ **Access the output:** branch step outputs are stored under the parent
141
+ parallel step keyed by branch name, then by inner step name. There is no
142
+ flat top-level entry for branch-internal steps.
143
+
144
+ ```liquid
145
+ {{ steps.enrich.output.legal.extract-clauses.clauses }}
146
+ {{ steps.enrich.output.financial.extract-totals.total }}
147
+ ```
148
+
149
+ **Scope inside a branch:** top-level steps that ran before the parallel,
150
+ plus earlier siblings in the same branch. **Other branches' steps are
151
+ NOT visible** — each branch runs in an isolated child scope.
152
+
153
+ ```liquid
154
+ # Inside branch `legal`, second step:
155
+ {{ steps.parse.output.markdown }} # ancestor: visible
156
+ {{ steps.extract-clauses.output.clauses }} # sibling: visible
157
+ {{ steps.extract-totals.output.total }} # other branch: UNDEFINED
158
+ ```
159
+
160
+ ### `control.parallel_map` — fan-out over an array
161
+
162
+ Runs the inner step block concurrently for each item, up to `concurrency`.
163
+
164
+ ```yaml
165
+ - name: process-pages
166
+ type: control.parallel_map
167
+ items: '{{ steps.split.output.pages }}'
168
+ as: page
169
+ indexAs: i # optional
170
+ concurrency: 5 # default 5, max 50
171
+ steps:
172
+ - name: extract-fields
173
+ type: ai.extract
174
+ with:
175
+ input: '{{ page.text }}'
176
+ schema: { fields: { type: object } }
177
+ ```
178
+
179
+ **Access the output:** iteration results are returned as an array in the
180
+ original input order under the parent step's `items` field.
181
+
182
+ ```liquid
183
+ {{ steps.process-pages.output.items[0].extract-fields.fields }}
184
+ {{ steps.process-pages.output.count }} # number of completed iterations
185
+ {{ steps.process-pages.output.totalIterations }}
186
+ ```
187
+
188
+ `steps.extract-fields.output.fields` (the flat form) is **not** addressable
189
+ from outside the parallel_map.
190
+
191
+ **Multi-step iterations only key the LAST step's output into `items[i]`.**
192
+ If your iteration body has steps `[parse, extract]`, each `items[i]` is
193
+ `{ extract: <extract output> }` — `parse`'s output is gone. To preserve
194
+ intermediate fields, end the body with a `transform.script` that returns
195
+ the combined shape:
196
+
197
+ ```yaml
198
+ steps:
199
+ - name: parse
200
+ type: ai.parse
201
+ with: { ... }
202
+ - name: extract
203
+ type: ai.extract
204
+ with: { input: '{{ steps.parse.output.markdown }}', ... }
205
+ - name: combine
206
+ type: transform.script
207
+ with:
208
+ inputs:
209
+ parsed: '{{ steps.parse.output }}'
210
+ extracted: '{{ steps.extract.output }}'
211
+ function: |
212
+ function script(parsed, extracted): { parsed: any; extracted: any } {
213
+ return { parsed, extracted };
214
+ }
215
+ ```
216
+
217
+ Now `items[i].combine.parsed` / `items[i].combine.extracted` both survive.
218
+
219
+ ### `control.foreach` — sequential loop over an array
220
+
221
+ Same shape as `parallel_map` minus `concurrency`; iterations run in order.
222
+ Use when each iteration depends on the previous, or when concurrency would
223
+ cause downstream rate limits.
224
+
225
+ ```yaml
226
+ - name: per-row
227
+ type: control.foreach
228
+ items: '{{ steps.fetch.output.rows }}'
229
+ as: row
230
+ steps:
231
+ - name: persist
232
+ type: action.http-request
233
+ with:
234
+ method: POST
235
+ url: 'https://example.com/api/rows/{{ row.id }}'
236
+ body: '{{ row }}'
237
+ ```
238
+
239
+ **Access the output:** same shape as parallel_map — an `items[]` array of
240
+ per-iteration results, in iteration order. Same "only the LAST inner step
241
+ is keyed into `items[i]`" rule applies (use a trailing `transform.script`
242
+ to preserve intermediate outputs).
243
+
244
+ > ⚠️ Foreach does NOT isolate scopes — the last iteration's inner step
245
+ > outputs technically persist on the top-level `steps` map and `steps.<inner>.output.x`
246
+ > resolves at runtime. That's an only-the-last-row footgun; the autocomplete
247
+ > hides these names and you should treat `steps.<foreach>.output.items[]`
248
+ > as the only correct access.
249
+
250
+ ### `control.if` — conditional branch
251
+
252
+ ```yaml
253
+ - name: route
254
+ type: control.if
255
+ condition: '{{ input.priority == "high" }}'
256
+ then:
257
+ - name: rush-extract
258
+ type: ai.extract
259
+ with: { ... }
260
+ else:
261
+ - name: normal-extract
262
+ type: ai.extract
263
+ with: { ... }
264
+ ```
265
+
266
+ **Access the output:** the `if` step's output is
267
+
268
+ ```ts
269
+ {
270
+ condition: boolean, // evaluated condition
271
+ branch: 'then' | 'else', // which branch ran
272
+ result: <last inner step's output> | null // null when chosen branch is empty
273
+ }
274
+ ```
275
+
276
+ The canonical way to consume the result is through the if step itself:
277
+
278
+ ```liquid
279
+ {{ steps.route.output.branch }} # "then" or "else"
280
+ {{ steps.route.output.result.foo }} # field from the branch's last step
281
+ ```
282
+
283
+ `steps.<innerName>.output` references from outside the `if` also resolve
284
+ at runtime (unlike `parallel`/`parallel_map`, `if` does not isolate
285
+ scopes), but the template breaks the moment the OTHER branch runs. Use
286
+ `steps.<if>.output.result` so the access is branch-agnostic — or compute
287
+ the final shape in a trailing `transform.script` that handles both cases.
288
+
289
+ ### `control.block` — call a reusable workflow block
290
+
291
+ ```yaml
292
+ - name: invoice-flow
293
+ type: control.block
294
+ blockName: parse-invoice # other workflow's `name:`
295
+ inputs:
296
+ contract: '{{ input.contract }}'
297
+ ```
298
+
299
+ **Access the output:** the block's declared output fields are exposed as
300
+ top-level keys under `steps.<this-step>.output`. Block-internal steps are
301
+ not visible — the block runs in an isolated scope.
302
+
303
+ ## Classify-and-fail pattern
304
+
305
+ `ai.classify` + `control.fail` together let a workflow reject bad inputs
306
+ fast with a typed HTTP-style status code that callers (and evals) can
307
+ match against. The classifier picks one label from a closed set; the
308
+ fail step terminates the run when the label is one you don't want to
309
+ process.
310
+
311
+ ```yaml
312
+ - name: parse
313
+ type: ai.parse
314
+ with:
315
+ input: '{{ input.document }}'
316
+
317
+ - name: classify
318
+ type: ai.classify
319
+ with:
320
+ input: '{{ steps.parse.output.text }}'
321
+ labels:
322
+ - name: invoice
323
+ description: A vendor invoice with line items and totals.
324
+ - name: contract
325
+ description: A multi-page contract or agreement.
326
+ - name: other
327
+ description: Anything that is not an invoice or contract.
328
+
329
+ - name: reject-unsupported
330
+ type: control.fail
331
+ condition: '{{ steps.classify.output.label == "other" }}'
332
+ statusCode: 422
333
+ message: 'Unsupported document: {{ steps.classify.output.reason }}'
334
+ ```
335
+
336
+ > `control.fail` config is step-level (no `with:`), matching every other
337
+ > `control.*` step. `condition`, `statusCode`, and `message` sit directly
338
+ > on the step node.
339
+
340
+ The synchronous run endpoint surfaces the `statusCode` as the HTTP
341
+ response status; async runs persist `{ code, message, step }` to
342
+ `executions.error` so the eval scorer can match against
343
+ `expected/error.json` (see `reference/dataset-format.md`). When
344
+ `condition` is omitted, `control.fail` always fails when reached, so
345
+ compose with `control.if` for legacy gating.
346
+
104
347
  <!-- GENERATED:STEP_CATALOG START -->
105
348
  ## Full catalog
106
349
 
@@ -173,6 +416,29 @@ Split a parsed document into named sections using an LLM. Consumes ai.parse outp
173
416
  | --- | --- | --- | --- | --- |
174
417
  | `splits` | array<object> | yes | | Sections found in the document, in page order. Absent sections are omitted. |
175
418
 
419
+ #### `ai.classify` — Classify
420
+
421
+ Classify a document or text into one of a fixed label set using an LLM. Output exposes the picked label (constrained to the configured names), a coarse confidence, and a short justification. Pair with control.fail to reject documents that match an undesired label.
422
+
423
+ **Config** (in `step.with`):
424
+
425
+ | Field | Type | Required | Default | Description |
426
+ | --- | --- | --- | --- | --- |
427
+ | `input` | string | yes | | Template expression for the text to classify. Typically the output of ai.parse, e.g. "{{ steps.parse.output.text }}". |
428
+ | `labels` | array<object> | yes | | Allowed labels. The LLM is constrained to pick exactly one of these names. |
429
+ | `prompt` | string | no | | Custom classification instructions appended to the system prompt. Use to clarify edge cases or emphasize evidence the model should weigh. |
430
+ | `provider` | string | no | | Provider ID from eigenpal.config.yaml (e.g. "openai-gpt4o-mini"). Falls back to the tenant default LLM provider when omitted. |
431
+ | `model` | string | no | | Model override (advanced) |
432
+ | `maxInputTokens` | integer | no | | Max input tokens. Truncates input text when exceeded. Omit for no limit. |
433
+
434
+ **Output:** `object`
435
+
436
+ | Field | Type | Required | Default | Description |
437
+ | --- | --- | --- | --- | --- |
438
+ | `label` | string | yes | | The selected label name (one of the configured labels). Compare against literal strings to gate downstream steps. |
439
+ | `confidence` | `"low"` \| `"medium"` \| `"high"` | yes | | LLM confidence in the classification. Coarse enum — numeric scores cluster meaninglessly at 0.85-0.95. |
440
+ | `reason` | string | yes | | Short justification for the chosen label — useful for debugging. |
441
+
176
442
  ### Transform steps — deterministic data transforms
177
443
 
178
444
  #### `transform.set` — Set Value
@@ -568,4 +834,20 @@ Execute a reusable block workflow inline with input/output mapping
568
834
  **Output:** `record<string, unknown>`
569
835
 
570
836
  > Output from block's declared output mapping
837
+
838
+ #### `control.fail` — Fail
839
+
840
+ Terminate the workflow with a typed status code + message. With an optional condition, only fails when the condition is truthy; otherwise always fails when reached. Pair with ai.classify or any prior step to fail fast on bad inputs.
841
+
842
+ **Config** (at step level):
843
+
844
+ | Field | Type | Required | Default | Description |
845
+ | --- | --- | --- | --- | --- |
846
+ | `condition` | string | no | | Optional LiquidJS expression. When set, the step only fails if this evaluates truthy; when omitted, it always fails when reached (compose with control.if for legacy gating). |
847
+ | `statusCode` | integer | no | `422` | HTTP-style status code returned to the caller (sync runs) and persisted on the execution. Default 422 (Unprocessable Entity). |
848
+ | `message` | string | yes | | Human-readable failure message. Supports template expressions, e.g. "Document classified as {{ steps.classify.output.label }}". |
849
+
850
+ **Output:** `unknown`
851
+
852
+ > control.fail never produces output — it terminates the workflow when triggered.
571
853
  <!-- GENERATED:STEP_CATALOG END -->
@@ -16,7 +16,7 @@ eigenpal workflow step-type get <type> # JSON Schema for one type's config + o
16
16
  name: extract-invoices # string, lowercase kebab/snake-case, unique within tenant
17
17
  version: 1.0.0 # bare semver `MAJOR.MINOR.PATCH`, no leading `v`
18
18
  description: Parse a PDF and extract invoice fields. # string, optional
19
- triggerMethods: [api] # array, ≥1 entry from: api | cron | email
19
+ triggerMethods: [{ type: api }] # array, ≥1 entry with type: api | manual | email
20
20
  inputs: [...] # array of input definitions (see below)
21
21
  steps: [...] # array of steps, executed in topological order
22
22
  output: {...} # object — the workflow's return shape
@@ -36,13 +36,13 @@ folder.
36
36
 
37
37
  ### `triggerMethods`
38
38
 
39
- Required. Each entry adds an invocation surface:
39
+ Required. Each entry is an object that adds an invocation surface:
40
40
 
41
- - `api` — the workflow can be called via REST or the CLI's `workflow execution run`
42
- - `cron` — scheduled. Add a sibling `cron: { schedule: "0 9 * * *", timezone: "UTC" }`
43
- - `email` — inbound email. Add a sibling `email: { subject?: ..., aliases?: [...] }`
41
+ - `{ type: api }` — the workflow can be called via REST or the CLI's `workflow execution run`
42
+ - `{ type: manual }` — the workflow can be started manually
43
+ - `{ type: email, whitelist?: ... }` — inbound email
44
44
 
45
- If `api` is missing, `eigenpal workflow execution run` and `workflow run` reject with a
45
+ If `{ type: api }` is missing, `eigenpal workflow execution run` rejects with a
46
46
  403 / "Workflow does not have API trigger enabled."
47
47
 
48
48
  ### `inputs`
@@ -140,7 +140,7 @@ See `reference/step-types.md` for the full catalog. Categories:
140
140
  - `action.*` — side-effectful operations (HTTP, email, connectors)
141
141
  - `ai.*` — model-backed processing (parse, extract, classify, judge)
142
142
  - `transform.*` — deterministic data transforms (script, template, merge)
143
- - `control.*` — flow control (if, for-each, parallel-map)
143
+ - `control.*` — flow control (if, for-each, parallel-map, fail)
144
144
 
145
145
  When in doubt, use `eigenpal workflow step-type get <type>` — it returns
146
146
  the exact JSON Schema for the step's `config` + `output` so you know
@@ -486,5 +486,6 @@ _Generated from `WorkflowDefinitionSchema` in `@eigenpal/types/src/workflow/work
486
486
  | `description` | string | no | | |
487
487
  | `required` | boolean | no | `true` | |
488
488
  | `default` | unknown | no | | |
489
+ | `values` | array<string> | no | | |
489
490
  | `items` | object | no | | |
490
491
  <!-- GENERATED:WORKFLOW_REFERENCE END -->
@@ -4,9 +4,8 @@ description: Replace this with what your workflow does.
4
4
 
5
5
  # `triggerMethods` controls how this workflow can be invoked:
6
6
  # - api: workflow can be called via the REST API and CLI
7
- # - cron: workflow can be scheduled (set `cron` field below)
8
7
  triggerMethods:
9
- - api
8
+ - type: api
10
9
 
11
10
  # `inputs` declares the schema of arguments the workflow accepts. Each entry
12
11
  # becomes a top-level key on the call's `triggerInput` object. Use `file: true`
@@ -3,7 +3,7 @@ version: 1.0.0
3
3
  description: Parse a PDF and extract structured fields with an LLM.
4
4
 
5
5
  triggerMethods:
6
- - api
6
+ - type: api
7
7
 
8
8
  inputs:
9
9
  - name: document
@@ -3,7 +3,7 @@ version: 1.0.0
3
3
  description: Classify free-form text into one of a fixed set of labels.
4
4
 
5
5
  triggerMethods:
6
- - api
6
+ - type: api
7
7
 
8
8
  inputs:
9
9
  - name: text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eigenpal/cli",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
5
5
  "keywords": [
6
6
  "eigenpal",