@eigenpal/cli 0.19.1 → 0.20.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @eigenpal/cli
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0ce2092: `eigenpal workflow dataset review-request` (and the `eigenpal agents dataset` mirror) is the agent-friendly review loop: create a request with reviewer instructions, `--focus` / `--focus-reason` paths, `--ignore` paths reviewers can skip, and per-example `--item-note` / `--field-note` comments; `list`/`get`/`events` expose `progress.complete`, `fieldDecisions`, and `inputDrifted`; `item --action` records approve/reject/comment/edit plus per-field `field-decision` (clear with `--clear` / `--decision null`); `update --status closed` finishes the request. There is no auto-apply — pull and reconcile examples manually. Reject is recommendation-only; nothing is deleted.
8
+
3
9
  ## 0.17.5
4
10
 
5
11
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -7730,6 +7730,7 @@ var init_common = __esm(() => {
7730
7730
  USER: "user",
7731
7731
  TENANT: "org",
7732
7732
  TENANT_INVITE: "inv",
7733
+ ORGANIZATION_CREATION: "orgc",
7733
7734
  API_KEY: "eig",
7734
7735
  PROCESSOR: "proc",
7735
7736
  AI_PROVIDER: "aip",
@@ -7738,6 +7739,9 @@ var init_common = __esm(() => {
7738
7739
  VERIFICATION: "vrf",
7739
7740
  DATASET: "ds",
7740
7741
  DATASET_EXAMPLE: "dex",
7742
+ DATASET_REVIEW_REQUEST: "dsr",
7743
+ DATASET_REVIEW_ITEM: "dsri",
7744
+ DATASET_REVIEW_EVENT: "dsre",
7741
7745
  EXPERIMENT: "exp",
7742
7746
  EXPERIMENT_RUN: "exr",
7743
7747
  EXPERIMENT_EVALUATION: "evl",
@@ -15451,15 +15455,21 @@ var require_dist = __commonJS(function(exports, module) {
15451
15455
  });
15452
15456
 
15453
15457
  // ../types/src/validation/ajv.ts
15454
- var import_ajv, import_ajv_formats, import_json_schema_draft_07, eigenpalAjv;
15458
+ function getEigenpalAjv() {
15459
+ if (!sharedAjv) {
15460
+ const ajv = new import_ajv.default({ allErrors: true, strict: false, verbose: true });
15461
+ import_ajv_formats.default(ajv);
15462
+ ajv.addKeyword({ keyword: "x-eigenpal-type", metaSchema: { type: "string" } });
15463
+ ajv.addMetaSchema({ ...import_json_schema_draft_07.default, $id: "https://json-schema.org/draft-07/schema#" });
15464
+ sharedAjv = ajv;
15465
+ }
15466
+ return sharedAjv;
15467
+ }
15468
+ var import_ajv, import_ajv_formats, import_json_schema_draft_07, sharedAjv = null;
15455
15469
  var init_ajv = __esm(() => {
15456
15470
  import_ajv = __toESM(require_ajv(), 1);
15457
15471
  import_ajv_formats = __toESM(require_dist(), 1);
15458
15472
  import_json_schema_draft_07 = __toESM(require_json_schema_draft_07(), 1);
15459
- eigenpalAjv = new import_ajv.default({ allErrors: true, strict: false, verbose: true });
15460
- import_ajv_formats.default(eigenpalAjv);
15461
- eigenpalAjv.addKeyword({ keyword: "x-eigenpal-type", metaSchema: { type: "string" } });
15462
- eigenpalAjv.addMetaSchema({ ...import_json_schema_draft_07.default, $id: "https://json-schema.org/draft-07/schema#" });
15463
15473
  });
15464
15474
 
15465
15475
  // ../types/src/validation/workspace-schema.ts
@@ -15481,8 +15491,9 @@ function validateWorkspaceSchema(content, _filename) {
15481
15491
  if (schema.type !== "object") {
15482
15492
  errors.push('Root "type" must be "object"');
15483
15493
  }
15484
- if (!eigenpalAjv.validateSchema(schema)) {
15485
- const metaErrors = eigenpalAjv.errors ?? [];
15494
+ const ajv = getEigenpalAjv();
15495
+ if (!ajv.validateSchema(schema)) {
15496
+ const metaErrors = ajv.errors ?? [];
15486
15497
  for (const err of metaErrors) {
15487
15498
  errors.push(`${err.instancePath || "/"}: ${err.message ?? "invalid schema"}`);
15488
15499
  }
@@ -56081,7 +56092,7 @@ var init_manifests = __esm(() => {
56081
56092
  string2().includes("@"),
56082
56093
  object({
56083
56094
  address: string2().includes("@"),
56084
- allowlist: array(string2()).default([]),
56095
+ allowlist: array(string2()).optional(),
56085
56096
  replyConfig: record(string2(), unknown()).default({}),
56086
56097
  requireSenderAuth: boolean2().default(true),
56087
56098
  enabled: boolean2().optional()
@@ -206380,7 +206391,7 @@ import { fileURLToPath as fileURLToPath8 } from "url";
206380
206391
  // package.json
206381
206392
  var package_default = {
206382
206393
  name: "@eigenpal/cli",
206383
- version: "0.19.1",
206394
+ version: "0.20.1",
206384
206395
  description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
206385
206396
  keywords: [
206386
206397
  "eigenpal",
@@ -210037,6 +210048,393 @@ function toCsv(rows, columns) {
210037
210048
  `);
210038
210049
  }
210039
210050
 
210051
+ // src/commands/dataset-review-request-briefing.ts
210052
+ function splitOnce(value, separator) {
210053
+ const index = value.indexOf(separator);
210054
+ if (index < 0)
210055
+ return [value, undefined];
210056
+ return [value.slice(0, index), value.slice(index + separator.length)];
210057
+ }
210058
+ function parseFocusReasonFlag(value) {
210059
+ const [path, reason] = splitOnce(value, "=");
210060
+ const trimmedPath = path.trim();
210061
+ if (!trimmedPath) {
210062
+ throw new Error("--focus-reason requires path=reason");
210063
+ }
210064
+ const trimmedReason = reason?.trim();
210065
+ if (!trimmedReason) {
210066
+ throw new Error("--focus-reason requires path=reason");
210067
+ }
210068
+ return { path: trimmedPath, reason: trimmedReason };
210069
+ }
210070
+ function parseItemNoteFlag(value) {
210071
+ const [exampleName, comment] = splitOnce(value, "=");
210072
+ const trimmedName = exampleName.trim();
210073
+ const trimmedComment = comment?.trim();
210074
+ if (!trimmedName || !trimmedComment) {
210075
+ throw new Error("--item-note requires exampleName=comment");
210076
+ }
210077
+ return { exampleName: trimmedName, comment: trimmedComment };
210078
+ }
210079
+ function parseFieldNoteFlag(value) {
210080
+ const [left, comment] = splitOnce(value, "=");
210081
+ const trimmedComment = comment?.trim();
210082
+ const dot = left.indexOf(".");
210083
+ const exampleName = (dot < 0 ? left : left.slice(0, dot)).trim();
210084
+ const path = (dot < 0 ? "" : left.slice(dot + 1)).trim();
210085
+ if (!exampleName || !path || !trimmedComment) {
210086
+ throw new Error("--field-note requires exampleName.path=comment");
210087
+ }
210088
+ return { exampleName, fields: [{ path, comment: trimmedComment }] };
210089
+ }
210090
+ function mergeFocusFields(paths, reasons) {
210091
+ const byPath = new Map;
210092
+ for (const path of paths) {
210093
+ const trimmed = path.trim();
210094
+ if (!trimmed)
210095
+ continue;
210096
+ byPath.set(trimmed, { path: trimmed, reason: null });
210097
+ }
210098
+ for (const field of reasons) {
210099
+ byPath.set(field.path, field);
210100
+ }
210101
+ return [...byPath.values()];
210102
+ }
210103
+ function mergeItemNotes(notes) {
210104
+ const byName = new Map;
210105
+ for (const note of notes) {
210106
+ const current = byName.get(note.exampleName) ?? { exampleName: note.exampleName };
210107
+ if (note.comment)
210108
+ current.comment = note.comment;
210109
+ if (note.fields?.length) {
210110
+ current.fields = [...current.fields ?? [], ...note.fields];
210111
+ }
210112
+ byName.set(note.exampleName, current);
210113
+ }
210114
+ return [...byName.values()];
210115
+ }
210116
+ function parseJsonObjectFlag(raw, flag) {
210117
+ try {
210118
+ return JSON.parse(raw);
210119
+ } catch {
210120
+ throw new Error(`${flag} must be valid JSON`);
210121
+ }
210122
+ }
210123
+
210124
+ // src/commands/dataset-review-request.ts
210125
+ function datasetReviewRequestsPath(automationId, reviewId) {
210126
+ const base = `/v1/automations/${automationId}/dataset-review-requests`;
210127
+ return reviewId ? `${base}/${reviewId}` : base;
210128
+ }
210129
+ function datasetReviewRequestEventsPath(automationId, reviewId) {
210130
+ return `${datasetReviewRequestsPath(automationId, reviewId)}/events`;
210131
+ }
210132
+ function datasetReviewRequestItemsPath(automationId, reviewId, itemId) {
210133
+ const base = `${datasetReviewRequestsPath(automationId, reviewId)}/items`;
210134
+ return itemId ? `${base}/${itemId}` : base;
210135
+ }
210136
+ function collectRepeatable(val2, prev) {
210137
+ return [...prev, val2];
210138
+ }
210139
+ function formatProgress(progress) {
210140
+ if (!progress)
210141
+ return "-";
210142
+ if (progress.complete) {
210143
+ return progress.rejected ? `complete (${progress.rejected} rejected)` : "complete";
210144
+ }
210145
+ const remaining = progress.remaining ?? 0;
210146
+ const total = progress.total ?? remaining;
210147
+ return `${remaining}/${total} remaining`;
210148
+ }
210149
+ function parseFieldDecision(opts) {
210150
+ if (opts.clear) {
210151
+ if (opts.decision !== undefined) {
210152
+ throw new Error("pass either --clear or --decision, not both");
210153
+ }
210154
+ return null;
210155
+ }
210156
+ if (opts.decision === undefined) {
210157
+ throw new Error("--decision or --clear is required when --action field-decision");
210158
+ }
210159
+ const normalized = opts.decision.trim().toLowerCase();
210160
+ if (normalized === "null" || normalized === "clear") {
210161
+ return null;
210162
+ }
210163
+ if (normalized === "approved" || normalized === "rejected") {
210164
+ return normalized;
210165
+ }
210166
+ throw new Error("--decision must be approved, rejected, null, or clear");
210167
+ }
210168
+ function buildReviewItemPatchBody(action, opts) {
210169
+ const body = {
210170
+ action,
210171
+ expectedUpdatedAt: opts.expectedUpdatedAt
210172
+ };
210173
+ if (opts.comment !== undefined)
210174
+ body.comment = opts.comment;
210175
+ if (opts.fieldPath !== undefined)
210176
+ body.fieldPath = opts.fieldPath;
210177
+ if (action === "edit") {
210178
+ if (opts.expectedJson === undefined) {
210179
+ throw new Error("--expected-json is required when --action edit");
210180
+ }
210181
+ body.expected = parseJsonObjectFlag(opts.expectedJson, "--expected-json");
210182
+ } else if (opts.expectedJson !== undefined) {
210183
+ throw new Error("--expected-json is only valid with --action edit");
210184
+ }
210185
+ if (action === "field-decision") {
210186
+ if (!opts.fieldPath?.trim()) {
210187
+ throw new Error("--field-path is required when --action field-decision");
210188
+ }
210189
+ body.decision = parseFieldDecision({ decision: opts.decision, clear: opts.clear });
210190
+ } else if (opts.decision !== undefined || opts.clear) {
210191
+ throw new Error("--decision / --clear are only valid with --action field-decision");
210192
+ }
210193
+ return body;
210194
+ }
210195
+ function registerDatasetReviewRequestCommands(dataset, resolveAutomation) {
210196
+ const reviewRequest = dataset.command("review-request").description("Review dataset ground truth with a human. Snapshot examples for review and poll progress until reviewers finish.").action(() => {
210197
+ process.stderr.write("`dataset review-request` requires a subcommand. Run `dataset review-request --help`.\n");
210198
+ process.exit(2);
210199
+ });
210200
+ addJsonFlag(withPagination(withBaseUrl(reviewRequest.command("list <automation-id>")).option("--status <csv>", "Filter by review status (comma-separated: draft,open,paused,closed)"), 50)).action(action(async (automationRef, opts) => {
210201
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210202
+ const params = {
210203
+ limit: String(opts.limit),
210204
+ offset: String(opts.offset)
210205
+ };
210206
+ if (opts.status)
210207
+ params.status = opts.status;
210208
+ const raw = await client.get(datasetReviewRequestsPath(automationId), params);
210209
+ renderListResult(raw, [
210210
+ { key: "id", header: "id" },
210211
+ { key: "title", header: "title" },
210212
+ { key: "status", header: "status" },
210213
+ {
210214
+ key: "progress",
210215
+ header: "progress",
210216
+ format: (value) => formatProgress(value)
210217
+ },
210218
+ { key: "createdAt", header: "createdAt", format: formatTimestamp }
210219
+ ], { ...opts, entityLabel: "review request" });
210220
+ }));
210221
+ addJsonFlag(withBaseUrl(reviewRequest.command("create <automation-id>"))).description("Request human review of dataset ground truth. Snapshot examples and poll progress until review is complete.").requiredOption("--title <title>", "Review request title").requiredOption("--example-name <name>", "Example folder name to include (repeatable)", collectRepeatable, []).option("--instructions <text>", "Note shown to the reviewer for the whole request").option("--focus <path>", "Expected-output path to highlight (repeatable)", collectRepeatable, []).option("--focus-reason <spec>", "Reason a focus path needs review, as path=reason (repeatable)", collectRepeatable, []).option("--ignore <path>", "Expected-output path reviewers can skip (repeatable)", collectRepeatable, []).option("--item-note <spec>", "Example-level note seeded at create time, as exampleName=comment (repeatable)", collectRepeatable, []).option("--field-note <spec>", "Field-level note seeded at create time, as exampleName.path=comment (repeatable)", collectRepeatable, []).option("--focus-json <json>", "JSON array of { path, reason? } focus fields").option("--notes-json <json>", "JSON array of { exampleName, comment?, fields?: [{ path, comment }] }").option("--status <draft|open|paused>", "Initial lifecycle status (default: draft)").addHelpText("after", `
210222
+ Examples:
210223
+ $ eigenpal workflow dataset review-request create wf_abc123 \\
210224
+ --title "Q1 invoice GT review" \\
210225
+ --example-name invoice-foo --example-name invoice-bar \\
210226
+ --instructions "Check IBAN checksums and totals." \\
210227
+ --focus vendor.iban --focus-reason 'vendor.iban=OCR often mangles IBANs' \\
210228
+ --ignore currency \\
210229
+ --field-note 'invoice-foo.total=off by 0.01 last run' \\
210230
+ --status open --json
210231
+
210232
+ $ eigenpal workflow dataset review-request list wf_abc123 --status open --json
210233
+ $ eigenpal workflow dataset review-request get wf_abc123 dsr_... --json
210234
+
210235
+ Agent loop: create with focus/ignore and per-example notes → poll \`get --json\`
210236
+ until \`.progress.complete\` → record per-field decisions and per-example
210237
+ approve/reject with comments → read \`events\` → \`dataset pull\` and MANUAL
210238
+ per-example reconcile into the live dataset (no auto-apply exists by design;
210239
+ reviewers can err) → \`update --status closed\`.
210240
+
210241
+ \`reject\` is a recommendation only — nothing is deleted by any review endpoint.
210242
+ Use \`--json\` from agents; API errors exit non-zero.
210243
+ `).action(action(async (automationRef, opts) => {
210244
+ if (opts.exampleName.length === 0) {
210245
+ throw new Error("pass at least one --example-name");
210246
+ }
210247
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210248
+ const focusFromFlags = mergeFocusFields(opts.focus ?? [], (opts.focusReason ?? []).map(parseFocusReasonFlag));
210249
+ const focusFromJson = opts.focusJson ? parseJsonObjectFlag(opts.focusJson, "--focus-json") : [];
210250
+ if (opts.focusJson && !Array.isArray(focusFromJson)) {
210251
+ throw new Error("--focus-json must be a JSON array");
210252
+ }
210253
+ const notesFromFlags = mergeItemNotes([
210254
+ ...(opts.itemNote ?? []).map(parseItemNoteFlag),
210255
+ ...(opts.fieldNote ?? []).map(parseFieldNoteFlag)
210256
+ ]);
210257
+ const notesFromJson = opts.notesJson ? parseJsonObjectFlag(opts.notesJson, "--notes-json") : [];
210258
+ if (opts.notesJson && !Array.isArray(notesFromJson)) {
210259
+ throw new Error("--notes-json must be a JSON array");
210260
+ }
210261
+ const body = {
210262
+ title: opts.title,
210263
+ exampleNames: opts.exampleName
210264
+ };
210265
+ if (opts.instructions !== undefined)
210266
+ body.instructions = opts.instructions;
210267
+ if (opts.status !== undefined)
210268
+ body.status = opts.status;
210269
+ const focusFields = [...focusFromFlags, ...focusFromJson];
210270
+ if (focusFields.length > 0)
210271
+ body.focusFields = focusFields;
210272
+ const ignoredFields = [
210273
+ ...new Set((opts.ignore ?? []).map((path) => path.trim()).filter(Boolean))
210274
+ ];
210275
+ if (ignoredFields.length > 0)
210276
+ body.ignoredFields = ignoredFields;
210277
+ const itemNotes = mergeItemNotes([...notesFromFlags, ...notesFromJson]);
210278
+ if (itemNotes.length > 0)
210279
+ body.itemNotes = itemNotes;
210280
+ const payload = await client.post(datasetReviewRequestsPath(automationId), body);
210281
+ if (opts.json)
210282
+ return printJson2(payload);
210283
+ const created = payload;
210284
+ success(`Created review request ${ui.bold(created.id ?? "(unknown id)")}` + (created.status ? ` ${ui.dim(`(${created.status})`)}` : ""));
210285
+ }));
210286
+ addJsonFlag(withBaseUrl(reviewRequest.command("get <automation-id> <review-id>"))).description("Fetch one dataset review request with items, progress, focus fields, ignored fields, and events.").action(action(async (automationRef, reviewId, opts) => {
210287
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210288
+ const payload = await client.get(datasetReviewRequestsPath(automationId, reviewId));
210289
+ if (opts.json)
210290
+ return printJson2(payload);
210291
+ const detail = payload;
210292
+ console.log(table([
210293
+ { field: "id", value: detail.id },
210294
+ { field: "title", value: detail.title },
210295
+ { field: "status", value: detail.status },
210296
+ { field: "progress", value: formatProgress(detail.progress) },
210297
+ { field: "instructions", value: detail.instructions ?? "-" },
210298
+ {
210299
+ field: "focus",
210300
+ value: detail.focusFields?.map((field) => field.reason ? `${field.path} (${field.reason})` : field.path).join(", ") || "-"
210301
+ },
210302
+ {
210303
+ field: "ignored",
210304
+ value: detail.ignoredFields?.join(", ") || "-"
210305
+ }
210306
+ ], [
210307
+ { key: "field", header: "field" },
210308
+ { key: "value", header: "value" }
210309
+ ]));
210310
+ if (detail.items?.length) {
210311
+ console.log(table(detail.items.map((item) => ({
210312
+ exampleName: item.exampleName,
210313
+ status: item.status,
210314
+ inputDrifted: item.inputDrifted ? "yes" : "no"
210315
+ })), [
210316
+ { key: "exampleName", header: "example" },
210317
+ { key: "status", header: "status" },
210318
+ { key: "inputDrifted", header: "inputDrifted" }
210319
+ ]));
210320
+ }
210321
+ }));
210322
+ addJsonFlag(withBaseUrl(reviewRequest.command("update <automation-id> <review-id>"))).description("Update review metadata or lifecycle status. Set --status closed when review is finished.").option("--title <title>", "New review request title").option("--instructions <text>", "Note shown to the reviewer for the whole request").option("--focus <path>", "Replace focus paths (repeatable; use with --focus-reason / --focus-json)", collectRepeatable, []).option("--focus-reason <spec>", "Reason a focus path needs review, as path=reason (repeatable)", collectRepeatable, []).option("--focus-json <json>", "JSON array of { path, reason? } focus fields (replaces focus)").option("--ignore <path>", "Replace ignored paths (repeatable)", collectRepeatable).option("--status <draft|open|paused|closed>", "Lifecycle status").addHelpText("after", `
210323
+ Examples:
210324
+ $ eigenpal workflow dataset review-request update wf_abc123 dsr_... \\
210325
+ --status closed --json
210326
+ $ eigenpal workflow dataset review-request update wf_abc123 dsr_... \\
210327
+ --title "Q1 GT review (round 2)" --instructions "Re-check totals." --json
210328
+
210329
+ At least one of --title, --instructions, --focus/--focus-json, --ignore, or
210330
+ --status is required. Closing a request does not write expected outputs back
210331
+ to the dataset — reconcile manually after \`dataset pull\`.
210332
+ `).action(action(async (automationRef, reviewId, opts) => {
210333
+ const body = {};
210334
+ if (opts.title !== undefined)
210335
+ body.title = opts.title;
210336
+ if (opts.instructions !== undefined)
210337
+ body.instructions = opts.instructions;
210338
+ if (opts.status !== undefined)
210339
+ body.status = opts.status;
210340
+ const focusFlagCount = (opts.focus ?? []).length + (opts.focusReason ?? []).length;
210341
+ const hasFocusInput = focusFlagCount > 0 || opts.focusJson !== undefined;
210342
+ if (hasFocusInput) {
210343
+ const focusFromFlags = mergeFocusFields(opts.focus ?? [], (opts.focusReason ?? []).map(parseFocusReasonFlag));
210344
+ const focusFromJson = opts.focusJson ? parseJsonObjectFlag(opts.focusJson, "--focus-json") : [];
210345
+ if (opts.focusJson && !Array.isArray(focusFromJson)) {
210346
+ throw new Error("--focus-json must be a JSON array");
210347
+ }
210348
+ body.focusFields = [...focusFromFlags, ...focusFromJson];
210349
+ }
210350
+ if (opts.ignore !== undefined) {
210351
+ body.ignoredFields = [
210352
+ ...new Set(opts.ignore.map((path) => path.trim()).filter(Boolean))
210353
+ ];
210354
+ }
210355
+ if (Object.keys(body).length === 0) {
210356
+ throw new Error("pass at least one of --title, --instructions, --focus/--focus-json, --ignore, or --status");
210357
+ }
210358
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210359
+ const payload = await client.patch(datasetReviewRequestsPath(automationId, reviewId), body);
210360
+ if (opts.json)
210361
+ return printJson2(payload);
210362
+ const updated = payload;
210363
+ success(`Updated review request ${ui.bold(updated.id ?? reviewId)}` + (updated.status ? ` ${ui.dim(`(${updated.status})`)}` : ""));
210364
+ }));
210365
+ addJsonFlag(withBaseUrl(reviewRequest.command("items <automation-id> <review-id>"))).description("List snapshotted review items and their statuses.").option("--status <csv>", "Filter by item status (pending,approved,edited,rejected)").action(action(async (automationRef, reviewId, opts) => {
210366
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210367
+ const params = {};
210368
+ if (opts.status)
210369
+ params.status = opts.status;
210370
+ const payload = await client.get(datasetReviewRequestItemsPath(automationId, reviewId), params);
210371
+ if (opts.json)
210372
+ return printJson2(payload);
210373
+ const items = payload.items ?? [];
210374
+ console.log(table(items.map((item) => ({
210375
+ exampleName: item.exampleName,
210376
+ status: item.status,
210377
+ inputDrifted: item.inputDrifted ? "yes" : "no"
210378
+ })), [
210379
+ { key: "exampleName", header: "example" },
210380
+ { key: "status", header: "status" },
210381
+ { key: "inputDrifted", header: "inputDrifted" }
210382
+ ]));
210383
+ }));
210384
+ addJsonFlag(withBaseUrl(reviewRequest.command("events <automation-id> <review-id>"))).description("List review activity, including example and field notes.").action(action(async (automationRef, reviewId, opts) => {
210385
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210386
+ const payload = await client.get(datasetReviewRequestEventsPath(automationId, reviewId));
210387
+ if (opts.json)
210388
+ return printJson2(payload);
210389
+ const events = payload.events ?? [];
210390
+ console.log(table(events.map((event) => ({
210391
+ action: event.action,
210392
+ itemId: event.itemId ?? "-",
210393
+ field: event.diffSummary?.fieldPath ?? "-",
210394
+ comment: event.diffSummary?.comment ?? "-",
210395
+ createdAt: event.createdAt ? formatTimestamp(event.createdAt) : "-"
210396
+ })), [
210397
+ { key: "action", header: "action" },
210398
+ { key: "itemId", header: "item" },
210399
+ { key: "field", header: "field" },
210400
+ { key: "comment", header: "comment" },
210401
+ { key: "createdAt", header: "createdAt" }
210402
+ ]));
210403
+ }));
210404
+ addJsonFlag(withBaseUrl(reviewRequest.command("item <automation-id> <review-id> <item-id>"))).description("Approve, reject, reopen, comment, edit, or record a field-decision on one review item.").requiredOption("--action <approve|reject|reopen|comment|edit|field-decision>", "Item action").requiredOption("--expected-updated-at <iso>", "Item updatedAt the client last observed (optimistic concurrency)").option("--comment <text>", "Note stored on the item or field").option("--field-path <path>", "Dotted expected-output path for comment or field-decision").option("--decision <approved|rejected|null>", "Field decision for --action field-decision (null/clear removes it)").option("--clear", "Clear a field decision (sends decision: null)", false).option("--expected-json <json>", "Replacement expected JSON when --action edit").addHelpText("after", `
210405
+ Examples:
210406
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210407
+ --action field-decision --field-path vendor.iban --decision approved \\
210408
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210409
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210410
+ --action field-decision --field-path vendor.iban --clear \\
210411
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210412
+ $ eigenpal workflow dataset review-request item wf_abc123 dsr_... dsri_... \\
210413
+ --action reject --comment "wrong vendor" \\
210414
+ --expected-updated-at 2026-01-01T00:00:00.000Z --json
210415
+
210416
+ \`reject\` marks the example as not recommended for the dataset; it does not
210417
+ delete anything. \`--clear\` / \`--decision null\` removes a prior field
210418
+ decision (API: decision: null). Always pass \`--expected-updated-at\` from
210419
+ the item payload you last observed.
210420
+ `).action(action(async (automationRef, reviewId, itemId, opts) => {
210421
+ const { client, automationId } = await resolveAutomation(automationRef, opts);
210422
+ const body = buildReviewItemPatchBody(opts.action, {
210423
+ expectedUpdatedAt: opts.expectedUpdatedAt,
210424
+ comment: opts.comment,
210425
+ fieldPath: opts.fieldPath,
210426
+ decision: opts.decision,
210427
+ clear: opts.clear,
210428
+ expectedJson: opts.expectedJson
210429
+ });
210430
+ const payload = await client.patch(datasetReviewRequestItemsPath(automationId, reviewId, itemId), body);
210431
+ if (opts.json)
210432
+ return printJson2(payload);
210433
+ const item = payload.item;
210434
+ success(`Updated ${ui.bold(item?.exampleName ?? itemId)}` + (item?.status ? ` ${ui.dim(`(${item.status})`)}` : ""));
210435
+ }));
210436
+ }
210437
+
210040
210438
  // src/commands/agents/validation.ts
210041
210439
  init_src();
210042
210440
  import { existsSync as existsSync7, promises as fs2 } from "node:fs";
@@ -210342,7 +210740,7 @@ function validateValuesAgainstSchemaProps(data, schema, label) {
210342
210740
  }
210343
210741
  function validateValueAgainstSchema(value, schema, label) {
210344
210742
  try {
210345
- const validate = eigenpalAjv.compile(schema);
210743
+ const validate = getEigenpalAjv().compile(schema);
210346
210744
  if (validate(value))
210347
210745
  return [];
210348
210746
  return (validate.errors ?? []).map((err) => {
@@ -210377,6 +210775,11 @@ Layout
210377
210775
  (referenced via { "$file": "input/<path>" }), plus optional
210378
210776
  expected.json, expected/<file>, and meta.json.
210379
210777
  Run \`eigenpal agents dataset validate\` first to check locally.
210778
+
210779
+ After pushing examples for a new agent, request a dataset review before
210780
+ iterating to perfection: \`agents dataset review-request create <agent>
210781
+ --title ... --example-name ... --status open\`, poll \`.progress.complete\`,
210782
+ then close the request when review is finished.
210380
210783
  `).action(action(pushDataset));
210381
210784
  withBaseUrl(dataset.command("pull <agent-id-or-slug>")).description("Download an agent dataset as a .zip archive.").option("--out <path>", "Output .zip path (default: dataset.zip)", DATASET_DIR).action(action(pullDataset));
210382
210785
  addJsonFlag(dataset.command("validate [path]")).description("Validate a local dataset directory against the canonical examples/<name> layout. Defaults to ./dataset/.").option("--agent-dir <dir>", "Agent package directory with optional input/output schemas for extra value-level checks", ".").addHelpText("after", `
@@ -210394,6 +210797,10 @@ Layout (mirrors the server import rules)
210394
210797
  ` + ` workflow-only and rejected for agent datasets: agent runs are evaluated
210395
210798
  ` + ` only when they complete.
210396
210799
  `).action(action(validateDatasetCommand));
210800
+ registerDatasetReviewRequestCommands(dataset, async (agentId, opts) => {
210801
+ const client = buildClient2(opts);
210802
+ return { client, automationId: agentAutomationId(agentId) };
210803
+ });
210397
210804
  }
210398
210805
  async function listDataset(agentId, opts) {
210399
210806
  const client = buildClient2(opts);
@@ -247249,6 +247656,11 @@ Examples:
247249
247656
  \`--mode append\` is the default; \`--mode replace\` deletes every existing example
247250
247657
  + its files first and prompts a typed-slug confirmation in TTY (use \`--yes\` to
247251
247658
  skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset-format.md\`.
247659
+
247660
+ After pushing examples for a new workflow, request a dataset review before
247661
+ iterating to perfection: \`dataset review-request create <workflow-id>
247662
+ --title ... --example-name ... --status open\`, poll \`.progress.complete\`,
247663
+ then close the request when review is finished.
247252
247664
  `);
247253
247665
  addJsonFlag(withBaseUrl(pushCmd)).action(action(async (workflow, opts) => {
247254
247666
  if (opts.mode !== "append" && opts.mode !== "replace") {
@@ -247340,7 +247752,12 @@ skip in CI). Folder layout reference: \`packages/cli/src/skill/reference/dataset
247340
247752
  }));
247341
247753
  registerDatasetCrudCommands(dataset);
247342
247754
  registerDatasetValidateCommand(dataset);
247755
+ registerDatasetReviewRequestCommands(dataset, resolveAutomationForReviewRequest);
247343
247756
  }
247757
+ var resolveAutomationForReviewRequest = async (workflow, opts) => {
247758
+ const { client, workflowId } = await buildClientForWorkflow(workflow, opts);
247759
+ return { client, automationId: workflowId };
247760
+ };
247344
247761
  async function readJsonInput(literal, filePath, flagName) {
247345
247762
  if (literal !== undefined && filePath !== undefined) {
247346
247763
  throw new Error(`pass either --${flagName}-json or --${flagName}-file, not both`);