@eigenpal/cli 0.12.2 → 0.13.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.
@@ -9,6 +9,7 @@ Manage workflows: push, pull, and evaluate.
9
9
  - [Core](#core)
10
10
  - [Evaluators](#evaluators)
11
11
  - [Dataset](#dataset)
12
+ - [Templates](#templates)
12
13
  - [Experiment](#experiment)
13
14
  - [Versions](#versions)
14
15
  - [Step-type](#step-type)
@@ -33,6 +34,13 @@ Manage workflows: push, pull, and evaluate.
33
34
  - [`eigenpal workflow dataset example delete [options] <workflow-id> <exampleId>`](#eigenpal-workflow-dataset-example-delete-options-workflow-id-exampleid)
34
35
  - [`eigenpal workflow dataset example get [options] <workflow-id> <exampleId>`](#eigenpal-workflow-dataset-example-get-options-workflow-id-exampleid)
35
36
  - [`eigenpal workflow dataset validate [options] [path]`](#eigenpal-workflow-dataset-validate-options-path)
37
+ - [`eigenpal workflow templates upload [options] <file>`](#eigenpal-workflow-templates-upload-options-file)
38
+ - [`eigenpal workflow templates list|ls [options]`](#eigenpal-workflow-templates-listls-options)
39
+ - [`eigenpal workflow templates get|inspect [options] <template-id>`](#eigenpal-workflow-templates-getinspect-options-template-id)
40
+ - [`eigenpal workflow templates download [options] <template-id>`](#eigenpal-workflow-templates-download-options-template-id)
41
+ - [`eigenpal workflow templates replace [options] <template-id> <file>`](#eigenpal-workflow-templates-replace-options-template-id-file)
42
+ - [`eigenpal workflow templates delete [options] <template-id>`](#eigenpal-workflow-templates-delete-options-template-id)
43
+ - [`eigenpal workflow templates smoke [options] <template>`](#eigenpal-workflow-templates-smoke-options-template)
36
44
  - [`eigenpal workflow experiment|exp list|ls [options] <workflow-id>`](#eigenpal-workflow-experimentexp-listls-options-workflow-id)
37
45
  - [`eigenpal workflow experiment|exp run [options] <workflow-id>`](#eigenpal-workflow-experimentexp-run-options-workflow-id)
38
46
  - [`eigenpal workflow experiment|exp status [options] <workflow-id> <batchId>`](#eigenpal-workflow-experimentexp-status-options-workflow-id-batchid)
@@ -41,6 +49,8 @@ Manage workflows: push, pull, and evaluate.
41
49
  - [`eigenpal workflow experiment|exp compare|diff [options] <batchIdA> <batchIdB>`](#eigenpal-workflow-experimentexp-comparediff-options-batchida-batchidb)
42
50
  - [`eigenpal workflow experiment|exp watch [options] <workflow-id> <batchId>`](#eigenpal-workflow-experimentexp-watch-options-workflow-id-batchid)
43
51
  - [`eigenpal workflow versions list|ls [options] <workflow-id>`](#eigenpal-workflow-versions-listls-options-workflow-id)
52
+ - [`eigenpal workflow versions create [options] <workflow-id>`](#eigenpal-workflow-versions-create-options-workflow-id)
53
+ - [`eigenpal workflow versions promote [options] <workflow-id> <versionId>`](#eigenpal-workflow-versions-promote-options-workflow-id-versionid)
44
54
  - [`eigenpal workflow versions restore [options] <workflow-id> <versionId>`](#eigenpal-workflow-versions-restore-options-workflow-id-versionid)
45
55
  - [`eigenpal workflow step-type list|ls [options]`](#eigenpal-workflow-step-type-listls-options)
46
56
  - [`eigenpal workflow step-type get [options] <type>`](#eigenpal-workflow-step-type-get-options-type)
@@ -71,6 +81,14 @@ workflow
71
81
  │ │ ├── delete <workflow-id> <exampleId>
72
82
  │ │ └── get <workflow-id> <exampleId>
73
83
  │ └── validate [path]
84
+ ├── templates
85
+ │ ├── upload <file>
86
+ │ ├── list|ls
87
+ │ ├── get|inspect <template-id>
88
+ │ ├── download <template-id>
89
+ │ ├── replace <template-id> <file>
90
+ │ ├── delete <template-id>
91
+ │ └── smoke <template>
74
92
  ├── experiment|exp
75
93
  │ ├── list|ls <workflow-id>
76
94
  │ ├── run <workflow-id>
@@ -81,6 +99,8 @@ workflow
81
99
  │ └── watch <workflow-id> <batchId>
82
100
  ├── versions
83
101
  │ ├── list|ls <workflow-id>
102
+ │ ├── create <workflow-id>
103
+ │ ├── promote <workflow-id> <versionId>
84
104
  │ └── restore <workflow-id> <versionId>
85
105
  ├── step-type
86
106
  │ ├── list|ls
@@ -129,6 +149,18 @@ workflow
129
149
  | `eigenpal workflow dataset example get [options] <workflow-id> <exampleId>` | Fetch one eval example with full triggerInput, expectedOutput, and metadata. |
130
150
  | `eigenpal workflow dataset validate [options] [path]` | Validate a dataset folder against the examples/<name>/{input,expected,meta} convention. Defaults to ./dataset/. |
131
151
 
152
+ ### Templates
153
+
154
+ | Command | Description |
155
+ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
156
+ | `eigenpal workflow templates upload [options] <file>` | Upload a DOCX or XLSX file as a new tmpl*… resource with an immutable tmpr*… revision. |
157
+ | `eigenpal workflow templates list\|ls [options]` | List workspace templates. |
158
+ | `eigenpal workflow templates get\|inspect [options] <template-id>` | Inspect a tmpl*… resource: current tmpr*…, format, checksum, tokens, grammar. |
159
+ | `eigenpal workflow templates download [options] <template-id>` | Download current (or pinned) template bytes. |
160
+ | `eigenpal workflow templates replace [options] <template-id> <file>` | Append an immutable revision and advance the tmpl\_… pointer. |
161
+ | `eigenpal workflow templates delete [options] <template-id>` | Delete the mutable tmpl*… pointer. Pinned tmpr*… revisions remain so workflows that set templateRevisionId still run. |
162
+ | `eigenpal workflow templates smoke [options] <template>` | Fill a local Office file or a tmpl\_… resource with a JSON fixture and write the result. Local files never contact the server. |
163
+
132
164
  ### Experiment
133
165
 
134
166
  | Command | Description |
@@ -143,10 +175,12 @@ workflow
143
175
 
144
176
  ### Versions
145
177
 
146
- | Command | Description |
147
- | ------------------------------------------------------------------------ | ------------------------------------------------ |
148
- | `eigenpal workflow versions list\|ls [options] <workflow-id>` | List historical workflow versions, newest first. |
149
- | `eigenpal workflow versions restore [options] <workflow-id> <versionId>` | Restore the workflow to a previous version. |
178
+ | Command | Description |
179
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
180
+ | `eigenpal workflow versions list\|ls [options] <workflow-id>` | List tagged workflow versions plus the current untagged snapshot when HEAD is untagged, newest first. |
181
+ | `eigenpal workflow versions create [options] <workflow-id>` | Create a tagged workflow version from YAML or by copying an existing snapshot. |
182
+ | `eigenpal workflow versions promote [options] <workflow-id> <versionId>` | Make an existing tagged workflow version current without creating another snapshot. |
183
+ | `eigenpal workflow versions restore [options] <workflow-id> <versionId>` | Restore a previous snapshot as a new untagged current version. Does not retag the source. |
150
184
 
151
185
  ### Step-type
152
186
 
@@ -225,14 +259,15 @@ Create or update a workflow from a YAML file.
225
259
 
226
260
  ### Options
227
261
 
228
- | Flag | Required | Default | Description |
229
- | ------------------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
230
- | `--file <yaml>` | no | | Path to YAML file |
231
- | `--workflow-id <id>` | no | | Update existing workflow (default: create new) |
232
- | `--bump <level>` | no | | Auto-bump from the server's current version: patch \| minor \| major. Mutually exclusive with `--version` and with a top-level `version:` in the YAML. |
233
- | `--set-version <semver>` | no | | Push at this exact semver (e.g. 1.4.0). Mutually exclusive with `--bump` and with a top-level `version:` in the YAML. (Named `--set-version` to avoid the global `-v, --version` flag.) |
234
- | `--base-url <url>` | no | | Server base URL |
235
- | `--json` | no | | Output the raw server response as JSON |
262
+ | Flag | Required | Default | Description |
263
+ | ---------------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
264
+ | `--file <yaml>` | no | | Path to YAML file |
265
+ | `--workflow-id <id>` | no | | Update existing workflow (default: create new) |
266
+ | `--bump <level>` | no | | Auto-bump from the server's current version: patch \| minor \| major. Mutually exclusive with `--version` and with a top-level `version:` in the YAML. |
267
+ | `--set-version <semver>` | no | | Push at this exact semver (e.g. 1.4.0). Mutually exclusive with `--bump` and with a top-level `version:` in the YAML. (Named `--set-version` to avoid the global `-v, --version` flag.) |
268
+ | `--allow-external-templates` | no | | Allow local template: paths whose real path is outside the workflow project directory (the folder that contains the YAML file). Off by default; ../ and symlink escapes are rejected. |
269
+ | `--base-url <url>` | no | | Server base URL |
270
+ | `--json` | no | | Output the raw server response as JSON |
236
271
 
237
272
  ### `eigenpal workflow move [options] <workflow-id>`
238
273
 
@@ -264,11 +299,12 @@ Local-only validation. Without [path]: runs the templated three-way check (./wor
264
299
 
265
300
  ### Options
266
301
 
267
- | Flag | Required | Default | Description |
268
- | ------------------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
269
- | `--dir <path>` | no | | Project root (defaults to cwd; resolves the three default paths from here) |
270
- | `--online` | no | | Also validate cross-workflow action.invoke-workflow references against the server (needs auth). Catches target-not-found, input type mismatches, missing/unknown input keys, undeclared output, and invoke cycles that local validation cannot see. |
271
- | `--base-url <url>` | no | | Server base URL |
302
+ | Flag | Required | Default | Description |
303
+ | ---------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
304
+ | `--dir <path>` | no | | Project root (defaults to cwd; resolves the three default paths from here) |
305
+ | `--online` | no | | Authenticate and also validate action.invoke-workflow targets, transform.template tmpl\_… references, and explicitly selected OCR/vision/text models against this tenant environment (existence, format, revision pairing, tokens vs data, XLSX {{ }} mistakes, configured catalog). Local template: paths are inspected on disk with or without this flag. Model checks use the configured catalog only — they do not probe live provider health. |
306
+ | `--allow-external-templates` | no | | Allow local template: paths whose real path is outside the workflow project directory (the folder that contains the YAML file). Off by default; ../ and symlink escapes are rejected. |
307
+ | `--base-url <url>` | no | | Server base URL |
272
308
 
273
309
  ### `eigenpal workflow clear-local [options] [examples...]`
274
310
 
@@ -484,6 +520,129 @@ Validate a dataset folder against the examples/<name>/{input,expected,meta} conv
484
520
  | ------ | -------- | -------- | ----------- |
485
521
  | `path` | no | no | |
486
522
 
523
+ ### `eigenpal workflow templates upload [options] <file>`
524
+
525
+ Upload a DOCX or XLSX file as a new tmpl*… resource with an immutable tmpr*… revision.
526
+
527
+ ### Arguments
528
+
529
+ | Name | Required | Variadic | Description |
530
+ | ------ | -------- | -------- | ----------- |
531
+ | `file` | yes | no | |
532
+
533
+ ### Options
534
+
535
+ | Flag | Required | Default | Description |
536
+ | ---------------------- | -------- | ------- | --------------------------------------------------------- |
537
+ | `--name <name>` | no | | Display name (defaults to the filename without extension) |
538
+ | `--description <text>` | no | | Optional description |
539
+ | `--base-url <url>` | no | | Server base URL |
540
+ | `--json` | no | | Output the raw server response as JSON |
541
+
542
+ ### `eigenpal workflow templates list|ls [options]`
543
+
544
+ List workspace templates.
545
+
546
+ ### Options
547
+
548
+ | Flag | Required | Default | Description |
549
+ | ------------------ | -------- | ------- | -------------------------------------- |
550
+ | `--limit <n>` | no | `50` | Page size |
551
+ | `--offset <n>` | no | `0` | Page offset |
552
+ | `--base-url <url>` | no | | Server base URL |
553
+ | `--json` | no | | Output the raw server response as JSON |
554
+
555
+ ### `eigenpal workflow templates get|inspect [options] <template-id>`
556
+
557
+ Inspect a tmpl*… resource: current tmpr*…, format, checksum, tokens, grammar.
558
+
559
+ ### Arguments
560
+
561
+ | Name | Required | Variadic | Description |
562
+ | ------------- | -------- | -------- | ----------- |
563
+ | `template-id` | yes | no | |
564
+
565
+ ### Options
566
+
567
+ | Flag | Required | Default | Description |
568
+ | ------------------ | -------- | ------- | -------------------------------------- |
569
+ | `--base-url <url>` | no | | Server base URL |
570
+ | `--json` | no | | Output the raw server response as JSON |
571
+
572
+ ### `eigenpal workflow templates download [options] <template-id>`
573
+
574
+ Download current (or pinned) template bytes.
575
+
576
+ ### Arguments
577
+
578
+ | Name | Required | Variadic | Description |
579
+ | ------------- | -------- | -------- | ----------- |
580
+ | `template-id` | yes | no | |
581
+
582
+ ### Options
583
+
584
+ | Flag | Required | Default | Description |
585
+ | ---------------------- | -------- | ------- | --------------------------------- |
586
+ | `--out <path>` | no | | Write to this file (required) |
587
+ | `--revision-id <tmpr>` | no | | Pin an immutable tmpr\_… revision |
588
+ | `--base-url <url>` | no | | Server base URL |
589
+
590
+ ### `eigenpal workflow templates replace [options] <template-id> <file>`
591
+
592
+ Append an immutable revision and advance the tmpl\_… pointer.
593
+
594
+ ### Arguments
595
+
596
+ | Name | Required | Variadic | Description |
597
+ | ------------- | -------- | -------- | ----------- |
598
+ | `template-id` | yes | no | |
599
+ | `file` | yes | no | |
600
+
601
+ ### Options
602
+
603
+ | Flag | Required | Default | Description |
604
+ | ------------------ | -------- | ------- | -------------------------------------- |
605
+ | `--base-url <url>` | no | | Server base URL |
606
+ | `--json` | no | | Output the raw server response as JSON |
607
+
608
+ ### `eigenpal workflow templates delete [options] <template-id>`
609
+
610
+ Delete the mutable tmpl*… pointer. Pinned tmpr*… revisions remain so workflows that set templateRevisionId still run.
611
+
612
+ ### Arguments
613
+
614
+ | Name | Required | Variadic | Description |
615
+ | ------------- | -------- | -------- | ----------- |
616
+ | `template-id` | yes | no | |
617
+
618
+ ### Options
619
+
620
+ | Flag | Required | Default | Description |
621
+ | ------------------ | -------- | ------- | -------------------------------------- |
622
+ | `--yes` | no | `false` | Required for non-TTY shells |
623
+ | `--base-url <url>` | no | | Server base URL |
624
+ | `--json` | no | | Output the raw server response as JSON |
625
+
626
+ ### `eigenpal workflow templates smoke [options] <template>`
627
+
628
+ Fill a local Office file or a tmpl\_… resource with a JSON fixture and write the result. Local files never contact the server.
629
+
630
+ ### Arguments
631
+
632
+ | Name | Required | Variadic | Description |
633
+ | ---------- | -------- | -------- | ----------- |
634
+ | `template` | yes | no | |
635
+
636
+ ### Options
637
+
638
+ | Flag | Required | Default | Description |
639
+ | ---------------------- | -------- | ------- | ------------------------------------------------------------ |
640
+ | `--data <file>` | yes | | Path to a JSON fixture file (object with template data keys) |
641
+ | `--out <path>` | yes | | Filled DOCX/XLSX output path |
642
+ | `--revision-id <tmpr>` | no | | When <template> is a tmpl\_… id, pin this revision |
643
+ | `--base-url <url>` | no | | Server base URL |
644
+ | `--json` | no | | Output the raw server response as JSON |
645
+
487
646
  ### `eigenpal workflow experiment|exp list|ls [options] <workflow-id>`
488
647
 
489
648
  List executions for the workflow, newest first.
@@ -629,7 +788,7 @@ Poll until terminal, then auto-pull results — replaces `status --watch` + `res
629
788
 
630
789
  ### `eigenpal workflow versions list|ls [options] <workflow-id>`
631
790
 
632
- List historical workflow versions, newest first.
791
+ List tagged workflow versions plus the current untagged snapshot when HEAD is untagged, newest first.
633
792
 
634
793
  ### Arguments
635
794
 
@@ -639,16 +798,56 @@ List historical workflow versions, newest first.
639
798
 
640
799
  ### Options
641
800
 
801
+ | Flag | Required | Default | Description |
802
+ | ------------------ | -------- | ------- | --------------------------------------------------------------------------------------------- |
803
+ | `--limit <n>` | no | `50` | Page size |
804
+ | `--offset <n>` | no | `0` | Page offset |
805
+ | `--base-url <url>` | no | | Server base URL |
806
+ | `--json` | no | | Print the sliced { data, total, limit, offset } envelope as JSON (not the raw server payload) |
807
+
808
+ ### `eigenpal workflow versions create [options] <workflow-id>`
809
+
810
+ Create a tagged workflow version from YAML or by copying an existing snapshot.
811
+
812
+ ### Arguments
813
+
814
+ | Name | Required | Variadic | Description |
815
+ | ------------- | -------- | -------- | ----------- |
816
+ | `workflow-id` | yes | no | |
817
+
818
+ ### Options
819
+
820
+ | Flag | Required | Default | Description |
821
+ | ---------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
822
+ | `--file <yaml>` | no | | Path to workflow YAML. Mutually exclusive with --from. |
823
+ | `--allow-external-templates` | no | | Allow local template paths outside the workflow project. Use only for trusted YAML. |
824
+ | `--from <version-id>` | no | | Existing version id to copy into a new tagged snapshot. Leaves the source tag unchanged. Mutually exclusive with --file. |
825
+ | `--set-version <semver>` | no | | Bare semver tag such as 1.4.0. Required when copying with --from. For --file, omit this flag if the YAML already has a top-level version: field. (Named --set-version to avoid the global -v, --version flag.) |
826
+ | `--no-activate` | no | | Keep the tagged version off live traffic until you promote it. Default is to make it current. Requires an existing current version. |
827
+ | `--base-url <url>` | no | | Server base URL |
828
+ | `--json` | no | | Output the raw server response as JSON |
829
+
830
+ ### `eigenpal workflow versions promote [options] <workflow-id> <versionId>`
831
+
832
+ Make an existing tagged workflow version current without creating another snapshot.
833
+
834
+ ### Arguments
835
+
836
+ | Name | Required | Variadic | Description |
837
+ | ------------- | -------- | -------- | ----------- |
838
+ | `workflow-id` | yes | no | |
839
+ | `versionId` | yes | no | |
840
+
841
+ ### Options
842
+
642
843
  | Flag | Required | Default | Description |
643
844
  | ------------------ | -------- | ------- | -------------------------------------- |
644
- | `--limit <n>` | no | `50` | Page size |
645
- | `--offset <n>` | no | `0` | Page offset |
646
845
  | `--base-url <url>` | no | | Server base URL |
647
846
  | `--json` | no | | Output the raw server response as JSON |
648
847
 
649
848
  ### `eigenpal workflow versions restore [options] <workflow-id> <versionId>`
650
849
 
651
- Restore the workflow to a previous version.
850
+ Restore a previous snapshot as a new untagged current version. Does not retag the source.
652
851
 
653
852
  ### Arguments
654
853
 
@@ -659,10 +858,11 @@ Restore the workflow to a previous version.
659
858
 
660
859
  ### Options
661
860
 
662
- | Flag | Required | Default | Description |
663
- | ------------------ | -------- | ------- | -------------------------------------- |
664
- | `--base-url <url>` | no | | Server base URL |
665
- | `--json` | no | | Output the raw server response as JSON |
861
+ | Flag | Required | Default | Description |
862
+ | ------------------ | -------- | ------- | ------------------------------------------------ |
863
+ | `--message <text>` | no | | Optional restore note stored on the new snapshot |
864
+ | `--base-url <url>` | no | | Server base URL |
865
+ | `--json` | no | | Output the raw server response as JSON |
666
866
 
667
867
  ### `eigenpal workflow step-type list|ls [options]`
668
868
 
@@ -29,6 +29,21 @@ eigenpal runs list <workflow-id> --type workflow --status failed --limit 5
29
29
  eigenpal runs get exec_…
30
30
  ```
31
31
 
32
+ If a `transform.template` fill looks wrong (leftover `{braces}`, empty
33
+ rows, or the wrong spreadsheet), smoke the Office file locally before
34
+ re-pushing. Local files never contact the server:
35
+
36
+ ```bash
37
+ eigenpal workflow templates smoke ./templates/roster.xlsx \
38
+ --data ./fixture.json --out ./filled.xlsx --json
39
+ ```
40
+
41
+ XLSX prototype rows use `{table:subjects.first_name}` in the spreadsheet.
42
+ YAML `data` mapping still uses `{{ steps.extract.output.subjects }}` —
43
+ do not put `{{ }}` in the Office file. `tmpl_…` smoke downloads current
44
+ bytes through `GET /v1/templates/:id/content` and fills on this machine;
45
+ there is no private remote-render route.
46
+
32
47
  ## 2. Pull the full execution
33
48
 
34
49
  ```bash
@@ -161,6 +176,27 @@ eigenpal runs compare <exec-a> <exec-b>
161
176
  Output: per-step status / Δ duration / output diff. Useful for spotting
162
177
  regressions between revisions of a workflow.
163
178
 
179
+ For agent runs with spreadsheet outputs, `runs compare` compares XLSX
180
+ workbooks by structured sheet content (headers, typed rows, sheet order) —
181
+ not raw ZIP bytes — so metadata-only rewrites still match when cell data
182
+ matches. Comparison scans the full workbook (within CLI safety limits); the
183
+ `--max-*` caps below apply only to `artifacts inspect`, not to `compare`.
184
+ When a workbook exceeds compare safety limits, the file diff is reported as
185
+ `inconclusive` and `--fail-on-diff` does not treat it as a match.
186
+
187
+ Inspect a downloaded or remote XLSX artifact as machine-readable JSON:
188
+
189
+ ```bash
190
+ eigenpal runs artifacts list <run-id>
191
+ eigenpal runs artifacts inspect <run-id> output/report.xlsx --json
192
+ eigenpal runs artifacts inspect <run-id> output/report.xlsx --sheet Summary,1 --max-rows 100 --json
193
+ eigenpal runs artifacts inspect --file ./report.xlsx --json
194
+ ```
195
+
196
+ The JSON payload includes `sheetNames`, per-sheet `dimensions`, ordered
197
+ `headers`, typed `rows`, and `truncated` flags when `--max-*` caps apply
198
+ (defaults: 20 sheets, 500 rows/sheet, 50 cols/sheet).
199
+
164
200
  For a freer-form diff, dump both and compare with `jq`:
165
201
 
166
202
  ```bash
@@ -23,7 +23,7 @@ implement at runtime. Server-side validation errors point you here.
23
23
 
24
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
- script, template, pdf-embed, xlsx-to-json). WASM sandboxed where applicable.
26
+ script, template, pdf-embed, xlsx-to-json, json-to-xlsx). WASM sandboxed where applicable.
27
27
  - `action.*` — external side effects (HTTP, invoke another workflow, website reader).
28
28
  - `control.*` — flow control (if, foreach, parallel, parallel_map, wait, fail).
29
29
 
@@ -35,13 +35,16 @@ the catalog tells you what fields it takes.
35
35
 
36
36
  | Use case | Step |
37
37
  | --------------------------------- | ------------------------------------------------- |
38
- | Read a PDF / DOCX / image | `ai.parse` |
38
+ | Read a PDF / DOCX / image (native-first) | `ai.parse` with `parseMode: native` or `native-or-ocr` |
39
+ | Read a PDF / DOCX / image (full OCR/vision) | `ai.parse` with `parseMode: ocr` or `vision` |
39
40
  | Pull a typed object from text | `ai.extract` with `config.schema` |
40
41
  | Pick one label from a fixed set | `ai.classify` with `config.labels` |
41
42
  | Reject bad inputs with a 4xx code | `control.fail` (often after `ai.classify`) |
42
43
  | Sum / filter / regex | `transform.script` (NOT Liquid) |
43
- | Render a DOCX template | `transform.template` |
44
+ | Render a DOCX or XLSX template (YAML workflow) | `transform.template` with `tmpl_...` or `./file.xlsx` |
45
+ | Fill a DOCX or XLSX template (runtime agent) | fill-template platform skill |
44
46
  | Convert XLSX to JSON | `transform.xlsx-to-json` |
47
+ | Convert JSON rows to XLSX | `transform.json-to-xlsx` |
45
48
  | Merge or combine objects | `transform.combine` / `transform.merge` |
46
49
  | Conditional execution | `if:` on the step (Liquid) or `control.if` |
47
50
  | Map over an input array | `forEach:` on the step or `control.foreach` |
@@ -51,6 +54,59 @@ the catalog tells you what fields it takes.
51
54
  | Call another workflow | `action.invoke-workflow` |
52
55
  | Fetch a webpage as markdown | `action.website-reader` |
53
56
 
57
+ ## Workspace vs Git templates
58
+
59
+ Two separate template systems share DOCX/XLSX placeholder syntax but not
60
+ references:
61
+
62
+ | Goal | Mechanism |
63
+ | --- | --- |
64
+ | YAML workflow produces a filled DOCX/XLSX | `transform.template` with `templateId: tmpl_...` or `template: ./templates/foo.xlsx` (CLI push uploads) |
65
+ | Runtime agent produces a filled DOCX/XLSX | fill-template platform skill against Git templates |
66
+
67
+ Git templates (`agents/<agent>/templates/<slug>/`, shared
68
+ `resources/templates/<slug>/`) never receive a `tmpl_...` ID and cannot be
69
+ used in `transform.template`. Manage Git templates through the agent source
70
+ loop — see the eigenpal skill's **Agent templates (Git source)** section.
71
+
72
+ Workspace templates are tenant-scoped `tmpl_...` resources with immutable
73
+ `tmpr_...` revisions. Manage them from the CLI (preferred) or Studio:
74
+
75
+ ```bash
76
+ eigenpal workflow templates upload ./templates/roster.xlsx --json
77
+ eigenpal workflow templates list --json
78
+ eigenpal workflow templates get tmpl_... --json
79
+ eigenpal workflow templates smoke ./templates/roster.xlsx --data ./fixture.json --out ./filled.xlsx
80
+ eigenpal workflow validate ./workflow.yaml --online
81
+ eigenpal workflow push --file workflow.yaml
82
+ ```
83
+
84
+ Author YAML with either a live id or a source-controlled path. Push uploads
85
+ the file (skipped when the SHA-256 still matches) and sends `templateId` +
86
+ current `templateRevisionId` to the server without rewriting the file on disk.
87
+
88
+ `templateId` must be `tmpl_...` when not using a local path; file IDs
89
+ (`file_...`) are rejected at validate/push.
90
+
91
+ In the Office file itself use `{placeholder}` and, for XLSX row expansion,
92
+ `{table:array.prop}`. `{{placeholder}}` is Liquid for YAML `data` mapping
93
+ only — putting it in an XLSX file fails at fill. Example prototype row:
94
+
95
+ ```yaml
96
+ - name: fill-roster
97
+ type: transform.template
98
+ with:
99
+ templateId: tmpl_...
100
+ data:
101
+ report_title: "{{ steps.extract.output.title }}"
102
+ subjects: "{{ steps.extract.output.subjects }}"
103
+ ```
104
+
105
+ Spreadsheet cells: `{report_title}`, `{table:subjects.first_name}`,
106
+ `{table:subjects.last_name}`. Null/blank cells stay empty; numbers and
107
+ booleans keep spreadsheet types; every sheet is filled; existing formulas
108
+ stay formulas.
109
+
54
110
  ## Liquid vs `transform.script`
55
111
 
56
112
  | Logic shape | Use |
@@ -356,7 +412,7 @@ _Generated from `STEP_SCHEMAS` in `@eigenpal/types/src/workflow/step-configs.ts`
356
412
 
357
413
  #### `ai.parse` — Parse Document
358
414
 
359
- Extract text from documents (PDF, DOCX, images) using OCR or vision models
415
+ Extract text from documents (PDF, DOCX, images) using native extraction, OCR, or vision models
360
416
 
361
417
  **Durable retry:** Provider request retries are separate; the workflow engine does not durably retry this step.
362
418
 
@@ -365,7 +421,7 @@ Extract text from documents (PDF, DOCX, images) using OCR or vision models
365
421
  | Field | Type | Required | Default | Description |
366
422
  | --- | --- | --- | --- | --- |
367
423
  | `input` | string | yes | | Storage reference or template expression for the document |
368
- | `parseMode` | `"ocr"` \| `"vision"` | no | | Base parser for PDF/image inputs. OCR is the default; vision uses the selected LLM. Text and Office files always use native parsing. |
424
+ | `parseMode` | `"ocr"` \| `"vision"` \| `"native"` \| `"native-or-ocr"` | no | | Base parser for PDF/image inputs. OCR is the default; vision uses the selected LLM. `native` extracts PDF embedded text only and never calls OCR or vision. `native-or-ocr` extracts native text, runs page-quality diagnostics, and requests OCR when pages have detectable anomalies (empty, U+FFFD, lone surrogates, forbidden controls, unassigned/noncharacter, heavy PUA). Page selection, subset egress, and billing are provider-dependent. Valid-looking wrong text and literal "?" are not flagged. Text and Office files always use local parsing. |
369
425
  | `ocrModel` | string | no | | OCR provider ID for PDF/image parsing |
370
426
  | `llmModel` | string | no | | LLM provider ID for vision-based parsing |
371
427
  | `figureModel` | string | no | | Vision model used only for the optional figure-description pass |
@@ -391,6 +447,7 @@ Extract text from documents (PDF, DOCX, images) using OCR or vision models
391
447
  | `parserType` | `"plaintext"` \| `"office"` \| `"llm-vision"` \| `"ocr"` | yes | | |
392
448
  | `parserVersion` | string | no | | |
393
449
  | `model` | string | no | | Model used (for LLM/OCR parsers) |
450
+ | `processingStrategy` | `"native"` \| `"ocr"` \| `"vision"` \| `"hybrid"` | no | | How this result was produced: `native` (local PDF text only), `ocr`, `vision`, or `hybrid` (native pages merged with OCR on fallback pages). |
394
451
  | `structured` | record<string, unknown> | no | | Canonical structured document with ordered blocks, regions, bounding boxes, tables, figures, and chunks when supported by the parser |
395
452
 
396
453
  #### `ai.extract` — Extract Data
@@ -637,7 +694,7 @@ Merge multiple named inputs into a single output
637
694
 
638
695
  #### `transform.template` — Fill Template
639
696
 
640
- Fill a DOCX template with data from previous steps. Select a template in the workflow builder or provide a template ID from your workspace.
697
+ Fill a DOCX or XLSX workspace template with data from previous steps. Reference a tmpl_... id, or a local ./templates/file.xlsx path (CLI push uploads it). Git agent templates use the fill-template skill instead and cannot be referenced here. File IDs (file_...) are not accepted. Placeholders are auto-detected. In the Office file use {placeholder} and {table:array.prop}; {{placeholder}} in an XLSX file is rejected ({{ }} is YAML Liquid only).
641
698
 
642
699
  **Durable retry:** Transforms, including those that write files, are not durably retried.
643
700
 
@@ -645,11 +702,13 @@ Fill a DOCX template with data from previous steps. Select a template in the wor
645
702
 
646
703
  | Field | Type | Required | Default | Description |
647
704
  | --- | --- | --- | --- | --- |
648
- | `templateId` | string | yes | | ID of the template from templates table |
649
- | `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 |
650
- | `outputFilename` | string | no | | Output filename - supports {{field}} syntax |
651
- | `highlightNotFound` | boolean | no | `true` | Highlight missing variables with red-colored text in the output document |
652
- | `notFoundText` | string | no | `"NOT FOUND"` | Text to display for missing variables when highlightNotFound is enabled |
705
+ | `templateId` | string | no | | Workspace template ID (tmpl_...) from the Templates table (DOCX or XLSX). Git agent templates cannot be referenced here. File IDs (file_...) are not accepted. Mutually exclusive with `template`. |
706
+ | `template` | string | no | | Local DOCX or XLSX path relative to the workflow YAML (e.g. ./templates/foo.xlsx). CLI push keeps the real path inside the workflow project unless you pass --allow-external-templates, uploads unmatched files as new tmpl_ resources, and does not rewrite the source YAML. Mutually exclusive with templateId. |
707
+ | `templateRevisionId` | string | no | | Immutable template revision ID (tmpr_...). Pin this when executions must keep using the same template bytes after replacements. Requires templateId; local `template` paths are pinned automatically on push. |
708
+ | `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. |
709
+ | `outputFilename` | string | no | | Output filename - supports {{field}} syntax; .docx or .xlsx extension is added if omitted |
710
+ | `highlightNotFound` | boolean | no | `true` | Highlight missing variables with red-colored text in the output document (DOCX only; ignored for XLSX) |
711
+ | `notFoundText` | string | no | `"NOT FOUND"` | Text to display for missing variables when highlightNotFound is enabled (DOCX only; ignored for XLSX) |
653
712
 
654
713
  **Output:** `object`
655
714
 
@@ -703,6 +762,31 @@ Convert XLSX spreadsheet to JSON array of row objects for use in scripts or down
703
762
  | `rows` | array<record<string, unknown>> | yes | | Array of row objects (first row = headers as keys) |
704
763
  | `fileId` | string | no | | File ID of stored CSV when outputCsv is true |
705
764
 
765
+ #### `transform.json-to-xlsx` — JSON to XLSX
766
+
767
+ Convert JSON rows into an XLSX spreadsheet. Pair with transform.xlsx-to-json. Formula-looking strings stay text; nested cell values are rejected.
768
+
769
+ **Durable retry:** Transforms, including those that write files, are not durably retried.
770
+
771
+ **Config** (in `step.with`):
772
+
773
+ | Field | Type | Required | Default | Description |
774
+ | --- | --- | --- | --- | --- |
775
+ | `filename` | string | no | | Output filename — supports LiquidJS; .xlsx is added if omitted |
776
+ | `columns` | array<object> | no | | Ordered columns for a single-sheet workbook. Use sheets for multiple sheets. |
777
+ | `rows` | string \| array<record<string, unknown>> | no | | Array of row objects, or a template expression that resolves to one |
778
+ | `sheets` | array<object> | no | | Multiple sheets. Do not combine with top-level columns/rows. |
779
+ | `limits` | object | no | | Optional workload caps that can only lower the server defaults. Omitted fields use the server defaults. |
780
+
781
+ **Output:** `object`
782
+
783
+ | Field | Type | Required | Default | Description |
784
+ | --- | --- | --- | --- | --- |
785
+ | `fileId` | string | yes | | File ID from the files table |
786
+ | `filename` | string | yes | | Sanitized output filename including .xlsx |
787
+ | `sheetCount` | integer | yes | | Number of sheets in the workbook |
788
+ | `sheets` | array<object> | yes | | Per-sheet name and row count |
789
+
706
790
  #### `transform.script` — Script
707
791
 
708
792
  Execute a TypeScript function in a QuickJS sandbox. Input keys become the function's parameter list, in declaration order, and the required `: R` return-type annotation IS this step's output schema: `inputs: { items, taxRate }` ⇒ `function script(items: …, taxRate: …): R { … }`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eigenpal/cli",
3
- "version": "0.12.2",
3
+ "version": "0.13.1",
4
4
  "description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
5
5
  "keywords": [
6
6
  "eigenpal",
@@ -58,10 +58,12 @@
58
58
  "openai": "^6.27.0",
59
59
  "picocolors": "^1.1.1",
60
60
  "quickjs-emscripten": "^0.32.0",
61
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
61
62
  "yaml": "^2.7.0",
62
63
  "zod": "^4.4.3"
63
64
  },
64
65
  "devDependencies": {
66
+ "@eigenpal/common": "workspace:*",
65
67
  "@eigenpal/types": "workspace:*",
66
68
  "@eigenpal/workflow-yaml": "workspace:*",
67
69
  "@t3-oss/env-core": "^0.13.11",