@erdoai/cli 0.55.0 → 0.55.3
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 -13
- 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();
|
|
@@ -783,6 +797,12 @@ var ErdoClient = class {
|
|
|
783
797
|
connectIntegration(body) {
|
|
784
798
|
return this.request("POST", "/v1/integrations-connect", body);
|
|
785
799
|
}
|
|
800
|
+
// `connected` reports whether the connection exists, which is not the same as
|
|
801
|
+
// whether it works: a key retired at the provider leaves status 'connected'
|
|
802
|
+
// here, because the connector platform keeps signing calls with it and simply
|
|
803
|
+
// relays the provider's refusal. `provider_auth_failed_at` is when that last
|
|
804
|
+
// happened, and the `note` says so in words — a connection carrying one needs
|
|
805
|
+
// reauthorizing even though nothing else looks wrong.
|
|
786
806
|
checkIntegrationConnection(app) {
|
|
787
807
|
return this.request("GET", `/v1/integrations-connect/${encodeURIComponent(app)}`);
|
|
788
808
|
}
|
|
@@ -1378,6 +1398,20 @@ function summariseResults(results, cases = []) {
|
|
|
1378
1398
|
}
|
|
1379
1399
|
}
|
|
1380
1400
|
}
|
|
1401
|
+
function printEvalRunAttribution(run) {
|
|
1402
|
+
console.log(`source: ${run.triggered_by}`);
|
|
1403
|
+
if (run.commit_sha) console.log(`commit: ${run.commit_sha}`);
|
|
1404
|
+
if (run.frontend_commit_sha) console.log(`frontend commit: ${run.frontend_commit_sha}`);
|
|
1405
|
+
if (run.backend_commit_sha) console.log(`backend commit: ${run.backend_commit_sha}`);
|
|
1406
|
+
}
|
|
1407
|
+
function compactEvalRunAttribution(run) {
|
|
1408
|
+
const revisions = [
|
|
1409
|
+
run.commit_sha ? `commit ${run.commit_sha.slice(0, 12)}` : void 0,
|
|
1410
|
+
run.frontend_commit_sha ? `frontend ${run.frontend_commit_sha.slice(0, 12)}` : void 0,
|
|
1411
|
+
run.backend_commit_sha ? `backend ${run.backend_commit_sha.slice(0, 12)}` : void 0
|
|
1412
|
+
].filter((part) => part !== void 0);
|
|
1413
|
+
return [run.triggered_by, ...revisions].join(" ");
|
|
1414
|
+
}
|
|
1381
1415
|
var program = new Command();
|
|
1382
1416
|
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)");
|
|
1383
1417
|
program.hook("preAction", (thisCommand) => {
|
|
@@ -2222,18 +2256,41 @@ function caseScoringPayload(opts) {
|
|
|
2222
2256
|
}
|
|
2223
2257
|
return out;
|
|
2224
2258
|
}
|
|
2225
|
-
|
|
2259
|
+
function parseEvalThreshold(value) {
|
|
2260
|
+
if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(value.trim())) {
|
|
2261
|
+
throw new Error("threshold must be greater than 0 and at most 5");
|
|
2262
|
+
}
|
|
2263
|
+
const threshold = Number(value);
|
|
2264
|
+
if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 5) {
|
|
2265
|
+
throw new Error("threshold must be greater than 0 and at most 5");
|
|
2266
|
+
}
|
|
2267
|
+
return threshold;
|
|
2268
|
+
}
|
|
2269
|
+
function parseStrictBoolean(value) {
|
|
2270
|
+
switch (value.trim().toLowerCase()) {
|
|
2271
|
+
case "true":
|
|
2272
|
+
case "1":
|
|
2273
|
+
case "yes":
|
|
2274
|
+
return true;
|
|
2275
|
+
case "false":
|
|
2276
|
+
case "0":
|
|
2277
|
+
case "no":
|
|
2278
|
+
return false;
|
|
2279
|
+
default:
|
|
2280
|
+
throw new Error("value must be true or false");
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
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(
|
|
2226
2284
|
async (slug, opts) => {
|
|
2227
2285
|
try {
|
|
2228
|
-
const parseBool = (v) => v === void 0 ? void 0 : v === "true" || v === "1" || v === "yes";
|
|
2229
2286
|
const res = await new ErdoClient().updateEvalSuite(slug, {
|
|
2230
2287
|
name: opts.name,
|
|
2231
2288
|
description: opts.description,
|
|
2232
2289
|
agent_key: opts.agent,
|
|
2233
2290
|
judge_model: opts.judge,
|
|
2234
2291
|
pass_threshold: opts.threshold,
|
|
2235
|
-
evaluate_artifact:
|
|
2236
|
-
cron_enabled:
|
|
2292
|
+
evaluate_artifact: opts.evaluateArtifact === void 0 ? void 0 : parseStrictBoolean(opts.evaluateArtifact),
|
|
2293
|
+
cron_enabled: opts.cron === void 0 ? void 0 : parseStrictBoolean(opts.cron)
|
|
2237
2294
|
});
|
|
2238
2295
|
print(res);
|
|
2239
2296
|
} catch (e) {
|
|
@@ -2241,7 +2298,7 @@ evalCmd.command("update <slug>").description("Update a suite's settings (only th
|
|
|
2241
2298
|
}
|
|
2242
2299
|
}
|
|
2243
2300
|
);
|
|
2244
|
-
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
|
|
2301
|
+
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) => {
|
|
2245
2302
|
acc.push(v);
|
|
2246
2303
|
return acc;
|
|
2247
2304
|
}, []).action(
|
|
@@ -2265,7 +2322,7 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
|
|
|
2265
2322
|
}
|
|
2266
2323
|
}
|
|
2267
2324
|
);
|
|
2268
|
-
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) => {
|
|
2325
|
+
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) => {
|
|
2269
2326
|
try {
|
|
2270
2327
|
const api = new ErdoClient();
|
|
2271
2328
|
const { suite } = await api.getEvalSuite(slug);
|
|
@@ -2281,7 +2338,14 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
|
|
|
2281
2338
|
);
|
|
2282
2339
|
process.exit(1);
|
|
2283
2340
|
}
|
|
2284
|
-
const { run_id } = await api.runEvalSuite(
|
|
2341
|
+
const { run_id } = await api.runEvalSuite(
|
|
2342
|
+
slug,
|
|
2343
|
+
opts.concurrency,
|
|
2344
|
+
opts.commitSha,
|
|
2345
|
+
opts.frontendCommitSha,
|
|
2346
|
+
opts.backendCommitSha,
|
|
2347
|
+
opts.source
|
|
2348
|
+
);
|
|
2285
2349
|
console.log(`run_id: ${run_id}`);
|
|
2286
2350
|
if (!opts.watch) return;
|
|
2287
2351
|
for (; ; ) {
|
|
@@ -2295,6 +2359,7 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
|
|
|
2295
2359
|
`
|
|
2296
2360
|
${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
|
|
2297
2361
|
);
|
|
2362
|
+
printEvalRunAttribution(run);
|
|
2298
2363
|
summariseResults(results, cases);
|
|
2299
2364
|
if (run.status !== "completed" || run.passed_cases < run.total_cases) {
|
|
2300
2365
|
process.exitCode = 1;
|
|
@@ -2316,6 +2381,7 @@ evalCmd.command("results <runId>").description("Show a run's results").option("-
|
|
|
2316
2381
|
console.log(
|
|
2317
2382
|
`${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
|
|
2318
2383
|
);
|
|
2384
|
+
printEvalRunAttribution(run);
|
|
2319
2385
|
summariseResults(results, cases);
|
|
2320
2386
|
} catch (e) {
|
|
2321
2387
|
fail(e);
|
|
@@ -2326,19 +2392,54 @@ evalCmd.command("runs").description("List recent runs").option("-s, --suite <slu
|
|
|
2326
2392
|
const { runs } = await new ErdoClient().listEvalRuns(opts.suite, opts.limit);
|
|
2327
2393
|
for (const r of runs) {
|
|
2328
2394
|
console.log(
|
|
2329
|
-
`${r.id} ${r.status} ${r.passed_cases}/${r.total_cases} avg ${r.avg_score.toFixed(2)}`
|
|
2395
|
+
`${r.id} ${r.status} ${r.passed_cases}/${r.total_cases} avg ${r.avg_score.toFixed(2)} ${compactEvalRunAttribution(r)}`
|
|
2330
2396
|
);
|
|
2331
2397
|
}
|
|
2332
2398
|
} catch (e) {
|
|
2333
2399
|
fail(e);
|
|
2334
2400
|
}
|
|
2335
2401
|
});
|
|
2402
|
+
var evalCanaryCmd = evalCmd.command("canary").description("Show or configure the current organization's deployed-experience widget canary (staff only)");
|
|
2403
|
+
evalCanaryCmd.command("show").description("Show the widget designated as this organization's eval canary").action(async () => {
|
|
2404
|
+
try {
|
|
2405
|
+
print(await new ErdoClient().getEvalWidgetCanary());
|
|
2406
|
+
} catch (e) {
|
|
2407
|
+
fail(e);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
evalCanaryCmd.command("set <widget>").description("Designate a widget by unique name or public key in the pinned organization").action(async (widget) => {
|
|
2411
|
+
try {
|
|
2412
|
+
if (!process.env.ERDO_ORG) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
"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>"
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
print(await new ErdoClient().configureEvalWidgetCanary(widget));
|
|
2418
|
+
} catch (e) {
|
|
2419
|
+
fail(e);
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
evalCanaryCmd.command("clear").description("Clear the pinned organization's eval widget canary").action(async () => {
|
|
2423
|
+
try {
|
|
2424
|
+
if (!process.env.ERDO_ORG) {
|
|
2425
|
+
throw new Error(
|
|
2426
|
+
"Refusing to clear an eval canary without a pinned org. Re-run as: erdo --org <id|slug> eval canary clear"
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
print(await new ErdoClient().configureEvalWidgetCanary(""));
|
|
2430
|
+
} catch (e) {
|
|
2431
|
+
fail(e);
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2336
2434
|
var caseCmd = evalCmd.command("case").description("Manage cases in a suite");
|
|
2337
2435
|
caseCmd.command("add <slug>").description("Add a case to a suite").requiredOption("--name <name>", "case name").requiredOption("--input <brief>", "the brief/prompt sent to the agent (the evaluated turn)").option(
|
|
2338
2436
|
"--setup <msg>",
|
|
2339
2437
|
"a turn to run BEFORE input, same thread/agent (repeatable; e.g. 'create a voice widget for X')",
|
|
2340
2438
|
collect,
|
|
2341
2439
|
[]
|
|
2440
|
+
).option(
|
|
2441
|
+
"--target-type <type>",
|
|
2442
|
+
"closed deployed target: live_page_fleet, widget_fleet_live, widget_text_canary, widget_voice_canary, or widget_video_canary"
|
|
2342
2443
|
).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(
|
|
2343
2444
|
async (slug, opts) => {
|
|
2344
2445
|
try {
|
|
@@ -2346,6 +2447,7 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
|
|
|
2346
2447
|
name: opts.name,
|
|
2347
2448
|
input: opts.input,
|
|
2348
2449
|
setup_messages: opts.setup,
|
|
2450
|
+
target_type: opts.targetType,
|
|
2349
2451
|
...caseScoringPayload(opts),
|
|
2350
2452
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
|
|
2351
2453
|
});
|
|
@@ -2355,13 +2457,19 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
|
|
|
2355
2457
|
}
|
|
2356
2458
|
}
|
|
2357
2459
|
);
|
|
2358
|
-
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
|
|
2460
|
+
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(
|
|
2461
|
+
"--target-type <type>",
|
|
2462
|
+
"replacement deployed target: live_page_fleet, widget_fleet_live, widget_text_canary, widget_voice_canary, or widget_video_canary (omit to preserve)"
|
|
2463
|
+
).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(
|
|
2359
2464
|
async (slug, caseName, opts) => {
|
|
2360
2465
|
try {
|
|
2361
2466
|
const res = await new ErdoClient().updateEvalCase(slug, caseName, {
|
|
2362
2467
|
name: caseName,
|
|
2363
2468
|
input: opts.input,
|
|
2364
2469
|
setup_messages: opts.setup,
|
|
2470
|
+
target_type: opts.targetType,
|
|
2471
|
+
clear_setup: !!opts.clearSetup,
|
|
2472
|
+
clear_target: !!opts.clearTarget,
|
|
2365
2473
|
...caseScoringPayload(opts),
|
|
2366
2474
|
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
|
|
2367
2475
|
});
|
|
@@ -3707,7 +3815,7 @@ integrationsCmd.command("apps [query]").description("Search connectable apps (na
|
|
|
3707
3815
|
fail(e);
|
|
3708
3816
|
}
|
|
3709
3817
|
});
|
|
3710
|
-
integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass its API key or other credentials with -c, or omit them to get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field, e.g. -c api_key=\u2026 (repeatable); rejected by OAuth apps, which have no credential to pass", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").action(async (app, opts) => {
|
|
3818
|
+
integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass its API key or other credentials with -c, or omit them to get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field, e.g. -c api_key=\u2026 (repeatable); rejected by OAuth apps, which have no credential to pass", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").option("--rotate", "replace the credentials of the app's existing connection instead of adding a second one \u2014 for a key that was rotated at the provider; requires -c").action(async (app, opts) => {
|
|
3711
3819
|
try {
|
|
3712
3820
|
let credentials;
|
|
3713
3821
|
if (opts.credential.length) {
|
|
@@ -3718,7 +3826,12 @@ integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass
|
|
|
3718
3826
|
credentials[c.slice(0, eq)] = c.slice(eq + 1);
|
|
3719
3827
|
}
|
|
3720
3828
|
}
|
|
3721
|
-
const res = await new ErdoClient().connectIntegration({
|
|
3829
|
+
const res = await new ErdoClient().connectIntegration({
|
|
3830
|
+
app,
|
|
3831
|
+
name: opts.name,
|
|
3832
|
+
credentials,
|
|
3833
|
+
rotate_credentials: opts.rotate || void 0
|
|
3834
|
+
});
|
|
3722
3835
|
print(res);
|
|
3723
3836
|
const notes = [];
|
|
3724
3837
|
const org2 = formatOrg(res.organization_name, res.organization_slug);
|