@eigenpal/cli 0.0.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +63 -2
  3. package/dist/cli.js +162159 -0
  4. package/dist/skill/SKILL.md +366 -0
  5. package/dist/skill/examples/hello-world/dataset/examples/sample/expected/output.json +4 -0
  6. package/dist/skill/examples/hello-world/dataset/examples/sample/input/arguments.json +3 -0
  7. package/dist/skill/examples/hello-world/evaluators.yaml +5 -0
  8. package/dist/skill/examples/hello-world/workflow.yaml +28 -0
  9. package/dist/skill/reference/cli/agent.md +7 -0
  10. package/dist/skill/reference/cli/auth.md +75 -0
  11. package/dist/skill/reference/cli/completion.md +13 -0
  12. package/dist/skill/reference/cli/init.md +63 -0
  13. package/dist/skill/reference/cli/skill.md +79 -0
  14. package/dist/skill/reference/cli/status.md +14 -0
  15. package/dist/skill/reference/cli/workflow.md +788 -0
  16. package/dist/skill/reference/dataset-format.md +251 -0
  17. package/dist/skill/reference/debugging.md +280 -0
  18. package/dist/skill/reference/evaluators.md +210 -0
  19. package/dist/skill/reference/step-exec.md +164 -0
  20. package/dist/skill/reference/step-types.md +519 -0
  21. package/dist/skill/reference/workflow-yaml.md +304 -0
  22. package/dist/templates/blank/README.md +22 -0
  23. package/dist/templates/blank/dataset/examples/sample/expected/output.json +4 -0
  24. package/dist/templates/blank/dataset/examples/sample/input/arguments.json +3 -0
  25. package/dist/templates/blank/evaluators.yaml +9 -0
  26. package/dist/templates/blank/workflow.yaml +39 -0
  27. package/dist/templates/pdf-extraction/README.md +20 -0
  28. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/README.md +5 -0
  29. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/expected/output.json +6 -0
  30. package/dist/templates/pdf-extraction/dataset/examples/sample-invoice/input/arguments.json +1 -0
  31. package/dist/templates/pdf-extraction/evaluators.yaml +5 -0
  32. package/dist/templates/pdf-extraction/workflow.yaml +38 -0
  33. package/dist/templates/text-classification/README.md +8 -0
  34. package/dist/templates/text-classification/dataset/examples/negative/expected/output.json +3 -0
  35. package/dist/templates/text-classification/dataset/examples/negative/input/arguments.json +3 -0
  36. package/dist/templates/text-classification/dataset/examples/positive/expected/output.json +3 -0
  37. package/dist/templates/text-classification/dataset/examples/positive/input/arguments.json +3 -0
  38. package/dist/templates/text-classification/evaluators.yaml +6 -0
  39. package/dist/templates/text-classification/workflow.yaml +35 -0
  40. package/package.json +35 -15
  41. package/index.js +0 -4
@@ -0,0 +1,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.