@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.
- package/dist/index.js +69 -21
- 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
|
|
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
|
|
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(
|
|
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(
|
|
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);
|
|
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
|
|
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 =
|
|
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:
|
|
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 ?
|
|
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.
|
|
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"
|