agent-trials 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prince Jain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # agent-trials
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Interview your coding agent before you ship with it.
4
+
5
+ `agent-trials` runs the same task suite through every coding agent CLI you have installed and prints a scoreboard: who passed, how long they took, what they spent. Every task runs in a throwaway copy of your repo, so agents can do their worst and your working tree stays clean.
6
+
7
+ ![demo](demo.png)
8
+
9
+ ## why
10
+
11
+ Public benchmarks score agents on tasks someone else picked. Your repo is not that benchmark. The question that matters before you let an agent loose on a codebase: does it fix YOUR bugs, in YOUR stack, at a sane price? Write the tasks once, run every candidate, keep the receipts.
12
+
13
+ ## install
14
+
15
+ ```sh
16
+ npx agent-trials init # writes trials.json in your repo
17
+ ```
18
+
19
+ Works straight from GitHub too: `npx github:princejain756/agent-trials`.
20
+
21
+ ## use
22
+
23
+ ```sh
24
+ agent-trials init # starter suite
25
+ agent-trials doctor # which agent CLIs are installed
26
+ agent-trials run # run the suite, print the board, write TRIALS.md
27
+ ```
28
+
29
+ ## trials.json
30
+
31
+ ```json
32
+ {
33
+ "verify": "npm test",
34
+ "timeoutSec": 600,
35
+ "tasks": [
36
+ {
37
+ "id": "fix-cache",
38
+ "prompt": "The cache in src/cache.js never expires entries. Add a ttl option. Keep the API.",
39
+ "verify": "node checks/cache-ttl.mjs"
40
+ }
41
+ ],
42
+ "agents": {
43
+ "sonnet": { "run": ["claude", "-p", "{prompt}", "--dangerously-skip-permissions", "--model", "sonnet"] }
44
+ }
45
+ }
46
+ ```
47
+
48
+ Each cell (agent x task) works like this:
49
+
50
+ 1. your repo, minus `node_modules` and `.git`, is copied to a temp sandbox
51
+ 2. optional `setup` command runs once (`npm ci` and friends)
52
+ 3. the agent runs headless on the task prompt, permission checks off, timeout-guarded
53
+ 4. your `verify` command runs in the sandbox; exit 0 means pass
54
+
55
+ Builtin agents: `claude`, `codex`, `gemini`. Custom agents are one argv array away: anything installed, any flags, `{prompt}` gets replaced with the task prompt.
56
+
57
+ ## a real run
58
+
59
+ `examples/tinylib` is a 3-function library with planted bugs. Same 3 tasks, 3 model variants of the same agent CLI (`claude --model`):
60
+
61
+ ```
62
+ agent-trials · 3 tasks · 3 agents
63
+
64
+ opus sonnet haiku
65
+ fix-clamp pass 141.3s pass 44.2s pass 60.9s
66
+ divide-guard fail 96.1s pass 72.1s pass 118.6s
67
+ slug-test pass 253.4s fail 124.4s pass 50s
68
+
69
+ score 2/3 2/3 3/3
70
+ median 141.3s 72.1s 60.9s
71
+ cost $1.11 $1.10 $2.06
72
+
73
+ winner: haiku (3/3, median 60.9s)
74
+ ```
75
+
76
+ What the board caught:
77
+
78
+ - haiku, the cheap one, went 3/3 with the fastest median. It also spent the most: $2.06, because it ran more turns. Pass rate and price do not move together.
79
+ - an earlier run caught the agent asking a clarifying question ("should -0 count as zero?") into an empty terminal, then exiting 0 having changed nothing. Exit code means nothing; your verify command is the ground truth.
80
+ - sonnet lost slug-test outright. The task was write a test file that runs clean under plain node; it didn't.
81
+
82
+ Full results with per-cell costs: [LEADERBOARD.md](LEADERBOARD.md)
83
+
84
+ ## limits, honestly
85
+
86
+ - pass/fail is your verify command's exit code. The tool cannot judge taste, only outcomes.
87
+ - runs spend your agent quota and your auth; costs are reported when the agent's JSON output includes them (claude does)
88
+ - agent CLIs must be installed and logged in
89
+ - macOS/Linux; the sandbox copy and verify assume `sh`. Windows untested
90
+ - scoring is binary per task. No partial credit for "almost fixed it"
91
+
92
+ ## flags
93
+
94
+ | flag | default | what it does |
95
+ |---|---|---|
96
+ | `--suite <file>` | `trials.json` | suite file to run |
97
+ | `--agents a,b` | all known | restrict the agent list |
98
+ | `--root <dir>` | `.` | the repo being tested |
99
+ | `--jobs <n>` | `2` | parallel agent x task cells |
100
+ | `--out <file>` | `TRIALS.md` | markdown report path, `none` skips |
101
+ | `--json <file>` | - | raw per-cell results |
102
+ | `--keep` | off | keep sandboxes under /tmp for debugging |
103
+
104
+ ## why the suite format is the point
105
+
106
+ Tasks are plain JSON and `verify` is plain shell, so a suite doubles as a smoke-test pack for humans. Commit `trials.json` next to the code it describes. When you swap agents or bump model versions, rerun and diff the board.
107
+
108
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { agentInstalled, BUILTIN_AGENTS, loadSuite, renderBoard, renderMarkdown, resolveAgents, runSuite, writeMarkdown, verdictOf, } from "./trials.js";
5
+ const VERSION = "1.0.0";
6
+ const HELP = `agent-trials ${VERSION}
7
+ Interview your coding agent before you ship with it.
8
+
9
+ usage:
10
+ agent-trials init write a starter trials.json
11
+ agent-trials doctor check which agent CLIs are installed
12
+ agent-trials run run the suite and print the scoreboard
13
+
14
+ run flags:
15
+ --suite <file> suite file (default: trials.json)
16
+ --agents a,b only these agents (default: all known + custom)
17
+ --root <dir> repo to run on (default: cwd)
18
+ --jobs <n> parallel cells (default: 2)
19
+ --out <file> markdown report path (default: TRIALS.md, "none" skips)
20
+ --json <file> also write raw results JSON
21
+ --keep keep sandbox dirs for debugging
22
+ `;
23
+ const STARTER_SUITE = {
24
+ name: "my repo trials",
25
+ setup: "",
26
+ verify: "npm test",
27
+ timeoutSec: 600,
28
+ tasks: [
29
+ {
30
+ id: "example-task",
31
+ prompt: "Describe what this repo does in one sentence, then make the smallest possible improvement to the README.",
32
+ verify: "git diff --quiet || true",
33
+ },
34
+ ],
35
+ agents: {},
36
+ };
37
+ function parseArgs(argv) {
38
+ const out = {};
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ if (a.startsWith("--")) {
42
+ const key = a.slice(2);
43
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
44
+ out[key] = argv[++i];
45
+ }
46
+ else {
47
+ out[key] = true;
48
+ }
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+ function fail(msg) {
54
+ console.error(`agent-trials: ${msg}`);
55
+ process.exit(1);
56
+ }
57
+ async function main() {
58
+ const [cmd, ...rest] = process.argv.slice(2);
59
+ const flags = parseArgs(rest);
60
+ if (!cmd || flags.help || cmd === "help") {
61
+ console.log(HELP);
62
+ return;
63
+ }
64
+ if (cmd === "--version" || cmd === "-v" || flags.version) {
65
+ console.log(VERSION);
66
+ return;
67
+ }
68
+ if (cmd === "init") {
69
+ const file = typeof flags.suite === "string" ? flags.suite : "trials.json";
70
+ if (existsSync(file) && !flags.force) {
71
+ fail(`${file} already exists (use --force to overwrite)`);
72
+ }
73
+ writeFileSync(file, JSON.stringify(STARTER_SUITE, null, 2) + "\n");
74
+ console.log(`wrote ${file}\n\nedit tasks[] with real work for your repo, then:\n agent-trials run`);
75
+ return;
76
+ }
77
+ if (cmd === "doctor") {
78
+ const suiteFile = typeof flags.suite === "string" ? flags.suite : "trials.json";
79
+ const suite = existsSync(suiteFile)
80
+ ? loadSuite(suiteFile)
81
+ : { tasks: [] };
82
+ const agents = resolveAgents(typeof flags.agents === "string" ? flags.agents.split(",") : undefined, suite);
83
+ console.log("agent-trials doctor\n");
84
+ for (const [name, spec] of Object.entries(agents)) {
85
+ const bin = spec.argv[0];
86
+ const ok = agentInstalled(bin);
87
+ const src = BUILTIN_AGENTS[name] ? "builtin" : "custom";
88
+ console.log(`${ok ? "ok " : "miss"} ${name.padEnd(10)} ${bin}${ok ? "" : " (not on PATH)"} [${src}]`);
89
+ }
90
+ console.log("\nmissing agents are skipped at run time only if you pass --agents");
91
+ return;
92
+ }
93
+ if (cmd === "run") {
94
+ const suiteFile = typeof flags.suite === "string" ? flags.suite : "trials.json";
95
+ if (!existsSync(suiteFile)) {
96
+ fail(`no ${suiteFile}. run \`agent-trials init\` or pass --suite <file>`);
97
+ }
98
+ const suite = loadSuite(suiteFile);
99
+ const root = path.resolve(typeof flags.root === "string" ? flags.root : ".");
100
+ const agentNames = typeof flags.agents === "string" ? flags.agents.split(",") : undefined;
101
+ let agents = resolveAgents(agentNames, suite);
102
+ const missing = Object.entries(agents)
103
+ .filter(([, s]) => !agentInstalled(s.argv[0]))
104
+ .map(([n]) => n);
105
+ if (missing.length && !agentNames) {
106
+ for (const n of missing)
107
+ delete agents[n];
108
+ console.log(`skipping missing agents: ${missing.join(", ")}`);
109
+ }
110
+ const names = Object.keys(agents);
111
+ if (names.length === 0)
112
+ fail("no agents available. install claude/codex/gemini or define a custom agent");
113
+ const jobs = Math.max(1, Number(flags.jobs ?? 2) || 2);
114
+ const keep = Boolean(flags.keep);
115
+ const started = Date.now();
116
+ const results = await runSuite({
117
+ root,
118
+ suite,
119
+ agents,
120
+ jobs,
121
+ keep,
122
+ onCell: (c) => {
123
+ const verdict = verdictOf(c);
124
+ console.log(` [${verdict}] ${c.agent} · ${c.task} · ${c.sec}s`);
125
+ },
126
+ });
127
+ console.log("");
128
+ const board = renderBoard(suite, results, names);
129
+ console.log(board);
130
+ console.log(`\ntotal wall time ${Math.round((Date.now() - started) / 1000)}s`);
131
+ const out = flags.out === undefined ? "TRIALS.md" : flags.out;
132
+ if (out !== "none" && typeof out === "string") {
133
+ writeMarkdown(out, renderMarkdown(suite, results, names));
134
+ console.log(`wrote ${out}`);
135
+ }
136
+ if (typeof flags.json === "string") {
137
+ writeFileSync(flags.json, JSON.stringify(results, null, 2) + "\n");
138
+ console.log(`wrote ${flags.json}`);
139
+ }
140
+ return;
141
+ }
142
+ fail(`unknown command "${cmd}". try agent-trials help`);
143
+ }
144
+ main().catch((err) => fail(err instanceof Error ? err.message : String(err)));
package/dist/trials.js ADDED
@@ -0,0 +1,266 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ export const BUILTIN_AGENTS = {
6
+ claude: {
7
+ argv: ["claude", "-p", "{prompt}", "--dangerously-skip-permissions", "--output-format", "json"],
8
+ resultJson: true,
9
+ },
10
+ codex: { argv: ["codex", "exec", "--full-auto", "{prompt}"] },
11
+ gemini: { argv: ["gemini", "-p", "{prompt}", "--yolo", "-o", "json"], resultJson: true },
12
+ };
13
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".agent-trials"]);
14
+ export function loadSuite(file) {
15
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
16
+ if (!Array.isArray(raw.tasks) || raw.tasks.length === 0) {
17
+ throw new Error(`${file}: needs a non-empty "tasks" array`);
18
+ }
19
+ for (const t of raw.tasks) {
20
+ if (!t.id || !t.prompt)
21
+ throw new Error(`${file}: every task needs "id" and "prompt"`);
22
+ if (!t.verify && !raw.verify)
23
+ throw new Error(`${file}: task "${t.id}" has no verify and suite has no default`);
24
+ }
25
+ return raw;
26
+ }
27
+ export function resolveAgents(names, suite) {
28
+ const custom = suite.agents ?? {};
29
+ const all = { ...BUILTIN_AGENTS };
30
+ for (const [name, def] of Object.entries(custom)) {
31
+ if (!Array.isArray(def.run) || def.run.length === 0) {
32
+ throw new Error(`agent "${name}": "run" must be an argv array, e.g. ["sh","./agent.sh","{prompt}"]`);
33
+ }
34
+ all[name] = { argv: def.run, resultJson: def.resultJson === true };
35
+ }
36
+ if (!names || names.length === 0)
37
+ return all;
38
+ const picked = {};
39
+ for (const n of names) {
40
+ if (!all[n])
41
+ throw new Error(`unknown agent "${n}". known: ${Object.keys(all).join(", ")}`);
42
+ picked[n] = all[n];
43
+ }
44
+ return picked;
45
+ }
46
+ export function copyWorkdir(root, dest) {
47
+ cpSync(root, dest, {
48
+ recursive: true,
49
+ filter: (src) => {
50
+ const rel = path.relative(root, src);
51
+ if (!rel)
52
+ return true;
53
+ const first = rel.split(path.sep)[0];
54
+ return !SKIP_DIRS.has(first);
55
+ },
56
+ });
57
+ }
58
+ function runCmd(argv, cwd, timeoutMs) {
59
+ return new Promise((resolve) => {
60
+ const child = spawn(argv[0], argv.slice(1), { cwd, env: process.env });
61
+ let stdout = "";
62
+ let stderr = "";
63
+ let timedOut = false;
64
+ const cap = (s, chunk) => (s.length < 512_000 ? s + chunk.toString() : s);
65
+ child.stdout.on("data", (c) => (stdout = cap(stdout, c)));
66
+ child.stderr.on("data", (c) => (stderr = cap(stderr, c)));
67
+ const killer = setTimeout(() => {
68
+ timedOut = true;
69
+ child.kill("SIGTERM");
70
+ setTimeout(() => child.kill("SIGKILL"), 5000).unref();
71
+ }, timeoutMs);
72
+ child.on("error", (err) => {
73
+ clearTimeout(killer);
74
+ resolve({ code: null, stdout, stderr: String(err), timedOut });
75
+ });
76
+ child.on("close", (code) => {
77
+ clearTimeout(killer);
78
+ resolve({ code, stdout, stderr, timedOut });
79
+ });
80
+ });
81
+ }
82
+ function parseAgentJson(stdout) {
83
+ try {
84
+ const j = JSON.parse(stdout.trim());
85
+ const cost = j.total_cost_usd ?? j.cost_usd ?? j.stats?.total_cost_usd;
86
+ return typeof cost === "number" ? { costUsd: cost } : {};
87
+ }
88
+ catch {
89
+ return {};
90
+ }
91
+ }
92
+ export async function runSuite(opts) {
93
+ const { root, suite, agents, jobs, keep, onCell } = opts;
94
+ const timeoutMs = (suite.timeoutSec ?? 600) * 1000;
95
+ const cells = [];
96
+ for (const [agent, spec] of Object.entries(agents)) {
97
+ for (const task of suite.tasks)
98
+ cells.push({ agent, spec, task });
99
+ }
100
+ const results = [];
101
+ let idx = 0;
102
+ async function worker() {
103
+ while (idx < cells.length) {
104
+ const cell = cells[idx++];
105
+ const { agent, spec, task } = cell;
106
+ const sandbox = mkdtempSync(path.join(tmpdir(), `agent-trials-${task.id}-`));
107
+ const t0 = Date.now();
108
+ const out = {
109
+ agent,
110
+ task: task.id,
111
+ pass: false,
112
+ agentExit: null,
113
+ verifyExit: null,
114
+ sec: 0,
115
+ };
116
+ try {
117
+ copyWorkdir(root, sandbox);
118
+ if (suite.setup) {
119
+ const s = await runCmd(["sh", "-c", suite.setup], sandbox, timeoutMs);
120
+ if (s.code !== 0)
121
+ throw new Error(`setup failed (${s.code}): ${s.stderr.slice(-200)}`);
122
+ }
123
+ const argv = spec.argv.map((a) => (a === "{prompt}" ? task.prompt : a));
124
+ if (!spec.argv.includes("{prompt}"))
125
+ argv.push(task.prompt);
126
+ const agentRes = await runCmd(argv, sandbox, timeoutMs);
127
+ out.agentExit = agentRes.code;
128
+ if (spec.resultJson)
129
+ Object.assign(out, parseAgentJson(agentRes.stdout));
130
+ if (agentRes.code === null) {
131
+ out.error = agentRes.timedOut ? "timeout" : `spawn failed: ${agentRes.stderr.slice(-160)}`;
132
+ }
133
+ const verifyCmd = task.verify ?? suite.verify;
134
+ const v = await runCmd(["sh", "-c", verifyCmd], sandbox, timeoutMs);
135
+ out.verifyExit = v.code;
136
+ out.pass = out.agentExit === 0 && v.code === 0;
137
+ }
138
+ catch (err) {
139
+ out.error = err instanceof Error ? err.message : String(err);
140
+ }
141
+ finally {
142
+ out.sec = Math.round((Date.now() - t0) / 100) / 10;
143
+ if (!keep)
144
+ rmSync(sandbox, { recursive: true, force: true });
145
+ results.push(out);
146
+ onCell?.(out);
147
+ }
148
+ }
149
+ }
150
+ await Promise.all(Array.from({ length: Math.max(1, jobs) }, worker));
151
+ return results;
152
+ }
153
+ export function median(nums) {
154
+ if (nums.length === 0)
155
+ return 0;
156
+ const s = [...nums].sort((a, b) => a - b);
157
+ const mid = Math.floor(s.length / 2);
158
+ return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2;
159
+ }
160
+ export function summarize(results, agentNames) {
161
+ return agentNames.map((agent) => {
162
+ const cells = results.filter((r) => r.agent === agent);
163
+ const passes = cells.filter((r) => r.pass).length;
164
+ const costs = cells.map((r) => r.costUsd).filter((c) => typeof c === "number");
165
+ return {
166
+ agent,
167
+ passes,
168
+ total: cells.length,
169
+ medianSec: median(cells.map((r) => r.sec)),
170
+ totalCost: costs.length ? costs.reduce((a, b) => a + b, 0) : undefined,
171
+ };
172
+ });
173
+ }
174
+ export function verdictOf(c) {
175
+ if (c.error || c.agentExit !== 0)
176
+ return "err";
177
+ return c.pass ? "pass" : "fail";
178
+ }
179
+ export function renderBoard(suite, results, agentNames) {
180
+ const taskIds = suite.tasks.map((t) => t.id);
181
+ const find = (a, t) => results.find((r) => r.agent === a && r.task === t);
182
+ const colW = agentNames.map((a) => Math.max(a.length, ...taskIds.map((t) => {
183
+ const c = find(a, t);
184
+ return c ? `${verdictOf(c)} ${c.sec}s`.length : 1;
185
+ })) + 2);
186
+ const taskW = Math.max(...taskIds.map((t) => t.length), "median".length) + 2;
187
+ const lines = [];
188
+ lines.push(`agent-trials · ${taskIds.length} task${taskIds.length === 1 ? "" : "s"} · ${agentNames.length} agent${agentNames.length === 1 ? "" : "s"}`);
189
+ lines.push("");
190
+ lines.push("".padEnd(taskW) + agentNames.map((a, i) => a.padStart(colW[i])).join(""));
191
+ for (const tid of taskIds) {
192
+ const row = agentNames
193
+ .map((a, i) => {
194
+ const c = find(a, tid);
195
+ const label = c ? `${verdictOf(c)} ${c.sec}s` : "-";
196
+ return label.padStart(colW[i]);
197
+ })
198
+ .join("");
199
+ lines.push(tid.padEnd(taskW) + row);
200
+ }
201
+ lines.push("");
202
+ const sums = summarize(results, agentNames);
203
+ lines.push("score".padEnd(taskW) +
204
+ sums.map((s, i) => `${s.passes}/${s.total}`.padStart(colW[i])).join(""));
205
+ lines.push("median".padEnd(taskW) +
206
+ sums.map((s, i) => `${s.medianSec}s`.padStart(colW[i])).join(""));
207
+ if (sums.some((s) => s.totalCost !== undefined)) {
208
+ lines.push("cost".padEnd(taskW) +
209
+ sums
210
+ .map((s) => (s.totalCost === undefined ? "-" : `$${s.totalCost.toFixed(2)}`))
211
+ .map((x, i) => x.padStart(colW[i]))
212
+ .join(""));
213
+ }
214
+ const best = [...sums].sort((a, b) => b.passes - a.passes || a.medianSec - b.medianSec)[0];
215
+ if (best && sums.length > 1) {
216
+ lines.push("");
217
+ lines.push(`winner: ${best.agent} (${best.passes}/${best.total}, median ${best.medianSec}s)`);
218
+ }
219
+ return lines.join("\n");
220
+ }
221
+ export function renderMarkdown(suite, results, agentNames) {
222
+ const sums = summarize(results, agentNames);
223
+ const find = (a, t) => results.find((r) => r.agent === a && r.task === t);
224
+ const head = `| task | ${agentNames.join(" | ")} |`;
225
+ const sep = `|${"---|".repeat(agentNames.length + 1)}`;
226
+ const rows = suite.tasks.map((t) => {
227
+ const cells = agentNames
228
+ .map((a) => {
229
+ const c = find(a, t.id);
230
+ if (!c)
231
+ return "-";
232
+ const verdict = verdictOf(c);
233
+ const cost = c.costUsd !== undefined ? ` · $${c.costUsd.toFixed(2)}` : "";
234
+ return `${verdict} · ${c.sec}s${cost}`;
235
+ })
236
+ .join(" | ");
237
+ return `| ${t.id} | ${cells} |`;
238
+ });
239
+ const totals = `| **score** | ${sums.map((s) => `**${s.passes}/${s.total}**`).join(" | ")} |`;
240
+ const med = `| median | ${sums.map((s) => `${s.medianSec}s`).join(" | ")} |`;
241
+ const when = new Date().toISOString().slice(0, 10);
242
+ return [
243
+ `# agent-trials results`,
244
+ ``,
245
+ `${suite.name ?? "suite"} · run ${when} · ${suite.tasks.length} tasks × ${agentNames.length} agents`,
246
+ ``,
247
+ head,
248
+ sep,
249
+ ...rows,
250
+ totals,
251
+ med,
252
+ ``,
253
+ `Generated by [agent-trials](https://github.com/princejain756/agent-trials)`,
254
+ ].join("\n");
255
+ }
256
+ export function writeMarkdown(file, content) {
257
+ writeFileSync(file, content + "\n");
258
+ }
259
+ export function agentInstalled(argv0) {
260
+ const which = process.platform === "win32" ? "where" : "which";
261
+ const res = spawnSync(which, [argv0], { stdio: "ignore" });
262
+ return res.status === 0;
263
+ }
264
+ export function suiteFileExists(file) {
265
+ return existsSync(file);
266
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "agent-trials",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Interview your coding agent before you ship with it. Run the same tasks through every agent CLI you have installed and get a scoreboard.",
5
+ "type": "module",
6
+ "bin": {
7
+ "agent-trials": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "prepare": "npm run build",
15
+ "test": "node test/run.mjs"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "keywords": [
21
+ "ai-agents",
22
+ "evals",
23
+ "benchmark",
24
+ "claude-code",
25
+ "codex",
26
+ "gemini-cli",
27
+ "coding-agents",
28
+ "llm"
29
+ ],
30
+ "author": "Prince Jain",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/princejain756/agent-trials.git"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^26.6.5",
38
+ "typescript": "^7.0.2"
39
+ }
40
+ }