@erdoai/cli 0.24.0 → 0.26.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/dist/index.js +126 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -427,6 +427,32 @@ var ErdoClient = class {
|
|
|
427
427
|
input
|
|
428
428
|
);
|
|
429
429
|
}
|
|
430
|
+
// --- knowledge review queue ---
|
|
431
|
+
listReviewItems(params) {
|
|
432
|
+
const q = new URLSearchParams();
|
|
433
|
+
if (params?.status !== void 0) q.set("status", params.status);
|
|
434
|
+
if (params?.type) q.set("type", params.type);
|
|
435
|
+
if (params?.limit) q.set("limit", String(params.limit));
|
|
436
|
+
if (params?.offset) q.set("offset", String(params.offset));
|
|
437
|
+
const qs = q.toString();
|
|
438
|
+
return this.request(
|
|
439
|
+
"GET",
|
|
440
|
+
`/v1/review-items${qs ? `?${qs}` : ""}`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
getReviewItem(id) {
|
|
444
|
+
return this.request(
|
|
445
|
+
"GET",
|
|
446
|
+
`/v1/review-items/${encodeURIComponent(id)}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
decideReviewItem(id, input) {
|
|
450
|
+
return this.request(
|
|
451
|
+
"POST",
|
|
452
|
+
`/v1/review-items/${encodeURIComponent(id)}/decide`,
|
|
453
|
+
input
|
|
454
|
+
);
|
|
455
|
+
}
|
|
430
456
|
// --- agents ---
|
|
431
457
|
ask(input) {
|
|
432
458
|
return this.request("POST", "/v1/ask", input);
|
|
@@ -1272,9 +1298,45 @@ wsCmd.command("attach <slug>").description("Link a resource (page, dataset, hear
|
|
|
1272
1298
|
}
|
|
1273
1299
|
}
|
|
1274
1300
|
);
|
|
1301
|
+
var VARIANT_SPEC_KEYS = /* @__PURE__ */ new Set([
|
|
1302
|
+
"key",
|
|
1303
|
+
"label",
|
|
1304
|
+
"alloc",
|
|
1305
|
+
"allocation_percent",
|
|
1306
|
+
"control",
|
|
1307
|
+
"is_control",
|
|
1308
|
+
"page",
|
|
1309
|
+
"url",
|
|
1310
|
+
"treatment_url",
|
|
1311
|
+
"type",
|
|
1312
|
+
"treatment_resource_type",
|
|
1313
|
+
"resource",
|
|
1314
|
+
"treatment_resource_id",
|
|
1315
|
+
"class",
|
|
1316
|
+
"treatment_class"
|
|
1317
|
+
]);
|
|
1318
|
+
var COMMA_SAFE_VARIANT_KEYS = /* @__PURE__ */ new Set(["url", "treatment_url", "label"]);
|
|
1319
|
+
function splitVariantSpec(spec) {
|
|
1320
|
+
const parts = [];
|
|
1321
|
+
let lastKey = "";
|
|
1322
|
+
for (const raw of spec.split(",")) {
|
|
1323
|
+
const trimmed = raw.trim();
|
|
1324
|
+
if (!trimmed) continue;
|
|
1325
|
+
const eq = trimmed.indexOf("=");
|
|
1326
|
+
const key = eq > 0 ? trimmed.slice(0, eq).trim() : "";
|
|
1327
|
+
const startsToken = trimmed === "control" || trimmed === "is_control" || key !== "" && VARIANT_SPEC_KEYS.has(key);
|
|
1328
|
+
if (!startsToken && parts.length > 0 && COMMA_SAFE_VARIANT_KEYS.has(lastKey)) {
|
|
1329
|
+
parts[parts.length - 1] += "," + raw;
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
parts.push(raw);
|
|
1333
|
+
lastKey = startsToken && key !== "" ? key : "";
|
|
1334
|
+
}
|
|
1335
|
+
return parts;
|
|
1336
|
+
}
|
|
1275
1337
|
function parseVariant(spec) {
|
|
1276
1338
|
const v = {};
|
|
1277
|
-
for (const raw of spec
|
|
1339
|
+
for (const raw of splitVariantSpec(spec)) {
|
|
1278
1340
|
const part = raw.trim();
|
|
1279
1341
|
if (!part) continue;
|
|
1280
1342
|
const eq = part.indexOf("=");
|
|
@@ -1283,7 +1345,7 @@ function parseVariant(spec) {
|
|
|
1283
1345
|
v.is_control = true;
|
|
1284
1346
|
continue;
|
|
1285
1347
|
}
|
|
1286
|
-
throw new Error(`--variant: unknown flag "${part}" (expected key=, label=, page=, alloc=, class=, or 'control')`);
|
|
1348
|
+
throw new Error(`--variant: unknown flag "${part}" (expected key=, label=, page=, url=, alloc=, class=, or 'control')`);
|
|
1287
1349
|
}
|
|
1288
1350
|
const k = part.slice(0, eq).trim();
|
|
1289
1351
|
const val = part.slice(eq + 1).trim();
|
|
@@ -1307,6 +1369,10 @@ function parseVariant(spec) {
|
|
|
1307
1369
|
v.treatment_resource_type = "page";
|
|
1308
1370
|
v.treatment_resource_id = val;
|
|
1309
1371
|
break;
|
|
1372
|
+
case "url":
|
|
1373
|
+
case "treatment_url":
|
|
1374
|
+
v.treatment_url = val;
|
|
1375
|
+
break;
|
|
1310
1376
|
case "type":
|
|
1311
1377
|
case "treatment_resource_type":
|
|
1312
1378
|
v.treatment_resource_type = val;
|
|
@@ -1326,6 +1392,9 @@ function parseVariant(spec) {
|
|
|
1326
1392
|
if (!v.key) {
|
|
1327
1393
|
throw new Error('--variant requires key=, e.g. --variant "key=control,label=Current,control"');
|
|
1328
1394
|
}
|
|
1395
|
+
if (v.treatment_url && (v.treatment_resource_type || v.treatment_resource_id)) {
|
|
1396
|
+
throw new Error("--variant: url= is mutually exclusive with page=/type=/resource= (a variant is either an Erdo page or an external URL)");
|
|
1397
|
+
}
|
|
1329
1398
|
if (!v.label) v.label = v.key;
|
|
1330
1399
|
return v;
|
|
1331
1400
|
}
|
|
@@ -1386,7 +1455,7 @@ expCmd.command("policy <slug>").description(
|
|
|
1386
1455
|
});
|
|
1387
1456
|
expCmd.command("create").description("Create an experiment with its variants, evidence datasets and decision rule").requiredOption("--project <slug>", "project slug").requiredOption("--slug <slug>", "experiment slug (unique per org)").requiredOption("--title <title>", "title").option("--workstream <slug>", "host workstream").option("--scope <scope>", "local (default), platform_canary, platform_global").option("--hypothesis <text>", "hypothesis markdown").option("--primary-metric <metric>", "the metric the decision rule reads").option("--decision-rule <text>", "the rule that decides ship/stop from the primary metric").option("--guardrail <metric>", "a metric that must not regress (repeatable)", collect, []).option(
|
|
1388
1457
|
"--variant <spec>",
|
|
1389
|
-
|
|
1458
|
+
`a variant "key=b,label=3-field form,page=<artifact-id>,alloc=50,class=form_length_change,control" (repeatable). Fields: key=, label=, page= (Erdo artifact id) OR url=<https://\u2026> (an external page the persona panel visits, e.g. a client's live landing page \u2014 mutually exclusive with page=), alloc=, class=, and the bare flag control (or control=true) to mark the baseline. class= declares the structural change vs control for cross-experiment learning: headline_change | hero_media_change | cta_change | form_length_change | social_proof_add | layout_reorder | offer_change | full_page_rebuild`,
|
|
1390
1459
|
collect,
|
|
1391
1460
|
[]
|
|
1392
1461
|
).option(
|
|
@@ -1982,6 +2051,60 @@ attnCmd.command("respond <id>").description(
|
|
|
1982
2051
|
fail(e);
|
|
1983
2052
|
}
|
|
1984
2053
|
});
|
|
2054
|
+
var reviewsCmd = program.command("reviews").description(
|
|
2055
|
+
"The Knowledge Review queue \u2014 knowledge patches the critic proposes, investigations, and deduplicated failure signals awaiting a human decision"
|
|
2056
|
+
);
|
|
2057
|
+
reviewsCmd.command("list").description("List review items (decision items rank above failure signals)").option("--status <status>", "open (default) | snoozed | resolved | rejected | '' for all").option("--type <type>", "filter by type, e.g. knowledge_patch, eval_failure, failed_run").option("-n, --limit <n>", "max items", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).action(
|
|
2058
|
+
async (opts) => {
|
|
2059
|
+
try {
|
|
2060
|
+
print(
|
|
2061
|
+
await new ErdoClient().listReviewItems({
|
|
2062
|
+
status: opts.status,
|
|
2063
|
+
type: opts.type,
|
|
2064
|
+
limit: opts.limit,
|
|
2065
|
+
offset: opts.offset
|
|
2066
|
+
})
|
|
2067
|
+
);
|
|
2068
|
+
} catch (e) {
|
|
2069
|
+
fail(e);
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
);
|
|
2073
|
+
reviewsCmd.command("show <id>").description("Show one review item with its full payload (the proposed patch body for a knowledge_patch)").action(async (id) => {
|
|
2074
|
+
try {
|
|
2075
|
+
print(await new ErdoClient().getReviewItem(id));
|
|
2076
|
+
} catch (e) {
|
|
2077
|
+
fail(e);
|
|
2078
|
+
}
|
|
2079
|
+
});
|
|
2080
|
+
reviewsCmd.command("decide <id>").description(
|
|
2081
|
+
"Decide a review item: --apply a knowledge patch (mutates Knowledge, then resolves), --resolve, --reject, or --snooze [minutes]"
|
|
2082
|
+
).option("--apply", "apply a knowledge_patch: create/update the proposed Knowledge object, then resolve").option("--resolve", "close as handled").option("--reject", "close as declined").option("--snooze [minutes]", "hide the item for [minutes] (default 7 days)").option("--note <note>", "resolution note recorded on the item").action(
|
|
2083
|
+
async (id, opts) => {
|
|
2084
|
+
try {
|
|
2085
|
+
const chosen = [
|
|
2086
|
+
opts.apply ? "apply" : null,
|
|
2087
|
+
opts.resolve ? "resolve" : null,
|
|
2088
|
+
opts.reject ? "reject" : null,
|
|
2089
|
+
opts.snooze !== void 0 ? "snooze" : null
|
|
2090
|
+
].filter(Boolean);
|
|
2091
|
+
if (chosen.length !== 1) {
|
|
2092
|
+
fail(new Error("provide exactly one of --apply, --resolve, --reject, or --snooze"));
|
|
2093
|
+
}
|
|
2094
|
+
const action = chosen[0];
|
|
2095
|
+
let minutes;
|
|
2096
|
+
if (action === "snooze" && typeof opts.snooze === "string") {
|
|
2097
|
+
minutes = parseInt(opts.snooze, 10);
|
|
2098
|
+
if (Number.isNaN(minutes)) {
|
|
2099
|
+
throw new Error("--snooze minutes must be a number");
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
print(await new ErdoClient().decideReviewItem(id, { action, minutes, note: opts.note }));
|
|
2103
|
+
} catch (e) {
|
|
2104
|
+
fail(e);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
1985
2108
|
var pagesCmd = program.command("pages").description("Create and manage pages/artifacts");
|
|
1986
2109
|
function readMaybeFile(v) {
|
|
1987
2110
|
if (!v) return void 0;
|