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