@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 +6 -0
- package/dist/cli.js +427 -10
- package/dist/docs-bundle.json +24 -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
|
@@ -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
|
-
|
|
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
|
-
|
|
15485
|
-
|
|
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()).
|
|
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.
|
|
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 =
|
|
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`);
|