@erdoai/cli 0.55.1 → 0.56.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 +142 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -362,11 +362,17 @@ var ErdoClient = class {
|
|
|
362
362
|
`/v1/evals/suites/${encodeURIComponent(slug)}/cases/${encodeURIComponent(caseName)}`
|
|
363
363
|
);
|
|
364
364
|
}
|
|
365
|
-
runEvalSuite(slug, concurrency) {
|
|
365
|
+
runEvalSuite(slug, concurrency, commitSha, frontendCommitSha, backendCommitSha, source) {
|
|
366
366
|
return this.request(
|
|
367
367
|
"POST",
|
|
368
368
|
`/v1/evals/suites/${encodeURIComponent(slug)}/run`,
|
|
369
|
-
{
|
|
369
|
+
{
|
|
370
|
+
concurrency: concurrency ?? 0,
|
|
371
|
+
commit_sha: commitSha,
|
|
372
|
+
frontend_commit_sha: frontendCommitSha,
|
|
373
|
+
backend_commit_sha: backendCommitSha,
|
|
374
|
+
source
|
|
375
|
+
}
|
|
370
376
|
);
|
|
371
377
|
}
|
|
372
378
|
getEvalRun(runID) {
|
|
@@ -375,6 +381,14 @@ var ErdoClient = class {
|
|
|
375
381
|
`/v1/evals/runs/${encodeURIComponent(runID)}`
|
|
376
382
|
);
|
|
377
383
|
}
|
|
384
|
+
getEvalWidgetCanary() {
|
|
385
|
+
return this.request("GET", "/v1/evals/widget-canary");
|
|
386
|
+
}
|
|
387
|
+
configureEvalWidgetCanary(widget) {
|
|
388
|
+
return this.request("PUT", "/v1/evals/widget-canary", {
|
|
389
|
+
widget
|
|
390
|
+
});
|
|
391
|
+
}
|
|
378
392
|
// --- projects ---
|
|
379
393
|
listProjects(params) {
|
|
380
394
|
const q = new URLSearchParams();
|
|
@@ -866,6 +880,11 @@ var ErdoClient = class {
|
|
|
866
880
|
setKnowledgeVisibility(id, visibility) {
|
|
867
881
|
return this.request("PATCH", `/v1/knowledge/${id}/visibility`, { visibility });
|
|
868
882
|
}
|
|
883
|
+
// asset_id is what a page's `assetId` field takes — attaching an image here
|
|
884
|
+
// is how it becomes something a published page can render.
|
|
885
|
+
attachKnowledgeAsset(objectID, input) {
|
|
886
|
+
return this.request("POST", `/v1/knowledge/${encodeURIComponent(objectID)}/attachments`, input);
|
|
887
|
+
}
|
|
869
888
|
// --- automations (heartbeats) ---
|
|
870
889
|
listHeartbeats() {
|
|
871
890
|
return this.request("GET", "/v1/heartbeats");
|
|
@@ -1384,6 +1403,20 @@ function summariseResults(results, cases = []) {
|
|
|
1384
1403
|
}
|
|
1385
1404
|
}
|
|
1386
1405
|
}
|
|
1406
|
+
function printEvalRunAttribution(run) {
|
|
1407
|
+
console.log(`source: ${run.triggered_by}`);
|
|
1408
|
+
if (run.commit_sha) console.log(`commit: ${run.commit_sha}`);
|
|
1409
|
+
if (run.frontend_commit_sha) console.log(`frontend commit: ${run.frontend_commit_sha}`);
|
|
1410
|
+
if (run.backend_commit_sha) console.log(`backend commit: ${run.backend_commit_sha}`);
|
|
1411
|
+
}
|
|
1412
|
+
function compactEvalRunAttribution(run) {
|
|
1413
|
+
const revisions = [
|
|
1414
|
+
run.commit_sha ? `commit ${run.commit_sha.slice(0, 12)}` : void 0,
|
|
1415
|
+
run.frontend_commit_sha ? `frontend ${run.frontend_commit_sha.slice(0, 12)}` : void 0,
|
|
1416
|
+
run.backend_commit_sha ? `backend ${run.backend_commit_sha.slice(0, 12)}` : void 0
|
|
1417
|
+
].filter((part) => part !== void 0);
|
|
1418
|
+
return [run.triggered_by, ...revisions].join(" ");
|
|
1419
|
+
}
|
|
1387
1420
|
var program = new Command();
|
|
1388
1421
|
program.name("erdo").description("Erdo CLI").version(pkg.version).option("--org <idOrSlug>", "org to target for this command (overrides the active org)").option("--project <uuid>", "project context for this command (must belong to the active org)");
|
|
1389
1422
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -2228,18 +2261,41 @@ function caseScoringPayload(opts) {
|
|
|
2228
2261
|
}
|
|
2229
2262
|
return out;
|
|
2230
2263
|
}
|
|
2231
|
-
|
|
2264
|
+
function parseEvalThreshold(value) {
|
|
2265
|
+
if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim())) {
|
|
2266
|
+
throw new Error("threshold must be greater than 0 and at most 5");
|
|
2267
|
+
}
|
|
2268
|
+
const threshold = Number(value);
|
|
2269
|
+
if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 5) {
|
|
2270
|
+
throw new Error("threshold must be greater than 0 and at most 5");
|
|
2271
|
+
}
|
|
2272
|
+
return threshold;
|
|
2273
|
+
}
|
|
2274
|
+
function parseStrictBoolean(value) {
|
|
2275
|
+
switch (value.trim().toLowerCase()) {
|
|
2276
|
+
case "true":
|
|
2277
|
+
case "1":
|
|
2278
|
+
case "yes":
|
|
2279
|
+
return true;
|
|
2280
|
+
case "false":
|
|
2281
|
+
case "0":
|
|
2282
|
+
case "no":
|
|
2283
|
+
return false;
|
|
2284
|
+
default:
|
|
2285
|
+
throw new Error("value must be true or false");
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
evalCmd.command("update <slug>").description("Update a suite's settings (only the flags you pass change)").option("--name <name>", "new name").option("--description <text>", "new description").option("--agent <key>", "repoint the agent under test, e.g. erdo.data-question-answerer").option("--judge <model>", "new judge model").option("--threshold <n>", "new pass threshold >0 and <=5", parseEvalThreshold).option("--evaluate-artifact <bool>", "judge the rendered page (true/false)").option("--cron <bool>", "include in the daily cron (true/false)").action(
|
|
2232
2289
|
async (slug, opts) => {
|
|
2233
2290
|
try {
|
|
2234
|
-
const parseBool = (v) => v === void 0 ? void 0 : v === "true" || v === "1" || v === "yes";
|
|
2235
2291
|
const res = await new ErdoClient().updateEvalSuite(slug, {
|
|
2236
2292
|
name: opts.name,
|
|
2237
2293
|
description: opts.description,
|
|
2238
2294
|
agent_key: opts.agent,
|
|
2239
2295
|
judge_model: opts.judge,
|
|
2240
2296
|
pass_threshold: opts.threshold,
|
|
2241
|
-
evaluate_artifact:
|
|
2242
|
-
cron_enabled:
|
|
2297
|
+
evaluate_artifact: opts.evaluateArtifact === void 0 ? void 0 : parseStrictBoolean(opts.evaluateArtifact),
|
|
2298
|
+
cron_enabled: opts.cron === void 0 ? void 0 : parseStrictBoolean(opts.cron)
|
|
2243
2299
|
});
|
|
2244
2300
|
print(res);
|
|
2245
2301
|
} catch (e) {
|
|
@@ -2247,7 +2303,7 @@ evalCmd.command("update <slug>").description("Update a suite's settings (only th
|
|
|
2247
2303
|
}
|
|
2248
2304
|
}
|
|
2249
2305
|
);
|
|
2250
|
-
evalCmd.command("create <name>").description("Create a suite (in the active org)").requiredOption("--agent <key>", "agent under test, e.g. erdo.artifact-builder").option("--description <text>", "description").option("--judge <model>", "judge model").option("--threshold <n>", "pass threshold 0
|
|
2306
|
+
evalCmd.command("create <name>").description("Create a suite (in the active org)").requiredOption("--agent <key>", "agent under test, e.g. erdo.artifact-builder").option("--description <text>", "description").option("--judge <model>", "judge model").option("--threshold <n>", "pass threshold >0 and <=5", parseEvalThreshold).option("--evaluate-artifact", "judge the rendered page (visual suite)").option("--no-cron", "exclude from the daily cron (recommended for artifact suites)").requiredOption("--case <json>", "first case: {name,input,rubric,tags?}", (v, acc) => {
|
|
2251
2307
|
acc.push(v);
|
|
2252
2308
|
return acc;
|
|
2253
2309
|
}, []).action(
|
|
@@ -2271,7 +2327,7 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
|
|
|
2271
2327
|
}
|
|
2272
2328
|
}
|
|
2273
2329
|
);
|
|
2274
|
-
evalCmd.command("run <slug>").description("Run a suite; --watch polls until it completes").option("-w, --watch", "poll until the run finishes and print results").option("-c, --concurrency <n>", "parallel cases", (v) => parseInt(v, 10)).action(async (slug, opts) => {
|
|
2330
|
+
evalCmd.command("run <slug>").description("Run a suite; --watch polls until it completes").option("-w, --watch", "poll until the run finishes and print results").option("-c, --concurrency <n>", "parallel cases", (v) => parseInt(v, 10)).option("--commit-sha <sha>", "full 40-character Git commit SHA to associate with the run").option("--frontend-commit-sha <sha>", "frontend revision in the evaluated production snapshot").option("--backend-commit-sha <sha>", "backend revision in the evaluated production snapshot").option("--source <source>", "staff-only run source: post_deploy, nightly, or rollback").action(async (slug, opts) => {
|
|
2275
2331
|
try {
|
|
2276
2332
|
const api = new ErdoClient();
|
|
2277
2333
|
const { suite } = await api.getEvalSuite(slug);
|
|
@@ -2287,7 +2343,14 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
|
|
|
2287
2343
|
);
|
|
2288
2344
|
process.exit(1);
|
|
2289
2345
|
}
|
|
2290
|
-
const { run_id } = await api.runEvalSuite(
|
|
2346
|
+
const { run_id } = await api.runEvalSuite(
|
|
2347
|
+
slug,
|
|
2348
|
+
opts.concurrency,
|
|
2349
|
+
opts.commitSha,
|
|
2350
|
+
opts.frontendCommitSha,
|
|
2351
|
+
opts.backendCommitSha,
|
|
2352
|
+
opts.source
|
|
2353
|
+
);
|
|
2291
2354
|
console.log(`run_id: ${run_id}`);
|
|
2292
2355
|
if (!opts.watch) return;
|
|
2293
2356
|
for (; ; ) {
|
|
@@ -2301,6 +2364,7 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
|
|
|
2301
2364
|
`
|
|
2302
2365
|
${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
|
|
2303
2366
|
);
|
|
2367
|
+
printEvalRunAttribution(run);
|
|
2304
2368
|
summariseResults(results, cases);
|
|
2305
2369
|
if (run.status !== "completed" || run.passed_cases < run.total_cases) {
|
|
2306
2370
|
process.exitCode = 1;
|
|
@@ -2322,6 +2386,7 @@ evalCmd.command("results <runId>").description("Show a run's results").option("-
|
|
|
2322
2386
|
console.log(
|
|
2323
2387
|
`${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
|
|
2324
2388
|
);
|
|
2389
|
+
printEvalRunAttribution(run);
|
|
2325
2390
|
summariseResults(results, cases);
|
|
2326
2391
|
} catch (e) {
|
|
2327
2392
|
fail(e);
|
|
@@ -2332,9 +2397,41 @@ evalCmd.command("runs").description("List recent runs").option("-s, --suite <slu
|
|
|
2332
2397
|
const { runs } = await new ErdoClient().listEvalRuns(opts.suite, opts.limit);
|
|
2333
2398
|
for (const r of runs) {
|
|
2334
2399
|
console.log(
|
|
2335
|
-
`${r.id} ${r.status} ${r.passed_cases}/${r.total_cases} avg ${r.avg_score.toFixed(2)}`
|
|
2400
|
+
`${r.id} ${r.status} ${r.passed_cases}/${r.total_cases} avg ${r.avg_score.toFixed(2)} ${compactEvalRunAttribution(r)}`
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
} catch (e) {
|
|
2404
|
+
fail(e);
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
var evalCanaryCmd = evalCmd.command("canary").description("Show or configure the current organization's deployed-experience widget canary (staff only)");
|
|
2408
|
+
evalCanaryCmd.command("show").description("Show the widget designated as this organization's eval canary").action(async () => {
|
|
2409
|
+
try {
|
|
2410
|
+
print(await new ErdoClient().getEvalWidgetCanary());
|
|
2411
|
+
} catch (e) {
|
|
2412
|
+
fail(e);
|
|
2413
|
+
}
|
|
2414
|
+
});
|
|
2415
|
+
evalCanaryCmd.command("set <widget>").description("Designate a widget by unique name or public key in the pinned organization").action(async (widget) => {
|
|
2416
|
+
try {
|
|
2417
|
+
if (!process.env.ERDO_ORG) {
|
|
2418
|
+
throw new Error(
|
|
2419
|
+
"Refusing to change an eval canary without a pinned org. Re-run as: erdo --org <id|slug> eval canary set <widget-name-or-public-key>"
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
print(await new ErdoClient().configureEvalWidgetCanary(widget));
|
|
2423
|
+
} catch (e) {
|
|
2424
|
+
fail(e);
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
evalCanaryCmd.command("clear").description("Clear the pinned organization's eval widget canary").action(async () => {
|
|
2428
|
+
try {
|
|
2429
|
+
if (!process.env.ERDO_ORG) {
|
|
2430
|
+
throw new Error(
|
|
2431
|
+
"Refusing to clear an eval canary without a pinned org. Re-run as: erdo --org <id|slug> eval canary clear"
|
|
2336
2432
|
);
|
|
2337
2433
|
}
|
|
2434
|
+
print(await new ErdoClient().configureEvalWidgetCanary(""));
|
|
2338
2435
|
} catch (e) {
|
|
2339
2436
|
fail(e);
|
|
2340
2437
|
}
|
|
@@ -2345,6 +2442,9 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
|
|
|
2345
2442
|
"a turn to run BEFORE input, same thread/agent (repeatable; e.g. 'create a voice widget for X')",
|
|
2346
2443
|
collect,
|
|
2347
2444
|
[]
|
|
2445
|
+
).option(
|
|
2446
|
+
"--target-type <type>",
|
|
2447
|
+
"closed deployed target: live_page_fleet, widget_fleet_live, widget_text_canary, widget_voice_canary, or widget_video_canary"
|
|
2348
2448
|
).option("--rubric <json>", 'LLM rubric: JSON array of {"criterion","weight"}').option("--evaluator <json>", "evaluator JSON {type,weight,...} (repeatable)", collect, []).option("--tags <csv>", "comma-separated tags").action(
|
|
2349
2449
|
async (slug, opts) => {
|
|
2350
2450
|
try {
|
|
@@ -2352,6 +2452,7 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
|
|
|
2352
2452
|
name: opts.name,
|
|
2353
2453
|
input: opts.input,
|
|
2354
2454
|
setup_messages: opts.setup,
|
|
2455
|
+
target_type: opts.targetType,
|
|
2355
2456
|
...caseScoringPayload(opts),
|
|
2356
2457
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
|
|
2357
2458
|
});
|
|
@@ -2361,13 +2462,19 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
|
|
|
2361
2462
|
}
|
|
2362
2463
|
}
|
|
2363
2464
|
);
|
|
2364
|
-
caseCmd.command("update <slug> <caseName>").description("Replace a case's brief, scoring, and tags").requiredOption("--input <brief>", "replacement brief/prompt (the evaluated turn)").option("--setup <msg>", "replacement pre-turn run before input (repeatable; omit
|
|
2465
|
+
caseCmd.command("update <slug> <caseName>").description("Replace a case's brief, scoring, and tags").requiredOption("--input <brief>", "replacement brief/prompt (the evaluated turn)").option("--setup <msg>", "replacement pre-turn run before input (repeatable; omit to preserve)", collect, []).option(
|
|
2466
|
+
"--target-type <type>",
|
|
2467
|
+
"replacement deployed target: live_page_fleet, widget_fleet_live, widget_text_canary, widget_voice_canary, or widget_video_canary (omit to preserve)"
|
|
2468
|
+
).option("--clear-setup", "remove all existing setup turns").option("--clear-target", "remove the deployed target and return to ordinary agent execution").option("--rubric <json>", "replacement LLM rubric JSON").option("--evaluator <json>", "evaluator JSON {type,weight,...} (repeatable)", collect, []).option("--tags <csv>", "comma-separated tags").action(
|
|
2365
2469
|
async (slug, caseName, opts) => {
|
|
2366
2470
|
try {
|
|
2367
2471
|
const res = await new ErdoClient().updateEvalCase(slug, caseName, {
|
|
2368
2472
|
name: caseName,
|
|
2369
2473
|
input: opts.input,
|
|
2370
2474
|
setup_messages: opts.setup,
|
|
2475
|
+
target_type: opts.targetType,
|
|
2476
|
+
clear_setup: !!opts.clearSetup,
|
|
2477
|
+
clear_target: !!opts.clearTarget,
|
|
2371
2478
|
...caseScoringPayload(opts),
|
|
2372
2479
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
|
|
2373
2480
|
});
|
|
@@ -3929,6 +4036,30 @@ knowledgeCmd.command("visibility <id> <visibility>").description(
|
|
|
3929
4036
|
fail(e);
|
|
3930
4037
|
}
|
|
3931
4038
|
});
|
|
4039
|
+
knowledgeCmd.command("attach <objectId> <file>").description(
|
|
4040
|
+
"Attach an image or other small binary to a knowledge object as an org-hosted asset, and print its asset id \u2014 the value a page's assetId field takes. Pass a local path, or --url to have the service download it (the only path for video)."
|
|
4041
|
+
).option("--url <sourceUrl>", "download from this URL instead of reading a local file").option(
|
|
4042
|
+
"-d, --description <text>",
|
|
4043
|
+
"caption for the asset; for an image this is the only text search can match, so describe what is in the picture"
|
|
4044
|
+
).option("--media-type <mime>", "MIME type hint, e.g. image/jpeg; detected from the filename otherwise").action(
|
|
4045
|
+
async (objectId, file, opts) => {
|
|
4046
|
+
try {
|
|
4047
|
+
const body = opts.url ? { filename: basename(file), source_url: opts.url } : {
|
|
4048
|
+
filename: basename(file),
|
|
4049
|
+
content_base64: readFileSync3(file).toString("base64")
|
|
4050
|
+
};
|
|
4051
|
+
print(
|
|
4052
|
+
await new ErdoClient().attachKnowledgeAsset(objectId, {
|
|
4053
|
+
...body,
|
|
4054
|
+
description: opts.description,
|
|
4055
|
+
media_type: opts.mediaType
|
|
4056
|
+
})
|
|
4057
|
+
);
|
|
4058
|
+
} catch (e) {
|
|
4059
|
+
fail(e);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
);
|
|
3932
4063
|
var autoCmd = program.command("automations").description("Scheduled automations (heartbeats)");
|
|
3933
4064
|
autoCmd.command("list").description("List automations").action(async () => {
|
|
3934
4065
|
try {
|