@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.
- package/LICENSE +201 -0
- package/README.md +63 -2
- package/dist/cli.js +162159 -0
- package/dist/skill/SKILL.md +366 -0
- package/dist/skill/examples/hello-world/dataset/examples/sample/expected/output.json +4 -0
- package/dist/skill/examples/hello-world/dataset/examples/sample/input/arguments.json +3 -0
- package/dist/skill/examples/hello-world/evaluators.yaml +5 -0
- package/dist/skill/examples/hello-world/workflow.yaml +28 -0
- package/dist/skill/reference/cli/agent.md +7 -0
- package/dist/skill/reference/cli/auth.md +75 -0
- package/dist/skill/reference/cli/completion.md +13 -0
- package/dist/skill/reference/cli/init.md +63 -0
- package/dist/skill/reference/cli/skill.md +79 -0
- package/dist/skill/reference/cli/status.md +14 -0
- package/dist/skill/reference/cli/workflow.md +788 -0
- package/dist/skill/reference/dataset-format.md +251 -0
- package/dist/skill/reference/debugging.md +280 -0
- package/dist/skill/reference/evaluators.md +210 -0
- package/dist/skill/reference/step-exec.md +164 -0
- package/dist/skill/reference/step-types.md +519 -0
- package/dist/skill/reference/workflow-yaml.md +304 -0
- package/dist/templates/blank/README.md +22 -0
- package/dist/templates/blank/dataset/examples/sample/expected/output.json +4 -0
- package/dist/templates/blank/dataset/examples/sample/input/arguments.json +3 -0
- package/dist/templates/blank/evaluators.yaml +9 -0
- package/dist/templates/blank/workflow.yaml +39 -0
- package/dist/templates/pdf-extraction/README.md +20 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/README.md +5 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/expected/output.json +6 -0
- package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/input/arguments.json +1 -0
- package/dist/templates/pdf-extraction/evaluators.yaml +5 -0
- package/dist/templates/pdf-extraction/workflow.yaml +38 -0
- package/dist/templates/text-classification/README.md +8 -0
- package/dist/templates/text-classification/dataset/examples/negative/expected/output.json +3 -0
- package/dist/templates/text-classification/dataset/examples/negative/input/arguments.json +3 -0
- package/dist/templates/text-classification/dataset/examples/positive/expected/output.json +3 -0
- package/dist/templates/text-classification/dataset/examples/positive/input/arguments.json +3 -0
- package/dist/templates/text-classification/evaluators.yaml +6 -0
- package/dist/templates/text-classification/workflow.yaml +35 -0
- package/package.json +35 -15
- package/index.js +0 -4
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# evaluators.yaml — required schema
|
|
2
|
+
|
|
3
|
+
Evaluators run after a workflow execution to grade its output. They
|
|
4
|
+
attach to a workflow via `workflow evaluators push` and run in batch via
|
|
5
|
+
`workflow experiment run`. The full Zod schema is
|
|
6
|
+
`EvalConfigYamlSchema` in `@eigenpal/types/src/eval/evaluator-config.ts`;
|
|
7
|
+
this is the agent-facing summary.
|
|
8
|
+
|
|
9
|
+
## Top-level shape
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
evaluators: # array of evaluator definitions, ≥1 entry
|
|
13
|
+
- name: ... # required, unique within this file, [a-z0-9][a-z0-9-_]*
|
|
14
|
+
type: ... # required, one of: exact-diff | llm-judge | custom-script
|
|
15
|
+
config: # required, shape depends on `type`
|
|
16
|
+
passThreshold: 0.95 # 0..1, optional (default 1.0)
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
# OPTIONAL global config
|
|
20
|
+
concurrency: 4 # max examples in parallel during `experiment run` (default 1)
|
|
21
|
+
judge: # optional shared model defaults for llm-judge evaluators
|
|
22
|
+
model: gpt-4o
|
|
23
|
+
temperature: 0
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Evaluator types
|
|
27
|
+
|
|
28
|
+
### `exact-diff` — JSON deep-diff against `expected/output.json`
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
- name: invoice-fields
|
|
32
|
+
type: exact-diff
|
|
33
|
+
config:
|
|
34
|
+
passThreshold: 1.0 # 1.0 = byte-equal; lower = allow some slack
|
|
35
|
+
paths: [invoiceNumber, totalAmount] # OPTIONAL — restrict to specific dotted paths
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `passThreshold` — number in `[0, 1]`. The diff produces a similarity
|
|
39
|
+
score; `score >= passThreshold` ⇒ `passed: true`.
|
|
40
|
+
- `paths` — array of dotted paths into the actual / expected outputs.
|
|
41
|
+
When set, only those paths are diffed. When absent, the whole object
|
|
42
|
+
is compared.
|
|
43
|
+
|
|
44
|
+
### `llm-judge` — LLM-as-judge scoring
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
- name: covenants-recall
|
|
48
|
+
type: llm-judge
|
|
49
|
+
config:
|
|
50
|
+
model: gpt-4o # string — the OpenAI model id
|
|
51
|
+
temperature: 0 # number — for reproducibility
|
|
52
|
+
passThreshold: 0.85
|
|
53
|
+
prompt: |
|
|
54
|
+
Score how well the actual output captures every covenant in the
|
|
55
|
+
expected output, on a scale of 0..1. Penalise missing covenants;
|
|
56
|
+
do not penalise over-extraction. Return JSON { score, rationale }.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The harness wraps your `prompt` and feeds:
|
|
60
|
+
|
|
61
|
+
- `actual` — the workflow's output
|
|
62
|
+
- `expected` — the example's `expected/output.json`
|
|
63
|
+
- `meta` — example metadata
|
|
64
|
+
|
|
65
|
+
It expects the model to return JSON with at least `score` (0..1) and
|
|
66
|
+
`rationale` (string). The fixed harness handles parse failures + retries.
|
|
67
|
+
|
|
68
|
+
### `custom-script` — JavaScript in the sandbox
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
- name: total-with-tolerance
|
|
72
|
+
type: custom-script
|
|
73
|
+
config:
|
|
74
|
+
passThreshold: 1.0
|
|
75
|
+
code: |
|
|
76
|
+
const a = actual.totalAmount ?? 0;
|
|
77
|
+
const e = expected.totalAmount ?? 0;
|
|
78
|
+
const score = Math.abs(a - e) <= 0.01 ? 1 : 0;
|
|
79
|
+
return { score, rationale: `actual=${a}, expected=${e}` };
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Same WASM sandbox as `transform.script` (5s wall clock, 10 MB heap, no
|
|
83
|
+
network). Receives `actual`, `expected`, `meta` in scope. Returns
|
|
84
|
+
`{ score: number, label?: string, rationale?: string }`.
|
|
85
|
+
|
|
86
|
+
## Pass / fail vs score
|
|
87
|
+
|
|
88
|
+
Every evaluator emits both:
|
|
89
|
+
|
|
90
|
+
- `score` — numeric, typically `[0, 1]`
|
|
91
|
+
- `passed` — boolean, derived from `score >= passThreshold`
|
|
92
|
+
|
|
93
|
+
The batch pass-rate counts examples where **every** evaluator on the
|
|
94
|
+
workflow passed. A failing single evaluator is enough to fail the
|
|
95
|
+
example overall.
|
|
96
|
+
|
|
97
|
+
## Per-example overrides
|
|
98
|
+
|
|
99
|
+
Most useful for skipping steps that need external state. Example's
|
|
100
|
+
`meta.json`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"overrides": {
|
|
105
|
+
"steps": {
|
|
106
|
+
"enrich_company": {
|
|
107
|
+
"companyName": "Acme Corp",
|
|
108
|
+
"registeredAt": "2020-01-01"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
When the workflow runs against this example, `enrich_company` is
|
|
116
|
+
short-circuited and the override JSON is used as the step's output.
|
|
117
|
+
The execution row records `overrideMode: 'skipped'` so you can audit
|
|
118
|
+
which steps were faked. See `dataset-format.md` for the full meta.json
|
|
119
|
+
schema.
|
|
120
|
+
|
|
121
|
+
## Validate before pushing
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
eigenpal workflow evaluators validate ./evaluators.yaml
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Reports field-level issues:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
✗ evaluators (./evaluators.yaml) — 1 issue
|
|
131
|
+
evaluators.0.config.passThreshold Number must be less than or equal to 1
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Push + run
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Push the evaluators (overwrites the workflow's existing config).
|
|
138
|
+
eigenpal workflow evaluators push <wf-id> --file evaluators.yaml
|
|
139
|
+
|
|
140
|
+
# Kick off a server-side batch eval over the whole pushed dataset.
|
|
141
|
+
# Returns { batchId, executionIds }.
|
|
142
|
+
eigenpal workflow experiment run <wf-id>
|
|
143
|
+
|
|
144
|
+
# Or restrict to a single example:
|
|
145
|
+
eigenpal workflow experiment run <wf-id> --example-id evx_…
|
|
146
|
+
|
|
147
|
+
# Watch progress.
|
|
148
|
+
eigenpal workflow experiment status <wf-id> --batch-id <id>
|
|
149
|
+
|
|
150
|
+
# Pull results when done.
|
|
151
|
+
eigenpal workflow experiment results <wf-id> --batch-id <id> \
|
|
152
|
+
--format csv --out results.csv
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The signed-URL pattern means large result sets don't go through the CLI
|
|
156
|
+
process — `--out` writes the file directly from the server's storage.
|
|
157
|
+
|
|
158
|
+
## Stabilizing flaky judges
|
|
159
|
+
|
|
160
|
+
LLM-judge scores have variance. To reduce flake:
|
|
161
|
+
|
|
162
|
+
- Pin a specific model id (`gpt-4o-2024-08-06` rather than `gpt-4o`).
|
|
163
|
+
- Set `temperature: 0`.
|
|
164
|
+
- Keep the prompt narrow — score one dimension per evaluator. Use
|
|
165
|
+
multiple judges instead of one omnibus prompt.
|
|
166
|
+
- For high-stakes thresholds, run the experiment ≥3 times and average.
|
|
167
|
+
|
|
168
|
+
<!-- GENERATED:EVALUATOR_CATALOG START -->
|
|
169
|
+
## Evaluator types — full reference
|
|
170
|
+
|
|
171
|
+
_Generated from `EvalConfigYamlSchema` in `@eigenpal/types/src/eval/evaluator-config.ts`. Do not hand-edit between the GENERATED fences — run `bun run --cwd packages/cli generate:skill`._
|
|
172
|
+
|
|
173
|
+
### Top-level `evaluators.yaml`
|
|
174
|
+
|
|
175
|
+
| Field | Type | Required | Default | Description |
|
|
176
|
+
| --- | --- | --- | --- | --- |
|
|
177
|
+
| `evaluators` | array<object> | no | `[]` | |
|
|
178
|
+
| `passThreshold` | number | no | `0.7` | |
|
|
179
|
+
|
|
180
|
+
### `exact-diff` — JSON deep-diff against expected output
|
|
181
|
+
|
|
182
|
+
| Field | Type | Required | Default | Description |
|
|
183
|
+
| --- | --- | --- | --- | --- |
|
|
184
|
+
| `numericTolerance` | number | no | `0.000001` | Maximum absolute difference between numeric values that still counts as equal. Use 1e-6 for floats, 0 for exact integer match. |
|
|
185
|
+
| `allowExtraFields` | boolean | no | `true` | When on, extra fields in the actual output don't fail the diff. Off = actual must match expected exactly. |
|
|
186
|
+
| `passThreshold` | number | no | `1` | Minimum diff score this evaluator must reach for a run to pass. 1.0 = perfect match required. |
|
|
187
|
+
| `paths` | array<string> | no | | Dot-paths to scope the diff to a subset of the expected tree. Empty = diff entire expected. Syntax: `header.id`, `lineItems[].total`, `lineItems[0].sku`. Use this when expected has noisy sections you don't want to score. |
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
### `llm-judge` — LLM-as-judge scoring
|
|
191
|
+
|
|
192
|
+
| Field | Type | Required | Default | Description |
|
|
193
|
+
| --- | --- | --- | --- | --- |
|
|
194
|
+
| `model` | string | no | | Which LLM grades the output. Falls back to the workspace's default LLM provider when unset. |
|
|
195
|
+
| `mode` | `"continuous"` \| `"discrete"` | no | `"continuous"` | Continuous = the LLM returns a free-form score in [0, 1]. Discrete = the LLM picks one of your labels and the score is looked up from the table. |
|
|
196
|
+
| `labels` | record<string, number> | no | | Allowed labels and the score each maps to. Required in discrete mode. The judge MUST return one of these labels; each score must be in [0, 1]. |
|
|
197
|
+
| `passThreshold` | number | no | `0.7` | Minimum judge score this evaluator must reach for a run to pass. |
|
|
198
|
+
| `promptExtension` | string | yes | | Required. Evaluation criteria appended to the judge prompt — describe what makes a good answer for this workflow (tone, completeness, edge cases). Cannot be empty: an empty prompt yields the model inventing its own criteria. |
|
|
199
|
+
| `docLabel` | string | no | `"output"` | Name the judge uses to refer to the output in its prompt (e.g. 'invoice', 'extraction'). Default is 'output'. |
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
### `custom-script` — JavaScript in the sandbox
|
|
203
|
+
|
|
204
|
+
| Field | Type | Required | Default | Description |
|
|
205
|
+
| --- | --- | --- | --- | --- |
|
|
206
|
+
| `code` | string | yes | | Sandboxed JavaScript. Receives `actual`, `expected`, `input` as variables and must return { score, label? } where score is in [0, 1]. |
|
|
207
|
+
| `timeoutMs` | integer | no | `5000` | Maximum wall-clock time the script can run before it's killed and the run is marked failed. |
|
|
208
|
+
| `memoryLimitMb` | integer | no | `10` | Maximum memory the sandbox may allocate. Increase if the script processes large arrays or strings. |
|
|
209
|
+
| `passThreshold` | number | no | `1` | Minimum script score this evaluator must reach for a run to pass. |
|
|
210
|
+
<!-- GENERATED:EVALUATOR_CATALOG END -->
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Run one step locally — `workflow step exec`
|
|
2
|
+
|
|
3
|
+
Tightest iteration loop in the CLI. One step, no server, no queue, no
|
|
4
|
+
DAG. Milliseconds per run. Use this when you're tweaking a single
|
|
5
|
+
step's logic; use `workflow execution run` when you need the whole
|
|
6
|
+
workflow.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
eigenpal workflow step exec <type> [flags]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`<type>` is any value from `eigenpal workflow step-type list`. Only
|
|
13
|
+
some types have a local runner today (table below). The rest exit 2
|
|
14
|
+
with a "not yet supported locally" message — they still need the
|
|
15
|
+
server.
|
|
16
|
+
|
|
17
|
+
## What runs locally today
|
|
18
|
+
|
|
19
|
+
| Step type | Runner | Notes |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| `transform.script` | QuickJS sandbox in-process | Same sandbox as prod. TDZ traps and timeout / memory limits reproduce here. |
|
|
22
|
+
| `ai.extract` | OpenAI SDK | Reads `WORKER_LLM_API_KEY` (or `OPENAI_API_KEY` for openai/openai-compatible providers) from env. Real network call. |
|
|
23
|
+
| anything else | — | Exits 2 with "isn't yet supported by 'workflow step exec' locally". |
|
|
24
|
+
|
|
25
|
+
If the runner you need isn't here yet, fall back to `workflow execution run`
|
|
26
|
+
on the server (slower loop, but covers every step type).
|
|
27
|
+
|
|
28
|
+
## Inputs — `--inputs k=v`
|
|
29
|
+
|
|
30
|
+
Repeatable. Three value forms:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
--inputs limit=10 # inline literal (string; numeric strings stay strings)
|
|
34
|
+
--inputs items=@items.json # file contents (JSON-parsed if valid; raw text otherwise)
|
|
35
|
+
--inputs payload=@- # stdin (the `@` prefix is required; bare `-` is a literal)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For `transform.script`, every `--inputs k=v` becomes a top-level
|
|
39
|
+
binding in the sandbox (so `k` is referenced as `k` inside the code,
|
|
40
|
+
mirroring runtime semantics — including TDZ traps for `const x = x || []`).
|
|
41
|
+
|
|
42
|
+
For `ai.extract`, `--inputs input=…` is the document text.
|
|
43
|
+
|
|
44
|
+
## Config — `--config-json` or `--config-file`
|
|
45
|
+
|
|
46
|
+
Mutually exclusive. The shape is whatever the step type's `with:` block
|
|
47
|
+
accepts (see `eigenpal workflow step-type get <type>`).
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
--config-json '{"code":"return items.reduce((s,i)=>s+i.v,0)"}'
|
|
51
|
+
--config-file ./step-config.json
|
|
52
|
+
--config-file - # config from stdin (e.g. piped from `workflow pull`)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Output
|
|
56
|
+
|
|
57
|
+
Goes to **stdout as JSON**. Status messages go to stderr — pipe stdout
|
|
58
|
+
into `jq` cleanly:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
eigenpal workflow step exec transform.script --config-file cfg.json --inputs items=@arr.json | jq '.'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Output schema validation — `--output-schema`
|
|
65
|
+
|
|
66
|
+
Optional. Pass a JSON Schema file:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
--output-schema ./expected-schema.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When omitted, the CLI falls back to the step type's built-in
|
|
73
|
+
`outputSchema` from `STEP_SCHEMAS` — so even without the flag, output
|
|
74
|
+
shape gets checked.
|
|
75
|
+
|
|
76
|
+
A violation **exits 2** with a machine-readable envelope on stderr:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{ "code": "output_schema_violation", "issues": [
|
|
80
|
+
{ "field": "/total", "message": "must be number" }
|
|
81
|
+
]}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`field` is the JSON Pointer to the offending value (Ajv's `instancePath`,
|
|
85
|
+
remapped to `field` for stability) or `<root>` if the whole output is
|
|
86
|
+
wrong. Agents can parse this directly — no text scraping.
|
|
87
|
+
|
|
88
|
+
## Resource limits (transform.script only)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
--timeout-ms 10000 # default 5000 (5 s wall clock)
|
|
92
|
+
--memory-mb 25 # default 10 MB heap
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use these when iterating on a memory-heavy reducer. They override the
|
|
96
|
+
config's `timeout` / `memoryLimit` fields just for this run.
|
|
97
|
+
|
|
98
|
+
## Worked examples
|
|
99
|
+
|
|
100
|
+
### Tweak a JS reducer until tests pass
|
|
101
|
+
|
|
102
|
+
Put the config in a file so the JS doesn't need escaping:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cat > cfg.json <<'EOF'
|
|
106
|
+
{ "code": "return items.reduce((sum, i) => sum + i.value, 0)" }
|
|
107
|
+
EOF
|
|
108
|
+
echo '[{"value":1},{"value":2},{"value":3}]' > items.json
|
|
109
|
+
|
|
110
|
+
eigenpal workflow step exec transform.script --config-file cfg.json --inputs items=@items.json
|
|
111
|
+
# 6
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
For a one-liner without a config file, `--config-json '{"code":"..."}'`
|
|
115
|
+
works too — but mind the shell escaping.
|
|
116
|
+
|
|
117
|
+
### Run an extract once with sample text
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
cat > extract.json <<'EOF'
|
|
121
|
+
{
|
|
122
|
+
"prompt": "Extract the invoice total as a number.",
|
|
123
|
+
"schema": { "type": "object", "properties": { "total": { "type": "number" } } }
|
|
124
|
+
}
|
|
125
|
+
EOF
|
|
126
|
+
|
|
127
|
+
eigenpal workflow step exec ai.extract \
|
|
128
|
+
--config-file extract.json \
|
|
129
|
+
--inputs input=@invoice.txt
|
|
130
|
+
# {"total": 1234.56}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Pipe config straight from a workflow YAML
|
|
134
|
+
|
|
135
|
+
When you've already pushed a workflow and want to re-run one of its
|
|
136
|
+
steps locally:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
eigenpal workflow pull wf_abc > workflow.yaml
|
|
140
|
+
jq '.steps[] | select(.name == "extract") | .with' workflow.yaml \
|
|
141
|
+
| eigenpal workflow step exec ai.extract --config-file - --inputs input=@invoice.txt
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Exit codes
|
|
145
|
+
|
|
146
|
+
| Code | When |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| 0 | Step ran; output validated. |
|
|
149
|
+
| 1 | Runtime crash (sandbox blew up; `ai.extract` network failure; unexpected exception). |
|
|
150
|
+
| 2 | Misuse OR `output_schema_violation` (envelope on stderr) OR unknown / unsupported step type. |
|
|
151
|
+
|
|
152
|
+
Exit 2 is always recoverable — fix your input / config / schema and
|
|
153
|
+
re-run. Exit 1 means something is broken upstream (network, runtime,
|
|
154
|
+
your code).
|
|
155
|
+
|
|
156
|
+
## Discovery
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
eigenpal workflow step-type list # all registered types
|
|
160
|
+
eigenpal workflow step-type get <type> # config + output schema
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The catalog is generated from `STEP_SCHEMAS` in `@eigenpal/types` and
|
|
164
|
+
is always in sync with what the deployment supports.
|