@eigenpal/cli 0.8.0 → 0.9.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.
@@ -663,7 +663,7 @@ Get experiment status.
663
663
  | `--json` | no | | Output the raw server response as JSON |
664
664
  | `--watch` | no | | Poll until complete |
665
665
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
666
- | `--max-wait <seconds>` | no | `1800` | Maximum wait before exiting 2 |
666
+ | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
667
667
  | `--include <parts>` | no | | Reserved for future detailed parts |
668
668
 
669
669
  ### `eigenpal agents experiment|exp results [options] <agent-id-or-slug> [batch-id]`
@@ -17,4 +17,4 @@ Create a new run from a previous run's stored input snapshot.
17
17
  | `--version <version>` | no | `"latest"` | Version/source ref for the new run: latest, original, or an explicit ref |
18
18
  | `--wait` | no | | Poll until the rerun reaches a terminal status |
19
19
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
20
- | `--max-wait <seconds>` | no | `1800` | Maximum wait before exiting 2 |
20
+ | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
@@ -20,4 +20,4 @@ Start a workflow or agent run, e.g. workflows.extract-invoice.
20
20
  | `--dir <dir>` | no | | Local eigenpal directory for workflow examples |
21
21
  | `--wait` | no | | Poll until the run reaches a terminal status |
22
22
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
23
- | `--max-wait <seconds>` | no | `1800` | Maximum wait before exiting 2 |
23
+ | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
@@ -199,7 +199,7 @@ Create a new run from a previous run's stored input snapshot.
199
199
  | `--source-ref <ref>` | no | | Alias for --version |
200
200
  | `--wait` | no | | Poll until the rerun reaches a terminal status |
201
201
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
202
- | `--max-wait <seconds>` | no | `1800` | Maximum wait before exiting 2 |
202
+ | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
203
203
 
204
204
  ### `eigenpal runs promote [options] <run-id>`
205
205
 
@@ -253,7 +253,7 @@ Watch a run until it reaches a terminal status.
253
253
  | `--base-url <url>` | no | | Server base URL |
254
254
  | `--json` | no | | Output the raw server response as JSON |
255
255
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
256
- | `--max-wait <seconds>` | no | `1800` | Maximum wait before exiting 2 |
256
+ | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
257
257
 
258
258
  ### `eigenpal runs cancel [options] <run-id>`
259
259
 
@@ -1,240 +1,130 @@
1
1
  # Dataset folder format
2
2
 
3
- A dataset is a folder of named example folders. The folder structure
4
- itself is the manifest — there is no top-level `manifest.json`. The
5
- import endpoint **rejects** archives that contain one (legacy format).
3
+ A dataset is a folder of named example folders. The folder structure itself is
4
+ the manifest; there is no top-level `manifest.json`, and the import endpoint
5
+ rejects archives that contain one.
6
6
 
7
- This is the format the CLI builds and ships to
8
- `eigenpal workflow dataset push <workflow-id> --file dataset/`. After it lands
9
- on the server, the canonical per-row surface is
10
- `eigenpal workflow dataset example {get,create,update,delete}` (and
11
- `dataset list` for a table view) — the local folder is a pre-push
12
- staging area, not a synced mirror.
13
-
14
- Dataset folders are unrelated to workflow organization folders. To place
15
- the workflow itself under dashboard folders, use `workflow move --folder`;
16
- dataset push/pull never changes folder placement.
7
+ This is the archive format for automation datasets, shared by workflows and
8
+ agents. `eigenpal workflow dataset push <workflow-id> --file dataset/` builds
9
+ this archive locally and sends it to the server.
17
10
 
18
11
  ## Required layout
19
12
 
20
- ```
13
+ ```text
21
14
  dataset/
22
15
  └── examples/
23
16
  ├── invoice-foo/
17
+ │ ├── input.json REQUIRED — full run input object
24
18
  │ ├── input/
25
- │ │ ├── arguments.json REQUIRED — { language: "en" }
26
- │ │ └── contract/ file-argument folder
27
- │ │ ├── Contract_2026.pdf
28
- │ │ └── Appendix.pdf
19
+ │ │ ├── Contract_2026.pdf referenced by input.json
20
+ │ │ └── Appendix.pdf
21
+ │ ├── expected.json OPTIONAL — expected output or { "$error": ... }
29
22
  │ ├── expected/
30
- │ │ ├── output.json OPTIONAL — raw ground truth JSON (success-expected)
31
- │ │ └── generated_invoice/ OPTIONAL — expected file output
32
- │ │ └── Invoice.docx
23
+ │ │ └── Invoice.docx referenced by expected.json
33
24
  │ └── meta.json OPTIONAL — { rowOrder?, annotation?, overrides? }
34
25
  │
35
- ├── invoice-bar/
36
- │ └── input/
37
- │ └── arguments.json { amount: 42 }
38
- │
39
26
  └── unsupported-format/
40
- ├── input/
41
- │ └── arguments.json
42
- └── expected/
43
- └── error.json OPTIONAL — failure-expected, mutually exclusive with output.json
27
+ ├── input.json
28
+ └── expected.json { "$error": { "code": 422 } }
44
29
  ```
45
30
 
46
- The `expected/` subfolder mirrors `input/` exactly. `output.json` is
47
- the scalar/object data ground truth, and any expected file outputs go
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.
31
+ `input.json` is the source of truth for the full automation input. File values
32
+ are explicit references:
52
33
 
53
- ## Rules (every one is enforced server-side at import)
34
+ ```json
35
+ {
36
+ "language": "en",
37
+ "contract": [
38
+ { "$file": "input/Contract_2026.pdf" },
39
+ { "$file": "input/Appendix.pdf" }
40
+ ]
41
+ }
42
+ ```
54
43
 
55
- - **Folder names** (example names, argument names, expected document
56
- keys) must match `[a-z0-9][a-z0-9-_]*`.
57
- - **`input/arguments.json`** is REQUIRED for every example, even if it is
58
- `{}`. It must be a JSON object (not array, not literal).
59
- - **File-arguments** live in `input/<arg-name>/<filename>`. The
60
- materialized shape on `triggerInput` is **driven by the workflow YAML's
61
- `inputs[].type`**, not by the file count:
44
+ `expected.json` mirrors the expected automation output. Expected files use the
45
+ same reference shape with the `expected/` prefix.
62
46
 
63
- | workflow.yaml input | dataset folder | `triggerInput.<name>` |
64
- | ------------------------------------------ | ------------------------- | --------------------- |
65
- | `type: file` | `input/<name>/<one-file>` | `{ fileId }` |
66
- | `type: array`, `items: { type: file }` | `input/<name>/<file>...` | `[{ fileId }, ...]` |
47
+ ## Rules
67
48
 
68
- Reference single-file inputs as `{{ input.<name> }}` (resolves to the
69
- `{ fileId }` object the worker reads). Reference array-of-file inputs
70
- as `{{ input.<name>[0] }}` etc. Dropping multiple binaries into an
71
- `input/<name>/` folder for an input declared `type: file` is rejected
72
- at import with `code: single_file_input_overpopulated` — either trim
73
- to one file or change the workflow input to the array form.
74
- - **Argument-name collisions are rejected.** If `arguments.json` has
75
- `{"contract": "..."}` AND `input/contract/file.pdf` exists, import
76
- fails with `code: argument_name_collision`.
77
- - **`expected/output.json`** is OPTIONAL. When present, it must be a
78
- JSON object that mirrors your workflow's `output:` shape 1:1, no
79
- envelope, no wrapper. A scalar/array at the top level is rejected
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.
49
+ - Example folder names must match `[a-z0-9][a-z0-9-_]*`.
50
+ - Every example needs `input.json`, even if it is `{}`. It must be a JSON object.
51
+ - Files under `input/` must be referenced from `input.json` as
52
+ `{ "$file": "input/<path>" }`.
53
+ - Files under `expected/` must be referenced from `expected.json` as
54
+ `{ "$file": "expected/<path>" }`.
55
+ - File references cannot use `..`, absolute paths, backslashes, or null bytes.
56
+ - `expected.json` is optional. When present, it must be a JSON object.
57
+ - Failure-expected examples use `expected.json` with a single `$error` key:
92
58
 
93
- ```json
94
- // expected/error.json
95
- {
59
+ ```json
60
+ {
61
+ "$error": {
96
62
  "code": 422,
97
63
  "messageContains": "unsupported document type",
98
64
  "step": "reject-unsupported"
99
65
  }
100
- ```
101
-
102
- > **Note:** the importer is idempotent and accepts both bare objects and
103
- > pre-wrapped `{ data: { ... } }` forms; bare is preferred.
66
+ }
67
+ ```
104
68
 
105
- ```json
106
- // workflow.yaml
107
- // output:
108
- // invoiceNumber: '{{ steps.extract.output.invoiceNumber }}'
109
- // totalAmount: '{{ steps.extract.output.totalAmount }}'
110
- //
111
- // → expected/output.json
112
- {
113
- "invoiceNumber": "INV-001",
114
- "totalAmount": 1234.56
115
- }
116
- ```
117
- - **Expected file outputs** live in `expected/<docKey>/<filename>` —
118
- symmetric with `input/<argName>/<file>`. Each `<docKey>` folder is
119
- uploaded and becomes an `expectedDocuments[docKey]` entry server-side
120
- (single file ⇒ `{fileId}`, multiple ⇒ `[{fileId}, …]`). The judge
121
- compares by file presence + content, not byte-equal.
122
- - **`meta.json`** is OPTIONAL. Schema:
123
- ```json
124
- {
125
- "rowOrder": 0,
126
- "annotation": "free-form notes about this example",
127
- "overrides": {
128
- "steps": {
129
- "<stepName>": { ...stepOutputObject }
130
- }
131
- }
132
- }
133
- ```
134
- - `rowOrder` — sort order in the dashboard
135
- - `annotation` — surfaced in the UI; useful for "why this case is
136
- interesting" notes
137
- - `overrides.steps.<stepName>` — short-circuits the named step,
138
- returns the override as the step's output. Use for steps that
139
- depend on external state (third-party APIs, private connectors) so the rest
140
- of the example can still run.
141
- - **Path-traversal patterns** (`..`, leading `/`, backslashes, null
142
- bytes) reject the whole archive.
69
+ The `$error` object must contain at least one of `code`, `messageContains`, or
70
+ `step`. It asserts that the automation should fail with a matching typed
71
+ `control.fail` envelope.
143
72
 
144
- ## How the row materializes
73
+ ## How a row materializes
145
74
 
146
75
  For an example with:
147
76
 
148
- - `arguments.json`: `{"language": "en"}`
149
- - `input/contract/Contract_2026.pdf`
150
- - `input/contract/Appendix.pdf`
151
- - `expected/output.json`: `{"invoiceNumber": "INV-001"}`
152
- - `expected/generated_invoice/Invoice.docx`
77
+ - `input.json`: `{"language":"en","contract":[{"$file":"input/Contract_2026.pdf"},{"$file":"input/Appendix.pdf"}]}`
78
+ - `input/Contract_2026.pdf`
79
+ - `input/Appendix.pdf`
80
+ - `expected.json`: `{"invoiceNumber":"INV-001","generatedInvoice":{"$file":"expected/Invoice.docx"}}`
81
+ - `expected/Invoice.docx`
153
82
 
154
- The server materializes:
83
+ The stored row keeps the same input shape. When the example runs, each `$file`
84
+ reference is resolved into the S3 file descriptor the worker consumes.
155
85
 
156
86
  ```json
157
- // eval_examples.triggerInput
158
87
  {
159
88
  "language": "en",
160
89
  "contract": [
161
- { "fileId": "file_abc..." },
162
- { "fileId": "file_def..." }
90
+ { "kind": "s3", "ref": ".../input/Contract_2026.pdf", "filename": "Contract_2026.pdf" },
91
+ { "kind": "s3", "ref": ".../input/Appendix.pdf", "filename": "Appendix.pdf" }
163
92
  ]
164
93
  }
165
-
166
- // eval_examples.expectedOutput
167
- {
168
- "data": { "invoiceNumber": "INV-001" },
169
- "expectedDocuments": {
170
- "generated_invoice": { "fileId": "file_ghi..." }
171
- }
172
- }
173
94
  ```
174
95
 
175
- Each file is uploaded to S3 with a fresh `fileId`; the original
176
- filename is preserved on the `files` row and shows up in the dashboard.
177
- The `expectedOutput` envelope (`{ data?, expectedDocuments?, error? }`) is
178
- internal storage shape — your dataset folder stays clean (raw
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 does not match the expectation.
96
+ Each file is uploaded to S3 and the original filename is preserved.
205
97
 
206
98
  ## Validate before pushing
207
99
 
208
100
  ```bash
209
- eigenpal workflow validate dataset ./dataset
101
+ eigenpal workflow dataset validate ./dataset
210
102
  ```
211
103
 
212
- Reports per-example issues:
104
+ Example output:
213
105
 
214
- ```
106
+ ```text
215
107
  ✗ dataset (./dataset) — 2 issues
216
- examples/Invoice-Foo Folder name must be lowercase kebab/snake-case.
217
- examples/foo/input/contract Argument-name collision: "contract" appears in both arguments.json and as an input/ folder.
108
+ examples/Invoice-Foo Folder name must be lowercase kebab/snake-case.
109
+ examples/foo/input.json:contract Referenced file does not exist: input/contract.pdf.
218
110
  ```
219
111
 
220
112
  ## Push
221
113
 
222
114
  ```bash
223
- # Replace = wipes existing examples for the workflow, uploads the folder fresh.
115
+ # Replace wipes existing examples for the automation, uploads the folder fresh.
224
116
  eigenpal workflow dataset push <workflow-id> --file ./dataset --mode replace
225
117
 
226
- # Append = adds to whatever's already on the server.
118
+ # Append adds to whatever is already on the server.
227
119
  eigenpal workflow dataset push <workflow-id> --file ./dataset --mode append
228
120
  ```
229
121
 
230
- The endpoint streams progress as NDJSON. The terminal `done` event
231
- includes `{ created, expectedSet, … }`:
122
+ The endpoint streams progress as NDJSON. The terminal `done` event includes
123
+ `{ created, expectedSet, … }`:
232
124
 
233
- - `created` — examples successfully persisted
234
- - `expectedSet` — how many of those carried an `expected/output.json`.
235
- If `expectedSet < created`, your zip didn't include the `expected/`
236
- folders — the dashboard's "Expected" tab will be blank and evals
237
- will only run un-graded.
125
+ - `created` — examples successfully persisted.
126
+ - `expectedSet` — how many of those carried an `expected.json`. If
127
+ `expectedSet < created`, the rest run un-graded.
238
128
 
239
129
  ## Editing a dataset on the server
240
130
 
@@ -243,67 +133,10 @@ To inspect or round-trip what is currently on the server:
243
133
  ```bash
244
134
  eigenpal workflow dataset list <workflow-id>
245
135
  eigenpal workflow dataset pull <workflow-id> --out current.zip
246
- # (or omit --out to stream the zip to stdout for piping)
247
- ```
248
-
249
- For bulk changes — many examples at once, schema-level rework, file
250
- swaps — edit the local folder and re-push with
251
- `dataset push --mode replace`.
252
-
253
- For one-row tweaks — capturing a corrected output as the new GT,
254
- deleting one bad example, adding one example to a known dataset — use
255
- the per-example CRUD commands instead. They avoid the round-trip of
256
- re-zipping and re-uploading the whole folder.
257
-
258
- ### Inspect one example end-to-end
259
-
260
- ```bash
261
- eigenpal workflow dataset example get <workflow-id> <example-id>
262
- eigenpal workflow dataset example get <workflow-id> <example-id> --json | jq '.expectedOutput'
263
- ```
264
-
265
- Pretty mode prints labelled `Inputs` / `Expected` / `Metadata` sections;
266
- `--json` returns the full row (`triggerInput`, `expectedOutput`,
267
- `annotation`, `rowOrder`, timestamps) for piping.
268
-
269
- ### Capture a corrected output as the new ground truth
270
-
271
- ```bash
272
- # 1. Look at what the workflow actually returned for the example
273
- eigenpal runs get exec_… --json | jq '.output.data' > /tmp/correct.json
274
-
275
- # 2. Patch the example's expected output in place. PATCH semantics:
276
- # every flag you omit is left alone.
277
- eigenpal workflow dataset example update <workflow-id> <example-id> \
278
- --expected-file /tmp/correct.json
279
- ```
280
-
281
- `--expected-file -` reads JSON from stdin if you would rather pipe. Pass
282
- empty string to clear an annotation: `--annotation ""`.
283
-
284
- ### Add one new example
285
-
286
- ```bash
287
- eigenpal workflow dataset example create <workflow-id> \
288
- --name missing-required-field \
289
- --input-json '{"language":"en"}' \
290
- --expected-file /tmp/expected.json \
291
- --annotation "edge case: required field absent from input"
292
- ```
293
-
294
- `--input-json` / `--expected-json` accept inline JSON literals; the
295
- `-file` variants read from disk (or `-` for stdin). They are mutually
296
- exclusive — pass one or the other, not both.
297
-
298
- File-arg uploads still go through `dataset push`; CRUD only handles
299
- scalar args + `expected/output.json`-style outputs.
300
-
301
- ### Delete one bad example
302
-
303
- ```bash
304
- eigenpal workflow dataset example delete <workflow-id> <example-id> --yes
305
136
  ```
306
137
 
307
- `--yes` is required for non-TTY shells (CI). Interactive shells may
308
- omit it; single-row deletes have a small blast radius compared to
309
- `dataset push --mode replace`, so no confirmation prompt fires.
138
+ For bulk changes, edit the local folder and re-push with
139
+ `dataset push --mode replace`. For one-row tweaks, use
140
+ `eigenpal workflow dataset example {get,create,update,delete}`. File uploads
141
+ still go through `dataset push`; CRUD only handles JSON input and
142
+ `expected.json`-style outputs.
@@ -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` or `expected/error.json`
57
+ ### `exact-diff` — JSON deep-diff against `expected.json`
58
58
 
59
59
  ```yaml
60
60
  - name: invoice-fields
@@ -71,8 +71,8 @@ finance) who never see the YAML or the judge prompt. Write it for them:
71
71
  is compared.
72
72
 
73
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
74
+ examples (`expected.json`) are diffed against the workflow's actual output, and
75
+ failure-expected examples (`expected.json` with `$error`, see
76
76
  `reference/dataset-format.md`) are matched against the `control.fail`
77
77
  envelope persisted to `executions.error`. The same evaluator handles both
78
78
  paths; you do not need a separate evaluator for failure cases.
@@ -339,7 +339,7 @@ process.
339
339
  The synchronous run endpoint surfaces the `statusCode` as the HTTP
340
340
  response status; async runs persist `{ code, message, step }` to
341
341
  `executions.error` so the eval scorer can match against
342
- `expected/error.json` (see `reference/dataset-format.md`). When
342
+ `expected.json` with `$error` (see `reference/dataset-format.md`). When
343
343
  `condition` is omitted, `control.fail` always fails when reached, so
344
344
  compose with `control.if` for legacy gating.
345
345
 
@@ -526,7 +526,7 @@ Merge multiple named inputs into a single output
526
526
 
527
527
  #### `transform.template` — Fill Template
528
528
 
529
- Fill a DOCX template with data. Use list_templates tool to get template IDs and their placeholder schemas.
529
+ Fill a DOCX template with data from previous steps. Select a template in the workflow builder or provide a template ID from your workspace.
530
530
 
531
531
  **Config** (in `step.with`):
532
532
 
@@ -12,8 +12,9 @@ eigenpal run workflows.__NAME__ --example sample
12
12
  ```
13
13
 
14
14
  Add more examples under `dataset/examples/<name>/`. Each example needs an
15
- `input/arguments.json`; `expected/output.json` and `meta.json` are optional.
16
- File arguments live in `input/<arg-name>/<filename>`.
15
+ `input.json`; `expected.json` and `meta.json` are optional. File arguments live
16
+ under `input/` and are referenced from `input.json` with
17
+ `{ "$file": "input/<filename>" }`.
17
18
 
18
19
  When you are ready to push this workflow to the server:
19
20
 
@@ -1,5 +1,5 @@
1
1
  # Evaluators run after every workflow execution that's part of an eval batch.
2
- # Comment them in once you have an `expected/output.json` to compare against.
2
+ # Comment them in once you have an `expected.json` output object to compare against.
3
3
  #
4
4
  # evaluators:
5
5
  # - name: exact-match
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eigenpal/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
5
5
  "keywords": [
6
6
  "eigenpal",