@erdoai/cli 0.25.0 → 0.27.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 +96 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1298,9 +1298,45 @@ wsCmd.command("attach <slug>").description("Link a resource (page, dataset, hear
|
|
|
1298
1298
|
}
|
|
1299
1299
|
}
|
|
1300
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
|
+
}
|
|
1301
1337
|
function parseVariant(spec) {
|
|
1302
1338
|
const v = {};
|
|
1303
|
-
for (const raw of spec
|
|
1339
|
+
for (const raw of splitVariantSpec(spec)) {
|
|
1304
1340
|
const part = raw.trim();
|
|
1305
1341
|
if (!part) continue;
|
|
1306
1342
|
const eq = part.indexOf("=");
|
|
@@ -1309,7 +1345,7 @@ function parseVariant(spec) {
|
|
|
1309
1345
|
v.is_control = true;
|
|
1310
1346
|
continue;
|
|
1311
1347
|
}
|
|
1312
|
-
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')`);
|
|
1313
1349
|
}
|
|
1314
1350
|
const k = part.slice(0, eq).trim();
|
|
1315
1351
|
const val = part.slice(eq + 1).trim();
|
|
@@ -1333,6 +1369,10 @@ function parseVariant(spec) {
|
|
|
1333
1369
|
v.treatment_resource_type = "page";
|
|
1334
1370
|
v.treatment_resource_id = val;
|
|
1335
1371
|
break;
|
|
1372
|
+
case "url":
|
|
1373
|
+
case "treatment_url":
|
|
1374
|
+
v.treatment_url = val;
|
|
1375
|
+
break;
|
|
1336
1376
|
case "type":
|
|
1337
1377
|
case "treatment_resource_type":
|
|
1338
1378
|
v.treatment_resource_type = val;
|
|
@@ -1352,6 +1392,9 @@ function parseVariant(spec) {
|
|
|
1352
1392
|
if (!v.key) {
|
|
1353
1393
|
throw new Error('--variant requires key=, e.g. --variant "key=control,label=Current,control"');
|
|
1354
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
|
+
}
|
|
1355
1398
|
if (!v.label) v.label = v.key;
|
|
1356
1399
|
return v;
|
|
1357
1400
|
}
|
|
@@ -1412,7 +1455,7 @@ expCmd.command("policy <slug>").description(
|
|
|
1412
1455
|
});
|
|
1413
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(
|
|
1414
1457
|
"--variant <spec>",
|
|
1415
|
-
|
|
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`,
|
|
1416
1459
|
collect,
|
|
1417
1460
|
[]
|
|
1418
1461
|
).option(
|
|
@@ -1980,34 +2023,60 @@ attnCmd.command("list").description("List attention items").option("--status <st
|
|
|
1980
2023
|
}
|
|
1981
2024
|
);
|
|
1982
2025
|
attnCmd.command("respond <id>").description(
|
|
1983
|
-
`Respond to an attention item. A choice answer's shape is {"<question_id>": ["<option>", ...]}. Answering a choice mapped to experiment variants records your pick as a human comparison in the calibration ledger.`
|
|
1984
|
-
).option("--answer <json>", `a JSON answer, e.g. '{"q1":["option-a"]}'`).option(
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2026
|
+
`Respond to an attention item. A choice answer's shape is {"<question_id>": ["<option>", ...]}. Answering a choice mapped to experiment variants records your pick as a human comparison in the calibration ledger. Use --flag-broken to flag one or more variants as broken (recorded as defect observations judge generation reads) \u2014 on its own or alongside --answer.`
|
|
2027
|
+
).option("--answer <json>", `a JSON answer, e.g. '{"q1":["option-a"]}'`).option(
|
|
2028
|
+
"--flag-broken <json>",
|
|
2029
|
+
'flag choice variants as broken: JSON array [{"variant_key":"...","reason":"..."}]'
|
|
2030
|
+
).option("--ack", "mark read / acknowledge").option("--dismiss", "dismiss the item").action(
|
|
2031
|
+
async (id, opts) => {
|
|
2032
|
+
try {
|
|
2033
|
+
if (opts.ack || opts.dismiss) {
|
|
2034
|
+
if (opts.ack && opts.dismiss) {
|
|
2035
|
+
fail(new Error("provide only one of --ack or --dismiss"));
|
|
2036
|
+
}
|
|
2037
|
+
if (opts.answer !== void 0 || opts.flagBroken !== void 0) {
|
|
2038
|
+
fail(new Error("--answer / --flag-broken cannot be combined with --ack or --dismiss"));
|
|
2039
|
+
}
|
|
2040
|
+
print(
|
|
2041
|
+
await new ErdoClient().respondAttentionItem(id, {
|
|
2042
|
+
action: opts.ack ? "acknowledge" : "dismiss"
|
|
2043
|
+
})
|
|
1998
2044
|
);
|
|
2045
|
+
return;
|
|
1999
2046
|
}
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2047
|
+
if (opts.answer === void 0 && opts.flagBroken === void 0) {
|
|
2048
|
+
fail(new Error("provide one of --answer, --flag-broken, --ack, or --dismiss"));
|
|
2049
|
+
}
|
|
2050
|
+
const input = { action: "answer" };
|
|
2051
|
+
if (opts.answer !== void 0) {
|
|
2052
|
+
try {
|
|
2053
|
+
input.answer = JSON.parse(opts.answer);
|
|
2054
|
+
} catch {
|
|
2055
|
+
throw new Error(
|
|
2056
|
+
`--answer must be valid JSON, e.g. '{"<question_id>": ["<option>", ...]}'`
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
if (opts.flagBroken !== void 0) {
|
|
2061
|
+
let parsed;
|
|
2062
|
+
try {
|
|
2063
|
+
parsed = JSON.parse(opts.flagBroken);
|
|
2064
|
+
} catch {
|
|
2065
|
+
throw new Error(
|
|
2066
|
+
`--flag-broken must be valid JSON, e.g. '[{"variant_key":"b","reason":"clipped hero"}]'`
|
|
2067
|
+
);
|
|
2068
|
+
}
|
|
2069
|
+
if (!Array.isArray(parsed)) {
|
|
2070
|
+
throw new Error('--flag-broken must be a JSON array of {"variant_key","reason"} objects');
|
|
2071
|
+
}
|
|
2072
|
+
input.defects = parsed;
|
|
2073
|
+
}
|
|
2074
|
+
print(await new ErdoClient().respondAttentionItem(id, input));
|
|
2075
|
+
} catch (e) {
|
|
2076
|
+
fail(e);
|
|
2005
2077
|
}
|
|
2006
|
-
print(await new ErdoClient().respondAttentionItem(id, input));
|
|
2007
|
-
} catch (e) {
|
|
2008
|
-
fail(e);
|
|
2009
2078
|
}
|
|
2010
|
-
|
|
2079
|
+
);
|
|
2011
2080
|
var reviewsCmd = program.command("reviews").description(
|
|
2012
2081
|
"The Knowledge Review queue \u2014 knowledge patches the critic proposes, investigations, and deduplicated failure signals awaiting a human decision"
|
|
2013
2082
|
);
|