@erdoai/cli 0.55.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +113 -11
  2. 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
- { concurrency: concurrency ?? 0 }
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();
@@ -1384,6 +1398,20 @@ function summariseResults(results, cases = []) {
1384
1398
  }
1385
1399
  }
1386
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
+ }
1387
1415
  var program = new Command();
1388
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)");
1389
1417
  program.hook("preAction", (thisCommand) => {
@@ -2228,18 +2256,41 @@ function caseScoringPayload(opts) {
2228
2256
  }
2229
2257
  return out;
2230
2258
  }
2231
- 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-5", (v) => parseFloat(v)).option("--evaluate-artifact <bool>", "judge the rendered page (true/false)").option("--cron <bool>", "include in the daily cron (true/false)").action(
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(
2232
2284
  async (slug, opts) => {
2233
2285
  try {
2234
- const parseBool = (v) => v === void 0 ? void 0 : v === "true" || v === "1" || v === "yes";
2235
2286
  const res = await new ErdoClient().updateEvalSuite(slug, {
2236
2287
  name: opts.name,
2237
2288
  description: opts.description,
2238
2289
  agent_key: opts.agent,
2239
2290
  judge_model: opts.judge,
2240
2291
  pass_threshold: opts.threshold,
2241
- evaluate_artifact: parseBool(opts.evaluateArtifact),
2242
- cron_enabled: parseBool(opts.cron)
2292
+ evaluate_artifact: opts.evaluateArtifact === void 0 ? void 0 : parseStrictBoolean(opts.evaluateArtifact),
2293
+ cron_enabled: opts.cron === void 0 ? void 0 : parseStrictBoolean(opts.cron)
2243
2294
  });
2244
2295
  print(res);
2245
2296
  } catch (e) {
@@ -2247,7 +2298,7 @@ evalCmd.command("update <slug>").description("Update a suite's settings (only th
2247
2298
  }
2248
2299
  }
2249
2300
  );
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-5", (v) => parseFloat(v)).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) => {
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) => {
2251
2302
  acc.push(v);
2252
2303
  return acc;
2253
2304
  }, []).action(
@@ -2271,7 +2322,7 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
2271
2322
  }
2272
2323
  }
2273
2324
  );
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) => {
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) => {
2275
2326
  try {
2276
2327
  const api = new ErdoClient();
2277
2328
  const { suite } = await api.getEvalSuite(slug);
@@ -2287,7 +2338,14 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
2287
2338
  );
2288
2339
  process.exit(1);
2289
2340
  }
2290
- const { run_id } = await api.runEvalSuite(slug, opts.concurrency);
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
+ );
2291
2349
  console.log(`run_id: ${run_id}`);
2292
2350
  if (!opts.watch) return;
2293
2351
  for (; ; ) {
@@ -2301,6 +2359,7 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
2301
2359
  `
2302
2360
  ${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
2303
2361
  );
2362
+ printEvalRunAttribution(run);
2304
2363
  summariseResults(results, cases);
2305
2364
  if (run.status !== "completed" || run.passed_cases < run.total_cases) {
2306
2365
  process.exitCode = 1;
@@ -2322,6 +2381,7 @@ evalCmd.command("results <runId>").description("Show a run's results").option("-
2322
2381
  console.log(
2323
2382
  `${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_score.toFixed(2)}`
2324
2383
  );
2384
+ printEvalRunAttribution(run);
2325
2385
  summariseResults(results, cases);
2326
2386
  } catch (e) {
2327
2387
  fail(e);
@@ -2332,9 +2392,41 @@ evalCmd.command("runs").description("List recent runs").option("-s, --suite <slu
2332
2392
  const { runs } = await new ErdoClient().listEvalRuns(opts.suite, opts.limit);
2333
2393
  for (const r of runs) {
2334
2394
  console.log(
2335
- `${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)}`
2396
+ );
2397
+ }
2398
+ } catch (e) {
2399
+ fail(e);
2400
+ }
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>"
2336
2415
  );
2337
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(""));
2338
2430
  } catch (e) {
2339
2431
  fail(e);
2340
2432
  }
@@ -2345,6 +2437,9 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
2345
2437
  "a turn to run BEFORE input, same thread/agent (repeatable; e.g. 'create a voice widget for X')",
2346
2438
  collect,
2347
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"
2348
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(
2349
2444
  async (slug, opts) => {
2350
2445
  try {
@@ -2352,6 +2447,7 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
2352
2447
  name: opts.name,
2353
2448
  input: opts.input,
2354
2449
  setup_messages: opts.setup,
2450
+ target_type: opts.targetType,
2355
2451
  ...caseScoringPayload(opts),
2356
2452
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
2357
2453
  });
@@ -2361,13 +2457,19 @@ caseCmd.command("add <slug>").description("Add a case to a suite").requiredOptio
2361
2457
  }
2362
2458
  }
2363
2459
  );
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 for none)", collect, []).option("--rubric <json>", "replacement LLM rubric JSON").option("--evaluator <json>", "evaluator JSON {type,weight,...} (repeatable)", collect, []).option("--tags <csv>", "comma-separated tags").action(
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(
2365
2464
  async (slug, caseName, opts) => {
2366
2465
  try {
2367
2466
  const res = await new ErdoClient().updateEvalCase(slug, caseName, {
2368
2467
  name: caseName,
2369
2468
  input: opts.input,
2370
2469
  setup_messages: opts.setup,
2470
+ target_type: opts.targetType,
2471
+ clear_setup: !!opts.clearSetup,
2472
+ clear_target: !!opts.clearTarget,
2371
2473
  ...caseScoringPayload(opts),
2372
2474
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : []
2373
2475
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.55.1",
3
+ "version": "0.55.3",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {