@erdoai/cli 0.63.0 → 0.64.1

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 +69 -21
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync3 } from "fs";
4
+ import { readFileSync as readFileSync4 } from "fs";
5
5
  import { basename } from "path";
6
6
  import { select } from "@inquirer/prompts";
7
7
  import { Command } from "commander";
@@ -418,7 +418,7 @@ var ErdoClient = class {
418
418
  `/v1/evals/suites/${encodeURIComponent(slug)}/cases/${encodeURIComponent(caseName)}`
419
419
  );
420
420
  }
421
- runEvalSuite(slug, concurrency, commitSha, frontendCommitSha, backendCommitSha, source) {
421
+ runEvalSuite(slug, concurrency, commitSha, frontendCommitSha, backendCommitSha, source, modelOverride) {
422
422
  return this.request(
423
423
  "POST",
424
424
  `/v1/evals/suites/${encodeURIComponent(slug)}/run`,
@@ -427,10 +427,15 @@ var ErdoClient = class {
427
427
  commit_sha: commitSha,
428
428
  frontend_commit_sha: frontendCommitSha,
429
429
  backend_commit_sha: backendCommitSha,
430
- source
430
+ source,
431
+ model_override: modelOverride
431
432
  }
432
433
  );
433
434
  }
435
+ getEvalLeaderboard(slug, days) {
436
+ const q = days && days > 0 ? `?days=${days}` : "";
437
+ return this.request("GET", `/v1/evals/leaderboard/${encodeURIComponent(slug)}${q}`);
438
+ }
434
439
  getEvalRun(runID) {
435
440
  return this.request(
436
441
  "GET",
@@ -1328,9 +1333,17 @@ async function browserLogin() {
1328
1333
  return { token: data.access_token, me };
1329
1334
  }
1330
1335
 
1336
+ // src/input.ts
1337
+ import { readFileSync as readFileSync2 } from "fs";
1338
+ function readMaybeFile(value) {
1339
+ if (!value) return void 0;
1340
+ if (value.startsWith("@@")) return value.slice(1);
1341
+ return value.startsWith("@") ? readFileSync2(value.slice(1), "utf8") : value;
1342
+ }
1343
+
1331
1344
  // src/update.ts
1332
1345
  import { spawn as spawn2, spawnSync } from "child_process";
1333
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1346
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1334
1347
  import { dirname as dirname2 } from "path";
1335
1348
  var PKG = "@erdoai/cli";
1336
1349
  var MANUAL_HINT = `npm install -g ${PKG}@latest`;
@@ -1368,7 +1381,7 @@ async function fetchLatestVersion(timeoutMs) {
1368
1381
  function readVersionCache() {
1369
1382
  try {
1370
1383
  if (!existsSync2(CACHE_PATH)) return {};
1371
- const raw = JSON.parse(readFileSync2(CACHE_PATH, "utf8"));
1384
+ const raw = JSON.parse(readFileSync3(CACHE_PATH, "utf8"));
1372
1385
  return raw && typeof raw === "object" ? raw : {};
1373
1386
  } catch {
1374
1387
  return {};
@@ -1436,7 +1449,7 @@ async function update(current) {
1436
1449
  }
1437
1450
 
1438
1451
  // src/index.ts
1439
- var pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1452
+ var pkg = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8"));
1440
1453
  function fail(err) {
1441
1454
  if (err instanceof ErdoApiError) {
1442
1455
  console.error(`Error ${err.status}: ${err.message}`);
@@ -2433,7 +2446,7 @@ evalCmd.command("create <name>").description("Create a suite (in the active org)
2433
2446
  }
2434
2447
  }
2435
2448
  );
2436
- 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) => {
2449
+ 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").option("--model <model>", "pin the suite's agent to a model for this run (e.g. glm-5.3) \u2014 for model comparisons").action(async (slug, opts) => {
2437
2450
  try {
2438
2451
  const api = new ErdoClient();
2439
2452
  const { suite } = await api.getEvalSuite(slug);
@@ -2455,7 +2468,8 @@ evalCmd.command("run <slug>").description("Run a suite; --watch polls until it c
2455
2468
  opts.commitSha,
2456
2469
  opts.frontendCommitSha,
2457
2470
  opts.backendCommitSha,
2458
- opts.source
2471
+ opts.source,
2472
+ opts.model
2459
2473
  );
2460
2474
  console.log(`run_id: ${run_id}`);
2461
2475
  if (!opts.watch) return;
@@ -2481,6 +2495,44 @@ ${run.status}: ${run.passed_cases}/${run.total_cases} passed, avg ${run.avg_scor
2481
2495
  fail(e);
2482
2496
  }
2483
2497
  });
2498
+ evalCmd.command("leaderboard <slug>").description("Model-comparison table for a suite: cases as rows, models as columns").option("-d, --days <n>", "window in days (default: all time)", (v) => parseInt(v, 10)).action(async (slug, opts) => {
2499
+ try {
2500
+ const res = await new ErdoClient().getEvalLeaderboard(slug, opts.days);
2501
+ if (res.models.length === 0) {
2502
+ console.log(`No completed runs found for "${slug}"${opts.days ? ` in the last ${opts.days} days` : ""}.`);
2503
+ return;
2504
+ }
2505
+ const modelCol = (m) => m.padEnd(18);
2506
+ console.log(`
2507
+ ${"Case".padEnd(34)}${res.models.map(modelCol).join("")}`);
2508
+ console.log("-".repeat(34 + 18 * res.models.length));
2509
+ for (const row of res.rows) {
2510
+ const byModel = new Map(row.cells.map((c) => [c.model, c]));
2511
+ let line = row.case_name.slice(0, 33).padEnd(34);
2512
+ for (const m of res.models) {
2513
+ const c = byModel.get(m);
2514
+ line += c ? `${c.avg_score.toFixed(1)} (${Math.round(c.pass_rate * 100)}%)\xD7${c.n}`.padEnd(18) : "\xB7".padEnd(18);
2515
+ }
2516
+ console.log(line);
2517
+ }
2518
+ console.log(`
2519
+ ${"Summary (avg of all cases)".padEnd(34)}${res.models.map(modelCol).join("")}`);
2520
+ for (const stat of ["score", "pass", "cost", "secs"]) {
2521
+ let line = `${" " + stat}`.padEnd(34);
2522
+ for (const s of res.summary) {
2523
+ const v = stat === "score" ? s.avg_score.toFixed(2) : stat === "pass" ? `${Math.round(s.pass_rate * 100)}%` : stat === "cost" ? `$${(s.avg_cost_millicents / 1e5).toFixed(3)}/case` : `${(s.avg_duration_ms / 1e3).toFixed(0)}s`;
2524
+ line += v.padEnd(18);
2525
+ }
2526
+ console.log(line);
2527
+ }
2528
+ console.log(
2529
+ `
2530
+ Cell = avg score (pass rate) \xD7 runs. Cost is recorded judge cost per case; agent cost rides the run's tokens. "(unrec.)" = rows predating per-model capture.`
2531
+ );
2532
+ } catch (e) {
2533
+ fail(e);
2534
+ }
2535
+ });
2484
2536
  evalCmd.command("results <runId>").description("Show a run's results").option("--json", "print the full JSON").action(async (runId, opts) => {
2485
2537
  try {
2486
2538
  const data = await new ErdoClient().getEvalRun(runId);
@@ -2599,14 +2651,14 @@ caseCmd.command("rm <slug> <caseName>").description("Archive a case from a suite
2599
2651
  }
2600
2652
  });
2601
2653
  var agentCmd = program.command("agent").description("Run agents");
2602
- agentCmd.command("ask <question>").description("Ask the data-question agent (one-shot); prints the answer").option("-d, --datasets <csv>", "dataset slugs to scope to").option(
2654
+ agentCmd.command("ask <question>").description("Ask the data-question agent (one-shot); question accepts @path to a UTF-8 file").option("-d, --datasets <csv>", "dataset slugs to scope to").option(
2603
2655
  "--sync",
2604
2656
  "hold one long HTTP request open until the run finishes, instead of polling. Faster for quick questions; a proxy will time it out on any run over ~100s, losing the thread id while the run continues server-side"
2605
2657
  ).action(async (question, opts) => {
2606
2658
  try {
2607
2659
  const client = new ErdoClient();
2608
2660
  const res = await client.ask({
2609
- question,
2661
+ question: readMaybeFile(question) ?? "",
2610
2662
  dataset_slugs: opts.datasets ? opts.datasets.split(",").map((s) => s.trim()) : void 0,
2611
2663
  async: !opts.sync
2612
2664
  });
@@ -2632,9 +2684,9 @@ thread: ${res.thread_id}`);
2632
2684
  fail(e);
2633
2685
  }
2634
2686
  });
2635
- agentCmd.command("send <threadId> <message>").description("Send a message to a thread and print the agent's reply").option("-a, --agent <key>", "agent to invoke (e.g. erdo.artifact-builder)").option(
2687
+ agentCmd.command("send <threadId> <message>").description("Send a message to a thread and print the agent's reply; message accepts @path to a UTF-8 file").option("-a, --agent <key>", "agent to invoke (e.g. erdo.artifact-builder)").option(
2636
2688
  "--context <text>",
2637
- "application context for this turn; available to the agent but omitted from the visible user message"
2689
+ "application context for this turn, or @path to a UTF-8 file; omitted from the visible user message"
2638
2690
  ).option(
2639
2691
  "--sync",
2640
2692
  "hold one long HTTP request open until the run finishes, instead of polling. Faster for quick questions; a proxy will time it out on any run over ~100s while the run continues server-side"
@@ -2643,8 +2695,8 @@ agentCmd.command("send <threadId> <message>").description("Send a message to a t
2643
2695
  const client = new ErdoClient();
2644
2696
  const baselineRunID = opts.sync ? void 0 : await client.latestRunID(threadId);
2645
2697
  const res = await client.sendMessage(threadId, {
2646
- message,
2647
- context: opts.context,
2698
+ message: readMaybeFile(message) ?? "",
2699
+ context: readMaybeFile(opts.context),
2648
2700
  agent_key: opts.agent,
2649
2701
  async: !opts.sync
2650
2702
  });
@@ -3552,10 +3604,6 @@ reviewsCmd.command("decide <id>").description(
3552
3604
  }
3553
3605
  );
3554
3606
  var pagesCmd = program.command("pages").description("Create and manage pages/artifacts");
3555
- function readMaybeFile(v) {
3556
- if (!v) return void 0;
3557
- return v.startsWith("@") ? readFileSync3(v.slice(1), "utf8") : v;
3558
- }
3559
3607
  function grantList(v) {
3560
3608
  if (v === void 0) return void 0;
3561
3609
  if (v.trim() === "[]") return [];
@@ -4095,7 +4143,7 @@ datasetsCmd.command("schema <slug>").alias("describe").description(
4095
4143
  var MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
4096
4144
  datasetsCmd.command("upload <file>").description("Upload a file (CSV, Excel, JSON, ...) and create a dataset from it").option("-n, --name <name>", "display name for the dataset (defaults to the filename)").option("-d, --description <text>", "description shown to agents analyzing the dataset").action(async (file, opts) => {
4097
4145
  try {
4098
- const content = readFileSync3(file);
4146
+ const content = readFileSync4(file);
4099
4147
  if (content.byteLength > MAX_UPLOAD_BYTES) {
4100
4148
  fail(
4101
4149
  new Error(
@@ -4524,7 +4572,7 @@ knowledgeCmd.command("attach <objectId> <file>").description(
4524
4572
  try {
4525
4573
  const body = opts.url ? { filename: basename(file), source_url: opts.url } : {
4526
4574
  filename: basename(file),
4527
- content_base64: readFileSync3(file).toString("base64")
4575
+ content_base64: readFileSync4(file).toString("base64")
4528
4576
  };
4529
4577
  print(
4530
4578
  await new ErdoClient().attachKnowledgeAsset(objectId, {
@@ -4564,7 +4612,7 @@ autoCmd.command("update <id>").description("Edit an automation \u2014 rename, re
4564
4612
  fail(new Error("pass either --script-js or --script-file, not both"));
4565
4613
  return;
4566
4614
  }
4567
- const scriptJs = opts.scriptFile ? readFileSync3(opts.scriptFile, "utf8") : opts.scriptJs;
4615
+ const scriptJs = opts.scriptFile ? readFileSync4(opts.scriptFile, "utf8") : opts.scriptJs;
4568
4616
  const body = {
4569
4617
  name: opts.name,
4570
4618
  description: opts.description,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.63.0",
3
+ "version": "0.64.1",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,7 @@
35
35
  "scripts": {
36
36
  "build": "tsup src/index.ts --format esm --clean",
37
37
  "build:check": "tsc --noEmit",
38
+ "test": "node --import tsx --test src/input.test.ts",
38
39
  "dev": "tsx src/index.ts",
39
40
  "postinstall": "node scripts/postinstall.mjs",
40
41
  "prepublishOnly": "npm run build"