@eigenpal/cli 0.10.15 → 0.10.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @eigenpal/cli
2
2
 
3
+ ## 0.10.16
4
+
5
+ ### Patch Changes
6
+
7
+ - b1f0bca: `eigenpal run <workflow> --example <name>` now actually grades the example
8
+ instead of printing `PASS` whenever the run merely finished. It runs the
9
+ workflow's current published version on the server's stored dataset example, so
10
+ configured evaluators run automatically and the CLI surfaces the real weighted
11
+ score and per-evaluator pass/fail. Workflows with no evaluators fall back to a
12
+ structural diff of the output against the example's stored expected output. The
13
+ summary reports two separate signals: execution health (`ok`/`errored`) and
14
+ accuracy. Add `--fail-on-mismatch` to exit non-zero when a graded example fails.
15
+
16
+ Because the run uses the server-side example, push your latest dataset and
17
+ evaluators (`eigenpal workflow dataset push`, `eigenpal workflow evaluators
18
+ push`) before running `--example`.
19
+
3
20
  ## 0.10.9
4
21
 
5
22
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -183722,7 +183722,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
183722
183722
  // package.json
183723
183723
  var package_default = {
183724
183724
  name: "@eigenpal/cli",
183725
- version: "0.10.15",
183725
+ version: "0.10.16",
183726
183726
  description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
183727
183727
  keywords: [
183728
183728
  "eigenpal",
@@ -184205,9 +184205,6 @@ function resolveProfileKey(name, profiles) {
184205
184205
 
184206
184206
  // src/lib/ui.ts
184207
184207
  var import_picocolors = __toESM(require_picocolors(), 1);
184208
- function isTTY() {
184209
- return Boolean(process.stdout.isTTY);
184210
- }
184211
184208
  var ui = {
184212
184209
  ok: import_picocolors.default.green,
184213
184210
  err: import_picocolors.default.red,
@@ -190289,10 +190286,6 @@ function formatEigenpalDirIfAvailable(dir) {
190289
190286
  });
190290
190287
  }
190291
190288
 
190292
- // src/commands/workflow/exec.ts
190293
- init_src();
190294
- import { join as join9 } from "path";
190295
-
190296
190289
  // src/lib/execution-artifacts.ts
190297
190290
  import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
190298
190291
  import { join as join7 } from "path";
@@ -190418,363 +190411,318 @@ async function writeExecutionArtifacts(client, exampleDir, payload) {
190418
190411
  return executionsDir;
190419
190412
  }
190420
190413
 
190421
- // src/lib/payload.ts
190422
- init_src();
190423
- import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
190424
- import { join as join8 } from "path";
190425
- var DATASET_EXAMPLES_DIR = ["dataset", "examples"];
190426
- function resolveEvalBaseDir(dir) {
190427
- const candidates = [join8(dir, ...DATASET_EXAMPLES_DIR), join8(...DATASET_EXAMPLES_DIR)];
190428
- for (const candidate of candidates) {
190429
- if (existsSync14(candidate))
190430
- return candidate;
190414
+ // src/lib/grade-example.ts
190415
+ var MAX_VALUE_LEN = 60;
190416
+ function preview(value) {
190417
+ let text;
190418
+ try {
190419
+ text = typeof value === "string" ? value : JSON.stringify(value);
190420
+ } catch {
190421
+ text = String(value);
190431
190422
  }
190432
- return null;
190423
+ if (text === undefined)
190424
+ text = String(value);
190425
+ return text.length > MAX_VALUE_LEN ? `${text.slice(0, MAX_VALUE_LEN)}…` : text;
190433
190426
  }
190434
- function getExampleNames(dir, exampleNamesFilter) {
190435
- const baseDir = resolveEvalBaseDir(dir);
190436
- if (!baseDir) {
190437
- throw new Error(`No dataset/examples directory found at ${join8(dir, ...DATASET_EXAMPLES_DIR)}. Run \`eigenpal init workflow <name>\` to scaffold one.`);
190438
- }
190439
- const all = readdirSync4(baseDir).filter((name) => statSync4(join8(baseDir, name)).isDirectory() && existsSync14(join8(baseDir, name, "input.json"))).sort((a5, b8) => a5.localeCompare(b8, undefined, { numeric: true }));
190440
- if (!exampleNamesFilter?.length)
190441
- return all;
190442
- return all.filter((n5) => exampleNamesFilter.includes(n5));
190427
+ function isPlainObject3(value) {
190428
+ return value !== null && typeof value === "object" && !Array.isArray(value);
190443
190429
  }
190444
- function readInput(exampleDir) {
190445
- const canonicalPath = join8(exampleDir, "input.json");
190446
- return readCanonicalInput(exampleDir, canonicalPath);
190430
+ function joinPath(path15, key2) {
190431
+ return path15 ? `${path15}.${key2}` : key2;
190447
190432
  }
190448
- function readCanonicalInput(exampleDir, inputPath) {
190449
- const raw = JSON.parse(readFileSync13(inputPath, "utf-8"));
190450
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
190451
- throw new Error(`${inputPath} must contain a JSON object.`);
190452
- }
190453
- const scalars = {};
190454
- const files = [];
190455
- for (const [key2, value] of Object.entries(raw)) {
190456
- const refs = topLevelFileRefs(value);
190457
- if (refs.length === 0) {
190458
- scalars[key2] = materializeNestedFileRefs(exampleDir, inputPath, value);
190459
- continue;
190433
+ function walk2(expected, actual, path15, diffs) {
190434
+ if (Array.isArray(expected)) {
190435
+ if (!Array.isArray(actual)) {
190436
+ diffs.push(`${path15 || "(root)"}: expected array, got ${preview(actual)}`);
190437
+ return;
190460
190438
  }
190461
- for (const ref of refs) {
190462
- const validation2 = validateScopedArtifactPath(ref, { allowedRoots: ["input"] });
190463
- if (!validation2.ok) {
190464
- throw new Error(`${inputPath}: ${key2} must reference files under input/ without path traversal.`);
190465
- }
190466
- const relative3 = validation2.value.segments.slice(1).join("/");
190467
- const filePath = join8(exampleDir, validation2.value.path);
190468
- const filename = validation2.value.segments.at(-1) ?? relative3;
190469
- files.push({
190470
- argument: key2,
190471
- filename,
190472
- content: readFileSync13(filePath),
190473
- mimeType: guessMimeType(filename)
190474
- });
190439
+ if (expected.length !== actual.length) {
190440
+ diffs.push(`${path15 || "(root)"}: length ${expected.length} != ${actual.length}`);
190475
190441
  }
190476
- }
190477
- return { scalars, files };
190478
- }
190479
- function materializeNestedFileRefs(exampleDir, inputPath, value) {
190480
- if (isScopedFileRef2(value)) {
190481
- const validation2 = validateScopedArtifactPath(value.$file, { allowedRoots: ["input"] });
190482
- if (!validation2.ok) {
190483
- throw new Error(`${inputPath}: $file refs must reference files under input/.`);
190442
+ const overlap = Math.min(expected.length, actual.length);
190443
+ for (let i5 = 0;i5 < overlap; i5++) {
190444
+ walk2(expected[i5], actual[i5], `${path15}[${i5}]`, diffs);
190484
190445
  }
190485
- const filename = validation2.value.segments.at(-1) ?? "file";
190486
- const filePath = join8(exampleDir, validation2.value.path);
190487
- return {
190488
- $inline: {
190489
- filename,
190490
- mimeType: guessMimeType(filename) ?? "application/octet-stream",
190491
- base64: readFileSync13(filePath).toString("base64")
190492
- }
190493
- };
190494
- }
190495
- if (Array.isArray(value)) {
190496
- return value.map((item) => materializeNestedFileRefs(exampleDir, inputPath, item));
190446
+ return;
190497
190447
  }
190498
- if (value && typeof value === "object") {
190499
- if (hasFileKey(value)) {
190500
- throw new Error('file refs must be exact { "$file": "input/..." } objects.');
190448
+ if (isPlainObject3(expected)) {
190449
+ if (!isPlainObject3(actual)) {
190450
+ diffs.push(`${path15 || "(root)"}: expected object, got ${preview(actual)}`);
190451
+ return;
190501
190452
  }
190502
- const out = {};
190503
- for (const [key2, item] of Object.entries(value)) {
190504
- out[key2] = materializeNestedFileRefs(exampleDir, inputPath, item);
190453
+ for (const [key2, value] of Object.entries(expected)) {
190454
+ const childPath = joinPath(path15, key2);
190455
+ if (!(key2 in actual)) {
190456
+ diffs.push(`${childPath}: missing in output`);
190457
+ continue;
190458
+ }
190459
+ walk2(value, actual[key2], childPath, diffs);
190505
190460
  }
190506
- return out;
190507
- }
190508
- return value;
190509
- }
190510
- function topLevelFileRefs(value) {
190511
- if (isScopedFileRef2(value))
190512
- return [value.$file];
190513
- if (hasFileKey(value)) {
190514
- throw new Error('file refs must be exact { "$file": "input/..." } objects.');
190461
+ return;
190515
190462
  }
190516
- if (Array.isArray(value) && value.every(isScopedFileRef2))
190517
- return value.map((item) => item.$file);
190518
- if (Array.isArray(value) && value.some(hasFileKey)) {
190519
- throw new Error('file arrays must contain only exact { "$file": "input/..." } entries.');
190463
+ if (!Object.is(expected, actual)) {
190464
+ diffs.push(`${path15 || "(root)"}: ${preview(expected)} != ${preview(actual)}`);
190520
190465
  }
190521
- return [];
190522
190466
  }
190523
- function isScopedFileRef2(value) {
190524
- return value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 1 && typeof value.$file === "string";
190525
- }
190526
- function hasFileKey(value) {
190527
- return value !== null && typeof value === "object" && !Array.isArray(value) && "$file" in value;
190467
+ function gradeAgainstExpected(expected, actual) {
190468
+ const diffs = [];
190469
+ walk2(expected, actual, "", diffs);
190470
+ return { matched: diffs.length === 0, diffs };
190528
190471
  }
190529
- function readOverrides(exampleDir) {
190530
- const metaPath = join8(exampleDir, "meta.json");
190531
- if (!existsSync14(metaPath))
190532
- return null;
190533
- const raw = JSON.parse(readFileSync13(metaPath, "utf-8"));
190534
- const ov2 = raw.overrides;
190535
- if (ov2 && typeof ov2 === "object" && "steps" in ov2 && typeof ov2.steps === "object") {
190536
- return ov2;
190472
+
190473
+ // src/lib/payload.ts
190474
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync13, statSync as statSync4 } from "fs";
190475
+ import { join as join8 } from "path";
190476
+ var DATASET_EXAMPLES_DIR = ["dataset", "examples"];
190477
+ function resolveEvalBaseDir(dir) {
190478
+ const candidates = [join8(dir, ...DATASET_EXAMPLES_DIR), join8(...DATASET_EXAMPLES_DIR)];
190479
+ for (const candidate of candidates) {
190480
+ if (existsSync14(candidate))
190481
+ return candidate;
190537
190482
  }
190538
190483
  return null;
190539
190484
  }
190540
- function buildExamplePayload(exampleDir) {
190541
- const { scalars, files } = readInput(exampleDir);
190542
- return {
190543
- scalars,
190544
- files,
190545
- overrides: readOverrides(exampleDir)
190546
- };
190547
- }
190548
190485
 
190549
- // src/lib/progress-lines.ts
190550
- var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
190551
- var SPINNER_INTERVAL_MS = 120;
190552
- function writeLineInPlace(text) {
190553
- process.stdout.write(`\r\x1B[K${text}`);
190486
+ // src/commands/workflow/eval-example.ts
190487
+ var RUN_POLL_INTERVAL_MS = 2000;
190488
+ var RUN_MAX_WAIT_MS = 5 * 60 * 1000;
190489
+ var EVAL_GRACE_MS = 90 * 1000;
190490
+ var EVAL_POLL_INTERVAL_MS = 2000;
190491
+ var EXAMPLE_PAGE_SIZE = 100;
190492
+ var TERMINAL = new Set(["completed", "failed", "cancelled", "rejected", "timeout"]);
190493
+ function fmtScore(score) {
190494
+ return typeof score === "number" ? score.toFixed(2) : "n/a";
190554
190495
  }
190555
- function cursorUp(n5) {
190556
- if (n5 > 0)
190557
- process.stdout.write(`\x1B[${n5}A`);
190496
+ function toNumberOrNull(value) {
190497
+ if (typeof value === "number")
190498
+ return Number.isFinite(value) ? value : null;
190499
+ if (typeof value === "string") {
190500
+ const parsed = Number(value);
190501
+ return Number.isFinite(parsed) ? parsed : null;
190502
+ }
190503
+ return null;
190558
190504
  }
190559
- function cursorDown(n5) {
190560
- if (n5 > 0)
190561
- process.stdout.write(`\x1B[${n5}B`);
190505
+ function runOutput(run) {
190506
+ return run.output !== undefined ? run.output : run.result?.output;
190562
190507
  }
190563
- function updateLineAt(lineIndex, totalLines, text) {
190564
- const up2 = totalLines - lineIndex;
190565
- cursorUp(up2);
190566
- writeLineInPlace(text);
190567
- cursorDown(up2);
190508
+ async function fetchHasEvaluators(client, workflowId) {
190509
+ const cfg = await client.get(`/api/v1/automations/${workflowId}/evaluators`);
190510
+ return Array.isArray(cfg.config?.evaluators) && cfg.config.evaluators.length > 0;
190568
190511
  }
190569
- function createProgressLines(options8) {
190570
- const {
190571
- interactive,
190572
- lineLabels,
190573
- waitingLabel = "waiting",
190574
- runningLabel = "running",
190575
- dim: dim2 = (s5) => s5,
190576
- spinnerStyle = (s5) => s5
190577
- } = options8;
190578
- const totalLines = lineLabels.length;
190579
- const runningLabels = new Map;
190580
- let updateLock = Promise.resolve();
190581
- let spinnerInterval = null;
190582
- let spinnerFrame = 0;
190583
- const scheduleUpdate = (fn9) => {
190584
- const run = updateLock.then(() => fn9());
190585
- updateLock = run;
190586
- return run;
190587
- };
190588
- const start = () => {
190589
- if (!interactive || totalLines === 0)
190590
- return;
190591
- const wait = dim2(waitingLabel);
190592
- for (const label2 of lineLabels) {
190593
- process.stdout.write(`${label2} ${wait}
190594
- `);
190595
- }
190596
- };
190597
- const setRunning = (lineIndex, customLabel) => {
190598
- if (!interactive)
190599
- return;
190600
- runningLabels.set(lineIndex, customLabel ?? runningLabel);
190601
- if (spinnerInterval === null) {
190602
- spinnerInterval = setInterval(() => {
190603
- updateLock = updateLock.then(() => {
190604
- if (runningLabels.size === 0) {
190605
- if (spinnerInterval) {
190606
- clearInterval(spinnerInterval);
190607
- spinnerInterval = null;
190608
- }
190609
- return;
190610
- }
190611
- const frame = SPINNER_FRAMES[spinnerFrame++ % SPINNER_FRAMES.length];
190612
- for (const [i5, label2] of runningLabels) {
190613
- const lineLabel = lineLabels[i5] ?? "";
190614
- updateLineAt(i5, totalLines, `${lineLabel} ${label2} ${spinnerStyle(frame)}`);
190615
- }
190616
- });
190617
- }, SPINNER_INTERVAL_MS);
190512
+ async function resolveExample(client, workflowId, name) {
190513
+ let offset = 0;
190514
+ for (;; ) {
190515
+ const page = await client.get(`/api/v1/automations/${workflowId}/examples`, {
190516
+ limit: String(EXAMPLE_PAGE_SIZE),
190517
+ offset: String(offset)
190518
+ });
190519
+ const rows = page.data ?? [];
190520
+ const hit = rows.find((r5) => r5.name === name);
190521
+ if (hit)
190522
+ return { id: hit.id, expected: hit.expected };
190523
+ if (rows.length < EXAMPLE_PAGE_SIZE)
190524
+ return null;
190525
+ offset += rows.length;
190526
+ }
190527
+ }
190528
+ async function pollRunTerminal(client, runId) {
190529
+ const start = Date.now();
190530
+ const base = `/api/v1/runs/${encodeURIComponent(runId)}`;
190531
+ for (;; ) {
190532
+ const run = await client.get(base);
190533
+ const status = run.execution?.status ?? run.status;
190534
+ if (run.finished || status && TERMINAL.has(status)) {
190535
+ return await client.get(`${base}?expand=execution`);
190618
190536
  }
190619
- };
190620
- const setDone = async (lineIndex, text, extraLines = []) => {
190621
- if (interactive) {
190622
- runningLabels.delete(lineIndex);
190623
- await scheduleUpdate(() => updateLineAt(lineIndex, totalLines, text));
190624
- for (const line3 of extraLines)
190625
- console.log(line3);
190626
- } else {
190627
- console.log(text);
190628
- for (const line3 of extraLines)
190629
- console.log(line3);
190537
+ if (Date.now() - start > RUN_MAX_WAIT_MS) {
190538
+ return { ...run, status: "timeout", execution: { status: "timeout" } };
190630
190539
  }
190631
- };
190632
- return {
190633
- totalLines,
190634
- start,
190635
- setRunning,
190636
- setDone
190637
- };
190638
- }
190639
-
190640
- // src/commands/workflow/exec.ts
190641
- var POLL_INTERVAL_MS = 2000;
190642
- var MAX_POLL_MS = 5 * 60 * 1000;
190643
- var DEFAULT_CONCURRENCY = 3;
190644
- function runTerminalOutput(run) {
190645
- return run.output !== undefined ? run.output : run.result?.output;
190646
- }
190647
- function runTerminalError(run) {
190648
- const error52 = run.error !== undefined ? run.error : run.result?.error;
190649
- return error52 ?? undefined;
190540
+ await new Promise((r5) => setTimeout(r5, RUN_POLL_INTERVAL_MS));
190541
+ }
190650
190542
  }
190651
- async function pollExecution(client, executionId) {
190543
+ async function pollEvalRollup(client, runId) {
190652
190544
  const start = Date.now();
190653
- while (Date.now() - start < MAX_POLL_MS) {
190654
- const run = await client.get(`/api/v1/runs/${executionId}`);
190655
- const status = run.execution?.status ?? run.status;
190656
- if (run.finished || isTerminalExecutionStatus(status)) {
190657
- return {
190658
- executionId: run.id,
190659
- status: status ?? (run.error ? "failed" : "completed"),
190660
- output: runTerminalOutput(run),
190661
- error: runTerminalError(run)
190662
- };
190545
+ for (;; ) {
190546
+ const run = await client.get(`/api/v1/runs/${encodeURIComponent(runId)}`);
190547
+ if (run.eval && run.eval.passed != null) {
190548
+ return { score: run.eval.score ?? null, passed: run.eval.passed };
190663
190549
  }
190664
- await new Promise((r5) => setTimeout(r5, POLL_INTERVAL_MS));
190550
+ if (Date.now() - start > EVAL_GRACE_MS)
190551
+ return null;
190552
+ await new Promise((r5) => setTimeout(r5, EVAL_POLL_INTERVAL_MS));
190665
190553
  }
190666
- return { executionId, status: "timeout", error: "Execution timed out" };
190667
190554
  }
190668
- function toExecutionArtifactPayload(run) {
190669
- const status = run.execution?.status ?? run.status ?? "unknown";
190555
+ async function fetchEvalRows(client, workflowId, batchId, runId) {
190556
+ const detail = await client.get(`/api/v1/automations/${workflowId}/experiments/${batchId}`);
190557
+ return detail.resultsByRun?.[runId] ?? [];
190558
+ }
190559
+ function toArtifactPayload(run, runId) {
190670
190560
  return {
190671
- executionId: run.id,
190672
- status,
190561
+ executionId: runId,
190562
+ status: run.execution?.status ?? run.status ?? "unknown",
190673
190563
  createdAt: run.timing?.createdAt ?? new Date().toISOString(),
190674
190564
  completedAt: run.timing?.completedAt ?? null,
190675
190565
  error: run.error !== undefined ? run.error : run.result?.error ?? null,
190676
- output: runTerminalOutput(run),
190677
- stepExecutions: run.execution?.steps ?? []
190566
+ output: runOutput(run),
190567
+ stepExecutions: []
190678
190568
  };
190679
190569
  }
190680
- async function runExec(client, dir, workflowIdOrSlug, exampleNames, options8 = {}) {
190681
- const workflowId = await resolveWorkflowId(client, workflowIdOrSlug);
190682
- const names = getExampleNames(dir, exampleNames.length ? exampleNames : undefined);
190683
- if (names.length === 0)
190684
- throw new Error("No matching eval examples found.");
190685
- const requested = options8.concurrencyOverride ?? DEFAULT_CONCURRENCY;
190686
- const requestedValid = Number.isFinite(requested) && requested > 0 ? requested : DEFAULT_CONCURRENCY;
190687
- const concurrency = Math.max(1, Math.min(requestedValid, names.length));
190570
+ async function runWorkflowExamplesWithEval(client, dir, workflowIdOrSlug, workflowId, exampleNames) {
190571
+ const hasEvaluators = await fetchHasEvaluators(client, workflowId);
190572
+ const mode = hasEvaluators ? "evaluators" : "diff";
190573
+ info(`Running ${ui.bold(`"${workflowIdOrSlug}"`)} ${ui.dim(`(${workflowId}, ${exampleNames.length} example(s))`)}`);
190574
+ dim(hasEvaluators ? "Server example run; grading with configured evaluators." : "Server example run; no evaluators configured, grading by structural diff vs expected.");
190688
190575
  const evalBaseDir = resolveEvalBaseDir(dir);
190689
- if (!evalBaseDir)
190690
- throw new Error("No dataset/examples directory found.");
190691
- info(`Running workflow ${ui.bold(`"${workflowIdOrSlug}"`)} ${ui.dim(`(${workflowId}, ${names.length} example(s), concurrency ${concurrency})`)}`);
190692
- dim("Using unified run endpoint.");
190693
- const interactive = isTTY();
190694
- const progress = createProgressLines({
190695
- interactive,
190696
- lineLabels: names,
190697
- waitingLabel: "waiting",
190698
- runningLabel: "running",
190699
- dim: ui.dim,
190700
- spinnerStyle: ui.info
190701
- });
190702
- progress.start();
190703
- let passed = 0;
190704
- let failed = 0;
190705
- let nextIndex = 0;
190706
- const reportDone = (index, label2) => {
190707
- if (interactive)
190708
- progress.setDone(index, label2);
190709
- else
190710
- console.log(label2);
190711
- };
190712
- const runOne = async () => {
190713
- while (true) {
190714
- const index = nextIndex++;
190715
- if (index >= names.length)
190716
- return;
190717
- const name = names[index];
190718
- const exampleDir = join9(evalBaseDir, name);
190719
- try {
190720
- const example = buildExamplePayload(exampleDir);
190721
- if (interactive)
190722
- progress.setRunning(index);
190723
- else
190724
- console.log(`${ui.dim("→")} ${name}`);
190725
- const form = await buildRunFormData({
190726
- target: runStartMultipartTarget({
190727
- type: "workflow",
190728
- id: workflowId,
190729
- version: options8.version
190730
- }),
190731
- input: example.scalars,
190732
- overrides: example.overrides,
190733
- files: example.files.map((file2) => ({
190734
- fieldName: file2.argument,
190735
- content: file2.content,
190736
- filename: file2.filename,
190737
- mimeType: file2.mimeType
190738
- }))
190739
- });
190740
- const res = await client.postFormData(runTargetApiPath({ type: "workflow", id: workflowId, version: options8.version }), form);
190741
- if (typeof res?.id !== "string") {
190742
- throw new Error("Run API did not return a run id");
190743
- }
190744
- const executionId = res.id;
190745
- process.stderr.write(` ${ui.dim(`→ ${name}: ${executionId}`)}
190576
+ const results = [];
190577
+ for (const name of exampleNames) {
190578
+ process.stderr.write(`${ui.dim("→")} ${name}
190746
190579
  `);
190747
- const result = await pollExecution(client, executionId);
190748
- reportDone(index, result.status === "completed" ? `${name} ${ui.ok("PASS")}` : `${name} ${ui.err("FAIL")} ${result.error ?? result.status}`);
190749
- if (result.status === "completed")
190750
- passed++;
190751
- else
190752
- failed++;
190753
- try {
190754
- const run = await client.get(`/api/v1/runs/${executionId}?expand=execution`);
190755
- const artifactDir = await writeExecutionArtifacts(client, exampleDir, toExecutionArtifactPayload(run));
190756
- if (!interactive)
190757
- console.log(ui.dim(` Artifacts: ${artifactDir}`));
190758
- } catch (artErr) {
190759
- if (!interactive)
190760
- console.warn(ui.dim(` Warning: could not write artifacts: ${formatCliError(artErr)}`));
190580
+ try {
190581
+ const example = await resolveExample(client, workflowId, name);
190582
+ if (!example) {
190583
+ throw new Error(`example "${name}" not found in the server dataset for ${workflowId}. ` + `Push it first: eigenpal workflow dataset push ${workflowId}`);
190584
+ }
190585
+ const started = await client.post(`/api/v1/automations/${workflowId}/examples/${example.id}/run`, {});
190586
+ const runId = started.id;
190587
+ if (typeof runId !== "string")
190588
+ throw new Error("Example run did not return a run id");
190589
+ process.stderr.write(` ${ui.dim(`run: ${runId}`)}
190590
+ `);
190591
+ const run = await pollRunTerminal(client, runId);
190592
+ const status = run.execution?.status ?? run.status;
190593
+ const runOk = status === "completed";
190594
+ const result = {
190595
+ name,
190596
+ exampleId: example.id,
190597
+ executionId: runId,
190598
+ run: runOk ? "ok" : "error",
190599
+ runError: runOk ? undefined : run.error ?? status ?? "unknown",
190600
+ mode: "none"
190601
+ };
190602
+ if (runOk && hasEvaluators && started.batchId) {
190603
+ const rollup = await pollEvalRollup(client, runId);
190604
+ if (rollup) {
190605
+ const rows = await fetchEvalRows(client, workflowId, started.batchId, runId);
190606
+ result.mode = "evaluators";
190607
+ result.score = rollup.score;
190608
+ result.passed = rollup.passed;
190609
+ result.evaluators = rows.map((r5) => ({
190610
+ name: r5.evaluatorName ?? "evaluator",
190611
+ score: toNumberOrNull(r5.score),
190612
+ passed: r5.passed ?? null,
190613
+ weight: toNumberOrNull(r5.weight),
190614
+ error: r5.error ?? null
190615
+ }));
190616
+ } else {
190617
+ warn(` ${name}: no evaluator score after ${EVAL_GRACE_MS / 1000}s; grading by diff.`);
190618
+ gradeByDiff(result, example.expected, runOutput(run));
190761
190619
  }
190762
- } catch (err2) {
190763
- reportDone(index, `${name} ${ui.err("FAIL")} ${formatCliError(err2)}`);
190764
- failed++;
190620
+ } else if (runOk) {
190621
+ gradeByDiff(result, example.expected, runOutput(run));
190622
+ }
190623
+ results.push(result);
190624
+ reportLine(result);
190625
+ if (evalBaseDir) {
190626
+ try {
190627
+ await writeExecutionArtifacts(client, `${evalBaseDir}/${name}`, toArtifactPayload(run, runId));
190628
+ } catch {}
190765
190629
  }
190630
+ } catch (err2) {
190631
+ const result = {
190632
+ name,
190633
+ run: "error",
190634
+ runError: formatCliError(err2),
190635
+ mode: "none"
190636
+ };
190637
+ results.push(result);
190638
+ reportLine(result);
190766
190639
  }
190767
- };
190768
- const workers = Array.from({ length: concurrency }, () => runOne());
190769
- await Promise.all(workers);
190770
- console.log(`
190771
- ${ui.bold("Results:")} ${ui.ok(String(passed))} passed, ${failed > 0 ? ui.err(String(failed)) : ui.ok(String(failed))} failed`);
190640
+ }
190641
+ const summary = summarize(workflowIdOrSlug, mode, results);
190642
+ process.stderr.write(`
190643
+ ${formatEvalSummary(summary)}
190644
+ `);
190645
+ return summary;
190646
+ }
190647
+ function gradeByDiff(result, expected, output) {
190648
+ if (expected === undefined || expected === null) {
190649
+ result.mode = "none";
190650
+ return;
190651
+ }
190652
+ const graded = gradeAgainstExpected(expected, output);
190653
+ result.mode = "diff";
190654
+ result.matched = graded.matched;
190655
+ result.diffCount = graded.diffs.length;
190656
+ result.diffs = graded.diffs.length ? graded.diffs : undefined;
190657
+ }
190658
+ function formatExampleLines(result) {
190659
+ if (result.run === "error") {
190660
+ return `${result.name} ${ui.err(`err ${result.runError ?? ""}`.trimEnd())}`;
190661
+ }
190662
+ if (result.mode === "evaluators") {
190663
+ const verdict = result.passed === true ? ui.ok("PASS") : ui.err("FAIL");
190664
+ const head = `${result.name} ${ui.ok("ok")} score ${fmtScore(result.score)} ${verdict}`;
190665
+ const detail = (result.evaluators ?? []).map((e8) => {
190666
+ const mark = e8.error ? ui.warn("error") : e8.passed === true ? ui.ok("pass") : e8.passed === false ? ui.err("fail") : ui.dim("n/a");
190667
+ return ` ${e8.name} ${fmtScore(e8.score)} ${mark}`;
190668
+ });
190669
+ return [head, ...detail].join(`
190670
+ `);
190671
+ }
190672
+ if (result.mode === "diff") {
190673
+ const verdict = result.matched ? ui.ok("PASS") : ui.err(`FAIL (${result.diffCount} diff)`);
190674
+ return `${result.name} ${ui.ok("ok")} ${verdict}`;
190675
+ }
190676
+ return `${result.name} ${ui.ok("ok")} ${ui.dim("-")}`;
190677
+ }
190678
+ function reportLine(result) {
190679
+ process.stderr.write(`${formatExampleLines(result)}
190680
+ `);
190681
+ }
190682
+ function summarize(workflow2, mode, examples) {
190683
+ const ok2 = examples.filter((r5) => r5.run === "ok").length;
190684
+ const errored = examples.length - ok2;
190685
+ const gradedResults = examples.filter((r5) => r5.mode === "evaluators" || r5.mode === "diff");
190686
+ const passedCases = gradedResults.filter((r5) => r5.mode === "evaluators" ? r5.passed === true : r5.matched === true).length;
190687
+ const scores = gradedResults.filter((r5) => r5.mode === "evaluators" && typeof r5.score === "number").map((r5) => r5.score);
190772
190688
  return {
190773
- workflow: workflowIdOrSlug,
190774
- passed,
190775
- failed,
190776
- total: names.length
190777
- };
190689
+ workflow: workflow2,
190690
+ mode,
190691
+ total: examples.length,
190692
+ ok: ok2,
190693
+ errored,
190694
+ graded: gradedResults.length,
190695
+ passedCases,
190696
+ failedCases: gradedResults.length - passedCases,
190697
+ ungraded: ok2 - gradedResults.length,
190698
+ weightedAvg: scores.length ? scores.reduce((s5, n5) => s5 + n5, 0) / scores.length : null,
190699
+ examples,
190700
+ passed: ok2,
190701
+ failed: errored
190702
+ };
190703
+ }
190704
+ function formatEvalSummary(summary) {
190705
+ const lines = [];
190706
+ let line3 = `${ui.bold("Ran")} ${summary.total}: ${ui.ok(`${summary.ok} ok`)}`;
190707
+ if (summary.errored > 0)
190708
+ line3 += `, ${ui.err(`${summary.errored} errored`)}`;
190709
+ if (summary.graded > 0) {
190710
+ const allPass = summary.failedCases === 0;
190711
+ if (summary.mode === "evaluators") {
190712
+ const cases = `${summary.passedCases}/${summary.graded} cases pass`;
190713
+ const weighted = summary.weightedAvg != null ? `weighted ${summary.weightedAvg.toFixed(2)}, ` : "";
190714
+ line3 += ` ${ui.dim("/")} ${weighted}${allPass ? ui.ok(cases) : ui.err(cases)}`;
190715
+ } else {
190716
+ const acc = `${summary.passedCases}/${summary.graded} matched`;
190717
+ line3 += ` ${ui.dim("/")} accuracy ${allPass ? ui.ok(acc) : ui.err(acc)}`;
190718
+ }
190719
+ }
190720
+ lines.push(line3);
190721
+ if (summary.ungraded > 0) {
190722
+ lines.push(ui.dim(` ${summary.ungraded} ungraded (no stored expected output)`));
190723
+ }
190724
+ return lines.join(`
190725
+ `);
190778
190726
  }
190779
190727
 
190780
190728
  // src/commands/workflow/execution.ts
@@ -190783,13 +190731,16 @@ async function runSavedWorkflowExamples(workflow2, examples, opts) {
190783
190731
  try {
190784
190732
  requireApiKey(config2);
190785
190733
  const client = new ApiClient(config2);
190786
- const summary = await runExec(client, config2.dir, workflow2, examples, {
190787
- concurrencyOverride: opts.concurrency,
190788
- version: opts.version
190789
- });
190734
+ const workflowId = await resolveWorkflowId(client, workflow2);
190735
+ if (opts.version && !["latest", "current", "undefined"].includes(opts.version)) {
190736
+ warn(`--example runs the current published version; ignoring @${opts.version}.`);
190737
+ }
190738
+ const summary = await runWorkflowExamplesWithEval(client, config2.dir, workflow2, workflowId, examples);
190790
190739
  if (opts.json)
190791
190740
  console.log(JSON.stringify(summary, null, 2));
190792
- if (summary.failed > 0)
190741
+ if (summary.errored > 0)
190742
+ process.exit(1);
190743
+ if (opts.failOnMismatch && summary.failedCases > 0)
190793
190744
  process.exit(1);
190794
190745
  } catch (err2) {
190795
190746
  const msg = err2 instanceof Error ? err2.message : String(err2);
@@ -190802,7 +190753,23 @@ async function runSavedWorkflowExamples(workflow2, examples, opts) {
190802
190753
 
190803
190754
  // src/commands/run.ts
190804
190755
  function registerRunCommands(program2) {
190805
- addJsonFlag(withBaseUrl(program2.command("run <target>"))).description("Start a workflow or agent run, e.g. workflows.extract-invoice.").option("--input-json <json>", "JSON input object").option("--input-file <field=path>", 'Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file".', collectRepeated, []).option("--example <name>", "Run one persisted example by name").option("--dir <dir>", "Local eigenpal directory for workflow examples", undefined).option("--wait", "Poll until the run reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(runTarget));
190756
+ addJsonFlag(withBaseUrl(program2.command("run <target>"))).description("Start a workflow or agent run, e.g. workflows.extract-invoice.").option("--input-json <json>", "JSON input object").option("--input-file <field=path>", 'Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file".', collectRepeated, []).option("--example <name>", "Run one persisted example by name").option("--dir <dir>", "Local eigenpal directory for workflow examples", undefined).option("--fail-on-mismatch", "For --example runs, exit non-zero when a graded example fails (evaluator fail, or output mismatch)").option("--wait", "Poll until the run reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(runTarget)).addHelpText("after", `
190757
+ Grading --example runs
190758
+ --example runs the workflow's CURRENT published version on the SERVER's stored
190759
+ dataset example, so push your latest dataset/evaluators first
190760
+ (eigenpal workflow dataset push / evaluators push).
190761
+
190762
+ If the workflow has evaluators configured, they run automatically and the CLI
190763
+ shows the real weighted score and per-evaluator pass/fail. If it has no
190764
+ evaluators, the output is graded by a structural diff against the example's
190765
+ stored expected output (every expected field must be present and equal; extra
190766
+ output fields are ignored, arrays match by index). The summary reports two
190767
+ separate signals: execution health ("ok"/"errored") and accuracy.
190768
+
190769
+ Exit codes
190770
+ 1 a run errored (or, with --fail-on-mismatch, a graded example failed)
190771
+ By default a grading failure is informational and does not change the exit code.
190772
+ `);
190806
190773
  addJsonFlag(withBaseUrl(program2.command("rerun <run-id>"))).description("Create a new run from a previous run's stored input snapshot.").option("--version <version>", "Version/source ref for the new run: latest, original, or an explicit ref", "latest").option("--wait", "Poll until the rerun reaches a terminal status").option("--interval <seconds>", "Polling interval in seconds", intArg, 2).option("--max-wait <seconds>", "Maximum wait before exit code 2", intArg, 1800).action(action(rerunTarget));
190807
190774
  }
190808
190775
  async function runTarget(target, opts) {
@@ -190820,7 +190787,8 @@ async function runTarget(target, opts) {
190820
190787
  }
190821
190788
  return runSavedWorkflowExamples(parsed.idOrSlug, [opts.example], {
190822
190789
  ...opts,
190823
- version: String(parsed.requestedVersion)
190790
+ version: String(parsed.requestedVersion),
190791
+ failOnMismatch: opts.failOnMismatch
190824
190792
  });
190825
190793
  }
190826
190794
  const client = buildClient(opts);
@@ -190865,73 +190833,73 @@ import {
190865
190833
  unlinkSync as unlinkSync2,
190866
190834
  writeFileSync as writeFileSync8
190867
190835
  } from "node:fs";
190868
- import { sep as PATH_SEP, dirname as dirname5, join as join11, relative as relative3, resolve as resolve5 } from "node:path";
190836
+ import { sep as PATH_SEP, dirname as dirname5, join as join10, relative as relative3, resolve as resolve5 } from "node:path";
190869
190837
  import { fileURLToPath as fileURLToPath6 } from "node:url";
190870
190838
 
190871
190839
  // src/commands/skill/tools.ts
190872
- import { join as join10 } from "node:path";
190840
+ import { join as join9 } from "node:path";
190873
190841
  var SKILL_NAME = "eigenpal";
190874
190842
  var TOOLS = [
190875
190843
  {
190876
190844
  id: "claude",
190877
190845
  label: "Claude Code",
190878
- relativePath: join10(".claude", "skills", SKILL_NAME),
190846
+ relativePath: join9(".claude", "skills", SKILL_NAME),
190879
190847
  detectPaths: [".claude", "CLAUDE.md"]
190880
190848
  },
190881
190849
  {
190882
190850
  id: "cursor",
190883
190851
  label: "Cursor",
190884
- relativePath: join10(".cursor", "skills", SKILL_NAME),
190852
+ relativePath: join9(".cursor", "skills", SKILL_NAME),
190885
190853
  detectPaths: [".cursor", ".cursorrules"]
190886
190854
  },
190887
190855
  {
190888
190856
  id: "codex",
190889
190857
  label: "Codex",
190890
- relativePath: join10(".codex", "skills", SKILL_NAME),
190858
+ relativePath: join9(".codex", "skills", SKILL_NAME),
190891
190859
  detectPaths: [".codex"]
190892
190860
  },
190893
190861
  {
190894
190862
  id: "gemini",
190895
190863
  label: "Gemini CLI",
190896
- relativePath: join10(".gemini", "skills", SKILL_NAME),
190864
+ relativePath: join9(".gemini", "skills", SKILL_NAME),
190897
190865
  detectPaths: [".gemini", "GEMINI.md"]
190898
190866
  },
190899
190867
  {
190900
190868
  id: "antigravity",
190901
190869
  label: "Antigravity",
190902
- relativePath: join10(".agent", "skills", SKILL_NAME),
190870
+ relativePath: join9(".agent", "skills", SKILL_NAME),
190903
190871
  detectPaths: [".agent"]
190904
190872
  },
190905
190873
  {
190906
190874
  id: "opencode",
190907
190875
  label: "OpenCode",
190908
- relativePath: join10(".opencode", "skills", SKILL_NAME),
190876
+ relativePath: join9(".opencode", "skills", SKILL_NAME),
190909
190877
  detectPaths: [".opencode"]
190910
190878
  },
190911
190879
  {
190912
190880
  id: "pi",
190913
190881
  label: "Pi",
190914
- relativePath: join10(".pi", "skills", SKILL_NAME),
190882
+ relativePath: join9(".pi", "skills", SKILL_NAME),
190915
190883
  detectPaths: [".pi"]
190916
190884
  },
190917
190885
  {
190918
190886
  id: "windsurf",
190919
190887
  label: "Windsurf",
190920
- relativePath: join10(".windsurf", "skills", SKILL_NAME),
190888
+ relativePath: join9(".windsurf", "skills", SKILL_NAME),
190921
190889
  detectPaths: [".windsurf", ".windsurfrules"]
190922
190890
  },
190923
190891
  {
190924
190892
  id: "github-copilot",
190925
190893
  label: "GitHub Copilot",
190926
- relativePath: join10(".github", "skills", SKILL_NAME),
190894
+ relativePath: join9(".github", "skills", SKILL_NAME),
190927
190895
  detectPaths: [
190928
- join10(".github", "copilot-instructions.md"),
190929
- join10(".github", "instructions"),
190930
- join10(".github", "prompts"),
190931
- join10(".github", "agents"),
190932
- join10(".github", "skills"),
190933
- join10(".github", "workflows", "copilot-setup-steps.yml"),
190934
- join10(".github", ".mcp.json")
190896
+ join9(".github", "copilot-instructions.md"),
190897
+ join9(".github", "instructions"),
190898
+ join9(".github", "prompts"),
190899
+ join9(".github", "agents"),
190900
+ join9(".github", "skills"),
190901
+ join9(".github", "workflows", "copilot-setup-steps.yml"),
190902
+ join9(".github", ".mcp.json")
190935
190903
  ]
190936
190904
  }
190937
190905
  ];
@@ -190951,7 +190919,7 @@ function resolveSkillSource() {
190951
190919
  ${distCandidate}`);
190952
190920
  }
190953
190921
  function defaultClaudeTarget() {
190954
- return join11(process.cwd(), TOOLS[0].relativePath);
190922
+ return join10(process.cwd(), TOOLS[0].relativePath);
190955
190923
  }
190956
190924
  function resolveSkillTarget(opts) {
190957
190925
  return opts.target ?? defaultClaudeTarget();
@@ -190979,8 +190947,8 @@ async function installSkill(opts) {
190979
190947
  let skipped = 0;
190980
190948
  let prompted = 0;
190981
190949
  for (const relativePath of sourceFiles) {
190982
- const sourcePath = join11(source2, relativePath);
190983
- const targetPath = join11(target, relativePath);
190950
+ const sourcePath = join10(source2, relativePath);
190951
+ const targetPath = join10(target, relativePath);
190984
190952
  const sourceBytes = templateSourceFile(relativePath, readFileSync14(sourcePath), cliVersion);
190985
190953
  const sourceHash = sha2562(sourceBytes);
190986
190954
  if (!existsSync15(targetPath)) {
@@ -191017,7 +190985,7 @@ async function installSkill(opts) {
191017
190985
  }
191018
190986
  const manifestEntries = [];
191019
190987
  for (const relativePath of sourceFiles) {
191020
- const targetPath = join11(target, relativePath);
190988
+ const targetPath = join10(target, relativePath);
191021
190989
  if (!existsSync15(targetPath))
191022
190990
  continue;
191023
190991
  manifestEntries.push({ path: relativePath, sha256: sha2562(readFileSync14(targetPath)) });
@@ -191027,7 +190995,7 @@ async function installSkill(opts) {
191027
190995
  installedAt: new Date().toISOString(),
191028
190996
  files: manifestEntries
191029
190997
  };
191030
- writeFileSync8(join11(target, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}
190998
+ writeFileSync8(join10(target, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}
191031
190999
  `);
191032
191000
  const displayTarget = relative3(process.cwd(), target) || target;
191033
191001
  success2(`Installed skill at ${ui.bold(displayTarget)} — ${written} written, ${skipped} unchanged${prompted > 0 ? `, ${prompted} prompted` : ""}.`);
@@ -191036,7 +191004,7 @@ async function uninstallSkill(opts) {
191036
191004
  const target = resolveSkillTarget(opts);
191037
191005
  const manifest = readManifest(target);
191038
191006
  if (!manifest) {
191039
- error51(`No manifest found at ${join11(target, MANIFEST_NAME)}; nothing to uninstall.`);
191007
+ error51(`No manifest found at ${join10(target, MANIFEST_NAME)}; nothing to uninstall.`);
191040
191008
  process.exit(1);
191041
191009
  }
191042
191010
  if (!opts.yes && process.stdin.isTTY) {
@@ -191069,7 +191037,7 @@ async function uninstallSkill(opts) {
191069
191037
  unlinkSync2(path15);
191070
191038
  removed += 1;
191071
191039
  }
191072
- rmSync2(join11(target, MANIFEST_NAME), { force: true });
191040
+ rmSync2(join10(target, MANIFEST_NAME), { force: true });
191073
191041
  pruneEmptyDirs(target);
191074
191042
  const displayTarget = relative3(process.cwd(), target) || target;
191075
191043
  success2(`Uninstalled skill from ${ui.bold(displayTarget)} — ${removed} removed${kept > 0 ? `, ${kept} user-edited kept` : ""}.`);
@@ -191130,14 +191098,14 @@ async function installSkillTools(opts) {
191130
191098
  for (const tool of toInstall) {
191131
191099
  await installSkill({
191132
191100
  source: opts.source,
191133
- target: join11(cwd, tool.relativePath),
191101
+ target: join10(cwd, tool.relativePath),
191134
191102
  force: opts.force,
191135
191103
  yes: opts.yes
191136
191104
  });
191137
191105
  }
191138
191106
  for (const tool of toUninstall) {
191139
191107
  await uninstallSkill({
191140
- target: join11(cwd, tool.relativePath),
191108
+ target: join10(cwd, tool.relativePath),
191141
191109
  yes: true
191142
191110
  });
191143
191111
  }
@@ -191200,7 +191168,7 @@ async function uninstallSkillTools(opts) {
191200
191168
  }
191201
191169
  for (const tool of targets) {
191202
191170
  await uninstallSkill({
191203
- target: join11(cwd, tool.relativePath),
191171
+ target: join10(cwd, tool.relativePath),
191204
191172
  yes: true
191205
191173
  });
191206
191174
  }
@@ -191208,7 +191176,7 @@ async function uninstallSkillTools(opts) {
191208
191176
  function listSkillTools(opts = {}) {
191209
191177
  const cwd = process.cwd();
191210
191178
  const rows = TOOLS.flatMap((tool) => {
191211
- const targetDir = join11(cwd, tool.relativePath);
191179
+ const targetDir = join10(cwd, tool.relativePath);
191212
191180
  const manifest = readManifest(targetDir);
191213
191181
  if (!manifest)
191214
191182
  return [];
@@ -191261,10 +191229,10 @@ function hintFor(tool, installed, detected) {
191261
191229
  return tool.relativePath;
191262
191230
  }
191263
191231
  function isInstalled(cwd, tool) {
191264
- return existsSync15(join11(cwd, tool.relativePath, MANIFEST_NAME));
191232
+ return existsSync15(join10(cwd, tool.relativePath, MANIFEST_NAME));
191265
191233
  }
191266
191234
  function isProjectUsing(cwd, tool) {
191267
- return tool.detectPaths.some((p4) => existsSync15(join11(cwd, p4)));
191235
+ return tool.detectPaths.some((p4) => existsSync15(join10(cwd, p4)));
191268
191236
  }
191269
191237
  function listFiles(root2) {
191270
191238
  const out = [];
@@ -191272,7 +191240,7 @@ function listFiles(root2) {
191272
191240
  while (stack2.length > 0) {
191273
191241
  const dir = stack2.pop();
191274
191242
  for (const entry of readdirSync5(dir)) {
191275
- const full = join11(dir, entry);
191243
+ const full = join10(dir, entry);
191276
191244
  const rel = relative3(root2, full);
191277
191245
  const stats = lstatSync(full);
191278
191246
  if (stats.isDirectory()) {
@@ -191287,7 +191255,7 @@ function listFiles(root2) {
191287
191255
  return out.sort();
191288
191256
  }
191289
191257
  function readManifest(target) {
191290
- const path15 = join11(target, MANIFEST_NAME);
191258
+ const path15 = join10(target, MANIFEST_NAME);
191291
191259
  if (!existsSync15(path15))
191292
191260
  return null;
191293
191261
  try {
@@ -191322,7 +191290,7 @@ function pruneEmptyDirs(root2) {
191322
191290
  const stack2 = [];
191323
191291
  const collect = (dir) => {
191324
191292
  for (const entry of readdirSync5(dir)) {
191325
- const full = join11(dir, entry);
191293
+ const full = join10(dir, entry);
191326
191294
  if (lstatSync(full).isDirectory())
191327
191295
  collect(full);
191328
191296
  }
@@ -191468,12 +191436,12 @@ import { createInterface as createInterface3 } from "node:readline/promises";
191468
191436
 
191469
191437
  // src/commands/workflow/clear.ts
191470
191438
  import { existsSync as existsSync16, readdirSync as readdirSync6, rmSync as rmSync3, statSync as statSync5 } from "fs";
191471
- import { basename, join as join12 } from "path";
191439
+ import { basename, join as join11 } from "path";
191472
191440
  var EMPTY_REPORT = { removed: 0, kept: 0, touched: 0 };
191473
191441
  function getDirectoryEntries(path15) {
191474
191442
  if (!existsSync16(path15))
191475
191443
  return [];
191476
- return readdirSync6(path15).filter((entry) => statSync5(join12(path15, entry)).isDirectory()).sort();
191444
+ return readdirSync6(path15).filter((entry) => statSync5(join11(path15, entry)).isDirectory()).sort();
191477
191445
  }
191478
191446
  function removeDirEntriesWithRetention(path15, removeAll) {
191479
191447
  const entries = getDirectoryEntries(path15);
@@ -191486,7 +191454,7 @@ function removeDirEntriesWithRetention(path15, removeAll) {
191486
191454
  for (const entry of entries) {
191487
191455
  if (keepSet.has(entry))
191488
191456
  continue;
191489
- rmSync3(join12(path15, entry), { recursive: true, force: true });
191457
+ rmSync3(join11(path15, entry), { recursive: true, force: true });
191490
191458
  removed++;
191491
191459
  }
191492
191460
  return { removed, kept: keepSet.size, touched: entries.length };
@@ -191513,7 +191481,7 @@ function removeJudgeSummaryFilesWithRetention(exampleDir, removeAll) {
191513
191481
  kept++;
191514
191482
  continue;
191515
191483
  }
191516
- rmSync3(join12(exampleDir, fileName), { force: true });
191484
+ rmSync3(join11(exampleDir, fileName), { force: true });
191517
191485
  removed++;
191518
191486
  }
191519
191487
  return { removed, kept, touched: stampByFile.size };
@@ -191521,17 +191489,17 @@ function removeJudgeSummaryFilesWithRetention(exampleDir, removeAll) {
191521
191489
  function getTargetExampleDirs(dir, exampleNames) {
191522
191490
  const baseDir = resolveEvalBaseDir(dir);
191523
191491
  if (!baseDir) {
191524
- error51(`No dataset/examples directory at ${join12(dir, "dataset", "examples")}`);
191492
+ error51(`No dataset/examples directory at ${join11(dir, "dataset", "examples")}`);
191525
191493
  process.exit(1);
191526
191494
  }
191527
- const all = readdirSync6(baseDir).filter((entry) => statSync5(join12(baseDir, entry)).isDirectory());
191495
+ const all = readdirSync6(baseDir).filter((entry) => statSync5(join11(baseDir, entry)).isDirectory());
191528
191496
  if (exampleNames.length === 0)
191529
- return all.map((name) => join12(baseDir, name));
191497
+ return all.map((name) => join11(baseDir, name));
191530
191498
  const matched = all.filter((name) => exampleNames.includes(name));
191531
191499
  if (matched.length === 0) {
191532
191500
  warn(`None of [${exampleNames.join(", ")}] match an example under ./dataset/examples/. ` + `Heads up: the \`clear-local\` positional changed — it used to take a workflow slug, ` + `now it takes example names (or omit to clean every example).`);
191533
191501
  }
191534
- return matched.map((name) => join12(baseDir, name));
191502
+ return matched.map((name) => join11(baseDir, name));
191535
191503
  }
191536
191504
  function runSection(args) {
191537
191505
  let removed = 0;
@@ -191563,8 +191531,8 @@ async function clearEvalOutputs(dir, exampleNames = [], options8 = {}) {
191563
191531
  const exampleDirs = getTargetExampleDirs(dir, exampleNames);
191564
191532
  runSection({
191565
191533
  exampleDirs,
191566
- clean: (exampleDir) => removeDirEntriesWithRetention(join12(exampleDir, "executions"), removeAll),
191567
- shouldCount: (exampleDir) => existsSync16(join12(exampleDir, "executions")),
191534
+ clean: (exampleDir) => removeDirEntriesWithRetention(join11(exampleDir, "executions"), removeAll),
191535
+ shouldCount: (exampleDir) => existsSync16(join11(exampleDir, "executions")),
191568
191536
  successLabel: "Execution cleanup complete",
191569
191537
  scannedLabel: "examples scanned:",
191570
191538
  seenLabel: "runs seen: ",
@@ -192139,7 +192107,7 @@ function looksLikeFreeform(prop) {
192139
192107
  // src/commands/workflow/validation.ts
192140
192108
  var import_yaml8 = __toESM(require_dist4(), 1);
192141
192109
  import { existsSync as existsSync17, readdirSync as readdirSync7, readFileSync as readFileSync15, statSync as statSync6 } from "fs";
192142
- import { join as join13, resolve as resolve6 } from "path";
192110
+ import { join as join12, resolve as resolve6 } from "path";
192143
192111
  var NAME_RE = /^[a-z0-9][a-z0-9-_]*$/;
192144
192112
  function isDirSafe(path15, issues, field) {
192145
192113
  try {
@@ -192259,19 +192227,19 @@ function validateDatasetFolder(root2) {
192259
192227
  if (!existsSync17(root2)) {
192260
192228
  return [{ field: root2, message: "Folder not found." }];
192261
192229
  }
192262
- if (existsSync17(join13(root2, "manifest.json"))) {
192230
+ if (existsSync17(join12(root2, "manifest.json"))) {
192263
192231
  issues.push({
192264
192232
  field: "manifest.json",
192265
192233
  message: "Top-level manifest.json is the legacy format. Re-export the dataset using the folder convention."
192266
192234
  });
192267
192235
  }
192268
- const examplesDir = join13(root2, "examples");
192236
+ const examplesDir = join12(root2, "examples");
192269
192237
  if (!existsSync17(examplesDir)) {
192270
192238
  issues.push({ field: "examples/", message: "No examples/ directory found at dataset root." });
192271
192239
  return issues;
192272
192240
  }
192273
192241
  for (const exampleName of readdirSync7(examplesDir)) {
192274
- const exampleFolder = join13(examplesDir, exampleName);
192242
+ const exampleFolder = join12(examplesDir, exampleName);
192275
192243
  if (!isDirSafe(exampleFolder, issues, `examples/${exampleName}`))
192276
192244
  continue;
192277
192245
  if (!NAME_RE.test(exampleName)) {
@@ -192281,7 +192249,7 @@ function validateDatasetFolder(root2) {
192281
192249
  });
192282
192250
  continue;
192283
192251
  }
192284
- const inputJsonPath = join13(exampleFolder, "input.json");
192252
+ const inputJsonPath = join12(exampleFolder, "input.json");
192285
192253
  const hasInputJson = existsSync17(inputJsonPath);
192286
192254
  if (!hasInputJson) {
192287
192255
  issues.push({
@@ -192308,8 +192276,8 @@ function validateDatasetFolder(root2) {
192308
192276
  });
192309
192277
  continue;
192310
192278
  }
192311
- const inputDir = join13(exampleFolder, "input");
192312
- if (existsSync17(join13(inputDir, "arguments.json"))) {
192279
+ const inputDir = join12(exampleFolder, "input");
192280
+ if (existsSync17(join12(inputDir, "arguments.json"))) {
192313
192281
  issues.push({
192314
192282
  field: `examples/${exampleName}/input/arguments.json`,
192315
192283
  message: "Legacy input/arguments.json is no longer accepted. Use input.json instead."
@@ -192324,8 +192292,8 @@ function validateDatasetFolder(root2) {
192324
192292
  })) {
192325
192293
  issues.push(issue2);
192326
192294
  }
192327
- const expectedDir = join13(exampleFolder, "expected");
192328
- const expectedJsonPath = join13(exampleFolder, "expected.json");
192295
+ const expectedDir = join12(exampleFolder, "expected");
192296
+ const expectedJsonPath = join12(exampleFolder, "expected.json");
192329
192297
  if (existsSync17(expectedDir)) {
192330
192298
  for (const entry of readdirSync7(expectedDir)) {
192331
192299
  if (entry === "output.json" || entry === "error.json") {
@@ -192335,7 +192303,7 @@ function validateDatasetFolder(root2) {
192335
192303
  });
192336
192304
  continue;
192337
192305
  }
192338
- const sub = join13(expectedDir, entry);
192306
+ const sub = join12(expectedDir, entry);
192339
192307
  const field = `examples/${exampleName}/expected/${entry}`;
192340
192308
  let isDir;
192341
192309
  try {
@@ -192391,7 +192359,7 @@ function validateDatasetFolder(root2) {
192391
192359
  } else {
192392
192360
  for (const issue2 of validateFileRefs({
192393
192361
  refs: collectDatasetFileRefs(parsed),
192394
- filesRoot: join13(exampleFolder, "expected"),
192362
+ filesRoot: join12(exampleFolder, "expected"),
192395
192363
  root: "expected",
192396
192364
  fieldPrefix: `examples/${exampleName}/expected.json`
192397
192365
  })) {
@@ -192444,7 +192412,7 @@ function validateFileRefs(args) {
192444
192412
  });
192445
192413
  continue;
192446
192414
  }
192447
- if (!existsSync17(join13(args.filesRoot, relative4))) {
192415
+ if (!existsSync17(join12(args.filesRoot, relative4))) {
192448
192416
  issues.push({
192449
192417
  field: `${args.fieldPrefix}:${ref.jsonPath}`,
192450
192418
  message: `Referenced file does not exist: ${ref.path}.`
@@ -192498,7 +192466,7 @@ Note
192498
192466
  return;
192499
192467
  }
192500
192468
  const root2 = rootDir(opts);
192501
- const wfFile = join13(root2, "workflow.yaml");
192469
+ const wfFile = join12(root2, "workflow.yaml");
192502
192470
  const wfResult = validateWorkflowYaml(wfFile);
192503
192471
  const wfPath = relPath(root2, wfFile);
192504
192472
  const wfOk = printIssues("workflow.yaml", wfPath, wfResult.issues);
@@ -192506,8 +192474,8 @@ Note
192506
192474
  printSchemaQualityWarnings(wfPath, wfResult.warnings);
192507
192475
  printInvokeOnlineHint(wfResult.invokeStepCount, opts.online);
192508
192476
  }
192509
- const evOk = printIssues("evaluators.yaml", relPath(root2, join13(root2, "evaluators.yaml")), validateEvaluatorsYaml(join13(root2, "evaluators.yaml")));
192510
- const dsOk = printIssues("dataset/", relPath(root2, join13(root2, "dataset")), validateDatasetFolder(join13(root2, "dataset")));
192477
+ const evOk = printIssues("evaluators.yaml", relPath(root2, join12(root2, "evaluators.yaml")), validateEvaluatorsYaml(join12(root2, "evaluators.yaml")));
192478
+ const dsOk = printIssues("dataset/", relPath(root2, join12(root2, "dataset")), validateDatasetFolder(join12(root2, "dataset")));
192511
192479
  let onlineOk = true;
192512
192480
  if (wfOk && opts.online && existsSync17(wfFile)) {
192513
192481
  onlineOk = printIssues("invoke refs (server)", wfPath, await onlineInvokeIssues(readFileSync15(wfFile, "utf-8"), opts));
@@ -192587,7 +192555,7 @@ async function writeOrPrint(out, content) {
192587
192555
  }
192588
192556
  async function readDirAsZip2(dir) {
192589
192557
  const tree = {};
192590
- async function walk2(current, relPrefix) {
192558
+ async function walk3(current, relPrefix) {
192591
192559
  const entries = await fs17.readdir(current, { withFileTypes: true });
192592
192560
  for (const e8 of entries) {
192593
192561
  if (e8.name.startsWith(".") || e8.name === "node_modules")
@@ -192595,12 +192563,12 @@ async function readDirAsZip2(dir) {
192595
192563
  const full = path15.join(current, e8.name);
192596
192564
  const rel = relPrefix ? `${relPrefix}/${e8.name}` : e8.name;
192597
192565
  if (e8.isDirectory())
192598
- await walk2(full, rel);
192566
+ await walk3(full, rel);
192599
192567
  else if (e8.isFile())
192600
192568
  tree[rel] = new Uint8Array(await fs17.readFile(full));
192601
192569
  }
192602
192570
  }
192603
- await walk2(dir, "");
192571
+ await walk3(dir, "");
192604
192572
  if (Object.keys(tree).length === 0) {
192605
192573
  throw new Error(`directory is empty: ${dir}`);
192606
192574
  }
@@ -18,6 +18,7 @@ Start a workflow or agent run, e.g. workflows.extract-invoice.
18
18
  | `--input-file <field=path>` | no | `[]` | Input file to upload as multipart form-data. Repeat for multiple files; bare paths use field "file". |
19
19
  | `--example <name>` | no | | Run one persisted example by name |
20
20
  | `--dir <dir>` | no | | Local eigenpal directory for workflow examples |
21
+ | `--fail-on-mismatch` | no | | For --example runs, exit non-zero when a graded example fails (evaluator fail, or output mismatch) |
21
22
  | `--wait` | no | | Poll until the run reaches a terminal status |
22
23
  | `--interval <seconds>` | no | `2` | Polling interval in seconds |
23
24
  | `--max-wait <seconds>` | no | `1800` | Maximum wait before exit code 2 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eigenpal/cli",
3
- "version": "0.10.15",
3
+ "version": "0.10.16",
4
4
  "description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
5
5
  "keywords": [
6
6
  "eigenpal",