firegoat 0.26.0 → 0.27.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.
Files changed (2) hide show
  1. package/dist/cli.js +227 -37
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6947,7 +6947,7 @@ var require_public_api = __commonJS(function(exports) {
6947
6947
  // package.json
6948
6948
  var package_default = {
6949
6949
  name: "firegoat",
6950
- version: "0.26.0",
6950
+ version: "0.27.0",
6951
6951
  repository: {
6952
6952
  type: "git",
6953
6953
  url: "git+https://github.com/firegoat-ai/firegoat-cli.git"
@@ -7157,7 +7157,7 @@ function read(stdout, exited, timeoutMs) {
7157
7157
  reportedModel,
7158
7158
  version,
7159
7159
  ok: false,
7160
- reason: `claude result is an error (subtype=${result.subtype})${result.result ? `: ${result.result.slice(0, 300)}` : ""}`,
7160
+ reason: `claude reported an error${result.subtype === undefined || result.subtype === "success" ? "" : ` (${result.subtype})`}${result.result ? `: ${result.result.slice(0, 300)}` : ""}`,
7161
7161
  sessionId
7162
7162
  };
7163
7163
  return { ...trace, reportedModel, version, ok: true, sessionId };
@@ -7231,6 +7231,7 @@ var claude = {
7231
7231
  }),
7232
7232
  authLabel: () => process.env.ANTHROPIC_API_KEY ? "ANTHROPIC_API_KEY (claude bills the API key; unset it to use your claude login)" : "your claude login",
7233
7233
  checkLogin,
7234
+ isFinal: (line) => parse(line)?.type === "result",
7234
7235
  progress,
7235
7236
  read,
7236
7237
  sessionsDir: (configDir) => join(configDir, "projects"),
@@ -7549,6 +7550,10 @@ var codex = {
7549
7550
  }),
7550
7551
  authLabel: () => `your codex login (${join3(userCodexHome(), "auth.json")})${process.env.CODEX_API_KEY ? "; CODEX_API_KEY is set and passes through" : ""}`,
7551
7552
  checkLogin: checkLogin2,
7553
+ isFinal: (line) => {
7554
+ const type = parse2(line)?.type;
7555
+ return type === "turn.completed" || type === "turn.failed";
7556
+ },
7552
7557
  progress: progress2,
7553
7558
  read: read2,
7554
7559
  sessionsDir: (configDir) => join3(configDir, "sessions"),
@@ -7653,7 +7658,7 @@ function steps3(events) {
7653
7658
  function resultError(result) {
7654
7659
  const errors = Array.isArray(result.errors) ? result.errors.filter((x) => typeof x === "string").join("; ") : "";
7655
7660
  const said = errors || result.result || "";
7656
- return `grok result is an error (subtype=${result.subtype})${said ? `: ${said.slice(0, 300)}` : ""}`;
7661
+ return `grok reported an error${result.subtype === undefined || result.subtype === "success" ? "" : ` (${result.subtype})`}${said ? `: ${said.slice(0, 300)}` : ""}`;
7657
7662
  }
7658
7663
  function read3(stdout, exited, timeoutMs) {
7659
7664
  const events = stdout.split(`
@@ -7836,6 +7841,7 @@ var grok = {
7836
7841
  }),
7837
7842
  authLabel: () => `your grok login (${USER_AUTH})`,
7838
7843
  checkLogin: checkLogin3,
7844
+ isFinal: (line) => parse3(line)?.type === "result",
7839
7845
  progress: progress3,
7840
7846
  read: read3,
7841
7847
  sessionsDir: (configDir) => join4(configDir, "sessions"),
@@ -8516,6 +8522,26 @@ var CLAUDE_AUTH = /^CLAUDE_CODE_(OAUTH_TOKEN|OAUTH_REFRESH_TOKEN|USE_[A-Z_]+|SKI
8516
8522
  function hostEnv() {
8517
8523
  return Object.fromEntries(Object.entries(process.env).filter(([k]) => !NPM_INJECTED.test(k) && !k.startsWith("FIREGOAT_") && (!CLAUDE_SESSION.test(k) || CLAUDE_AUTH.test(k))));
8518
8524
  }
8525
+ var GRACE_MS = 5000;
8526
+ var liveGroups = new Set;
8527
+ var signalsHooked = false;
8528
+ function hookSignals() {
8529
+ if (signalsHooked)
8530
+ return;
8531
+ signalsHooked = true;
8532
+ const onSignal = (signal) => {
8533
+ for (const g of liveGroups)
8534
+ try {
8535
+ process.kill(-g, "SIGKILL");
8536
+ } catch {}
8537
+ process.kill(process.pid, signal);
8538
+ };
8539
+ for (const s of ["SIGINT", "SIGTERM", "SIGHUP"])
8540
+ process.once(s, onSignal);
8541
+ }
8542
+ function forgetGroup(pgid) {
8543
+ liveGroups.delete(pgid);
8544
+ }
8519
8545
  function processTree(pid) {
8520
8546
  const children = new Map;
8521
8547
  const ps = execFileSync("ps", ["-A", "-o", "pid=,ppid="], {
@@ -8533,7 +8559,11 @@ function processTree(pid) {
8533
8559
  tree.push(...children.get(tree[i]) ?? []);
8534
8560
  return tree;
8535
8561
  }
8536
- function killTree(pid) {
8562
+ function killTree(pid, group) {
8563
+ if (group)
8564
+ try {
8565
+ process.kill(-pid, "SIGKILL");
8566
+ } catch {}
8537
8567
  let tree = [pid];
8538
8568
  let problem;
8539
8569
  try {
@@ -8549,18 +8579,27 @@ function killTree(pid) {
8549
8579
  }
8550
8580
  function run(opts) {
8551
8581
  return new Promise((resolve) => {
8582
+ const detached = opts.detached === true;
8583
+ if (detached)
8584
+ hookSignals();
8552
8585
  const child = spawn(opts.cmd, opts.args, {
8553
8586
  cwd: opts.cwd,
8554
8587
  env: opts.env,
8555
- stdio: ["ignore", "pipe", "pipe"]
8588
+ stdio: ["ignore", "pipe", "pipe"],
8589
+ detached
8556
8590
  });
8591
+ if (child.pid !== undefined) {
8592
+ if (detached)
8593
+ liveGroups.add(child.pid);
8594
+ opts.onSpawn?.(child.pid);
8595
+ }
8557
8596
  let timedOut = false;
8558
8597
  let settled = false;
8559
8598
  const timer = setTimeout(() => {
8560
8599
  timedOut = true;
8561
8600
  if (child.pid === undefined)
8562
8601
  return;
8563
- const problem = killTree(child.pid);
8602
+ const problem = killTree(child.pid, detached);
8564
8603
  if (problem)
8565
8604
  opts.onStderr(Buffer.from(`firegoat: timed out; could not list ${opts.cmd}'s child processes (${problem}), killed only ${opts.cmd}
8566
8605
  `));
@@ -8574,6 +8613,7 @@ function run(opts) {
8574
8613
  };
8575
8614
  child.stdout.on("data", opts.onStdout);
8576
8615
  child.stderr.on("data", opts.onStderr);
8616
+ opts.finalSeen?.then(() => setTimeout(() => done({ code: null, timedOut: false, ended: true }), GRACE_MS).unref());
8577
8617
  child.on("error", (error) => done({ code: null, timedOut, error }));
8578
8618
  child.on("close", (code) => done({ code, timedOut }));
8579
8619
  child.on("exit", (code) => setTimeout(() => {
@@ -8666,41 +8706,54 @@ function gitRepo(dir) {
8666
8706
  async function runAgent(harness, opts) {
8667
8707
  let stdout = "";
8668
8708
  let pending = "";
8709
+ let group;
8710
+ let markFinal = () => {};
8711
+ const finalSeen = new Promise((r) => {
8712
+ markFinal = r;
8713
+ });
8669
8714
  const exited = await run({
8670
8715
  cmd: opts.bin,
8671
8716
  args: harness.args(opts),
8672
8717
  cwd: opts.cwd,
8673
8718
  env: opts.env,
8674
8719
  timeoutMs: opts.timeoutMs,
8720
+ detached: true,
8721
+ finalSeen,
8722
+ onSpawn: (pid) => {
8723
+ group = pid;
8724
+ },
8675
8725
  onStdout: (c) => {
8676
8726
  const text = c.toString();
8677
8727
  stdout += text;
8678
- if (!opts.onProgress)
8679
- return;
8680
8728
  pending += text;
8681
8729
  const lines = pending.split(`
8682
8730
  `);
8683
8731
  pending = lines.pop() ?? "";
8684
8732
  for (const line of lines) {
8685
- const p = harness.progress(line);
8733
+ if (harness.isFinal(line))
8734
+ markFinal();
8735
+ const p = opts.onProgress && harness.progress(line);
8686
8736
  if (p)
8687
- opts.onProgress(p);
8737
+ opts.onProgress?.(p);
8688
8738
  }
8689
8739
  },
8690
8740
  onStderr: (c) => opts.stderr.write(c)
8691
8741
  });
8692
8742
  if (exited.error)
8693
8743
  return {
8694
- ok: false,
8695
- reason: `could not start ${opts.bin}: ${exited.error.message}`,
8696
- steps: [],
8697
- final: "",
8698
- reportedModel: null,
8699
- version: null,
8700
- turns: null,
8701
- cost: null
8744
+ group,
8745
+ outcome: {
8746
+ ok: false,
8747
+ reason: `could not start ${opts.bin}: ${exited.error.message}`,
8748
+ steps: [],
8749
+ final: "",
8750
+ reportedModel: null,
8751
+ version: null,
8752
+ turns: null,
8753
+ cost: null
8754
+ }
8702
8755
  };
8703
- return harness.read(stdout, exited, opts.timeoutMs);
8756
+ return { group, outcome: harness.read(stdout, exited, opts.timeoutMs) };
8704
8757
  }
8705
8758
 
8706
8759
  // src/help.ts
@@ -8778,8 +8831,10 @@ firegoat.yaml reference:
8778
8831
  install: <sh> | [<sh>, ...] In each fresh folder, before the agent starts; a
8779
8832
  failure: that run is not graded
8780
8833
  cleanup: <sh> | [<sh>, ...] In each run's folder once the run is over: after the
8781
- checks, and also after an error or timeout. Stops
8782
- what the agent left running (servers, daemons).
8834
+ checks, and also after an error or timeout. For
8835
+ what lives outside the run (e.g. seeded data);
8836
+ processes the agent left running are stopped by
8837
+ firegoat after cleanup (see eval --help).
8783
8838
  Every step runs; a failure is a warning (run.json
8784
8839
  cleanup, <test>/<n>/cleanup.txt), never the verdict
8785
8840
  vars: {KEY: value} Defaults for {{vars.KEY}} in prompts, checks and
@@ -8891,7 +8946,6 @@ relative paths in it are from the run's folder, claude's cwd:
8891
8946
  Example:
8892
8947
  build: npm pack --pack-destination "$FIREGOAT_OUT"
8893
8948
  install: npm init -y >/dev/null && npm install "$FIREGOAT_OUT"/*.tgz
8894
- cleanup: pkill -f "$FIREGOAT_WORKDIR" || true
8895
8949
  tests:
8896
8950
  - name: first-call
8897
8951
  prompt: >-
@@ -8973,6 +9027,17 @@ top-level). cleanup: runs whenever the folder exists, also after a failed instal
8973
9027
  a claude error or a timeout; a failing cleanup step prints a warning and never
8974
9028
  changes the run's status or the exit code.
8975
9029
 
9030
+ The agent's run ends with its final result (claude's and grok's result line,
9031
+ codex's turn.completed or turn.failed): a harness that hasn't exited 5s later
9032
+ (a server it started in the background keeps it alive) is left running, so the
9033
+ checks can still reach that server. After cleanup, firegoat stops whatever is
9034
+ still running from the run (SIGTERM, then SIGKILL after 2s): the harness's
9035
+ process group, every process below it, and every process whose working
9036
+ directory is inside the run's folder (lsof on macOS, /proc on Linux). They are
9037
+ listed in run.json leftover, with one warning per run; never the verdict. Not
9038
+ found: a daemon that left the group and changed its working directory out of
9039
+ the run's folder.
9040
+
8976
9041
  claude runs on its own login (or ANTHROPIC_API_KEY if set, as claude always does)
8977
9042
  with a fresh config dir, ~/.firegoat/runs/<hash>/.claude: none of your settings,
8978
9043
  CLAUDE.md, plugins, skills or MCP servers (harness_args: in firegoat.yaml adds
@@ -9087,8 +9152,9 @@ Output: .firegoat/runs/<runId>/
9087
9152
  the harness reported running, or null), harnessArgs
9088
9153
  (harness_args passed), prompt, end_goal,
9089
9154
  checks [{run, code}], cleanup [{run, code}],
9090
- and paths: dir (this folder), workdir, home (the
9091
- run's HOME; both real paths, under ~/.firegoat),
9155
+ leftover [{pid, command}] (processes stopped after
9156
+ cleanup), and paths: dir (this folder), workdir, home
9157
+ (the run's HOME; both real paths, under ~/.firegoat),
9092
9158
  transcript, session (the harness's session file), stderr,
9093
9159
  checksLog, cleanupLog; startedAt; upload ({status: "uploaded",
9094
9160
  url} | {status: "failed", error} | {status:
@@ -9153,6 +9219,121 @@ Example:
9153
9219
 
9154
9220
  Exit codes: 0 sent, 2 the service failed (or rate limit), 3 usage error.`;
9155
9221
 
9222
+ // src/leftover.ts
9223
+ import { spawn as spawn2 } from "node:child_process";
9224
+ import { readdirSync as readdirSync4, readlinkSync as readlinkSync2, realpathSync as realpathSync2 } from "node:fs";
9225
+ var TERM_WAIT_MS = 2000;
9226
+ function output(cmd, args) {
9227
+ return new Promise((resolve, reject) => {
9228
+ const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
9229
+ let out = "";
9230
+ let err = "";
9231
+ child.stdout.on("data", (c) => {
9232
+ out += c;
9233
+ });
9234
+ child.stderr.on("data", (c) => {
9235
+ err += c;
9236
+ });
9237
+ child.on("error", (e) => reject(new Error(`could not run ${cmd}: ${e.message}`)));
9238
+ child.on("close", (code) => code === 0 || out !== "" ? resolve(out) : reject(new Error(`${cmd} exited ${code}: ${err.trim().slice(0, 300)}`)));
9239
+ });
9240
+ }
9241
+ async function processes() {
9242
+ const ps = await output("ps", [
9243
+ "-A",
9244
+ "-o",
9245
+ "pid=,ppid=,pgid=,stat=,command="
9246
+ ]);
9247
+ const procs = [];
9248
+ for (const line of ps.split(`
9249
+ `)) {
9250
+ const m = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*(.*)$/.exec(line);
9251
+ if (!m || m[4]?.startsWith("Z"))
9252
+ continue;
9253
+ procs.push({
9254
+ pid: Number(m[1]),
9255
+ ppid: Number(m[2]),
9256
+ pgid: Number(m[3]),
9257
+ command: m[5] ?? ""
9258
+ });
9259
+ }
9260
+ return procs;
9261
+ }
9262
+ async function cwdInside(dir) {
9263
+ const inside = (p) => p === dir || p.startsWith(`${dir}/`);
9264
+ if (process.platform === "linux") {
9265
+ const pids = [];
9266
+ for (const name of readdirSync4("/proc")) {
9267
+ if (!/^\d+$/.test(name))
9268
+ continue;
9269
+ try {
9270
+ if (inside(readlinkSync2(`/proc/${name}/cwd`)))
9271
+ pids.push(Number(name));
9272
+ } catch {}
9273
+ }
9274
+ return pids;
9275
+ }
9276
+ if (process.platform === "darwin") {
9277
+ const pids = [];
9278
+ let pid = 0;
9279
+ for (const line of (await output("lsof", ["-d", "cwd", "-Fpn", "-w"])).split(`
9280
+ `)) {
9281
+ if (line.startsWith("p"))
9282
+ pid = Number(line.slice(1));
9283
+ else if (line.startsWith("n") && inside(line.slice(1)))
9284
+ pids.push(pid);
9285
+ }
9286
+ return pids;
9287
+ }
9288
+ throw new Error(`finding processes by cwd is not supported on ${process.platform}`);
9289
+ }
9290
+ function alive(pid) {
9291
+ try {
9292
+ process.kill(pid, 0);
9293
+ return true;
9294
+ } catch {
9295
+ return false;
9296
+ }
9297
+ }
9298
+ function signal(pids, group, sig) {
9299
+ for (const p of group === undefined ? pids : [...pids, -group])
9300
+ try {
9301
+ process.kill(p, sig);
9302
+ } catch {}
9303
+ }
9304
+ async function stopLeftovers(opts) {
9305
+ const workdir = realpathSync2(opts.workdir);
9306
+ const [procs, inWorkdir] = await Promise.all([
9307
+ processes(),
9308
+ cwdInside(workdir)
9309
+ ]);
9310
+ const found = new Set(inWorkdir);
9311
+ if (opts.group !== undefined) {
9312
+ for (const p of procs)
9313
+ if (p.pgid === opts.group)
9314
+ found.add(p.pid);
9315
+ }
9316
+ for (let grew = true;grew; ) {
9317
+ grew = false;
9318
+ for (const p of procs)
9319
+ if (found.has(p.ppid) && !found.has(p.pid)) {
9320
+ found.add(p.pid);
9321
+ grew = true;
9322
+ }
9323
+ }
9324
+ found.delete(process.pid);
9325
+ const leftover = procs.filter((p) => found.has(p.pid)).map((p) => ({ pid: p.pid, command: printable(p.command, 200) }));
9326
+ const pids = leftover.map((l) => l.pid);
9327
+ signal(pids, opts.group, "SIGTERM");
9328
+ const deadline = Date.now() + TERM_WAIT_MS;
9329
+ while (pids.some(alive) && Date.now() < deadline)
9330
+ await new Promise((r) => setTimeout(r, 100));
9331
+ signal(pids.filter(alive), opts.group, "SIGKILL");
9332
+ if (opts.group !== undefined)
9333
+ forgetGroup(opts.group);
9334
+ return leftover;
9335
+ }
9336
+
9156
9337
  // src/steps.ts
9157
9338
  import { createWriteStream } from "node:fs";
9158
9339
  var STEP_TIMEOUT_MS = 600000;
@@ -9287,7 +9468,7 @@ async function sendTelemetry(event, exitCode, startedAt) {
9287
9468
  }
9288
9469
 
9289
9470
  // src/transcript.ts
9290
- import { readdirSync as readdirSync4, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
9471
+ import { readdirSync as readdirSync5, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
9291
9472
  import { join as join8, relative } from "node:path";
9292
9473
  var OUTPUT_LINES = 15;
9293
9474
  var OUTPUT_CHARS = 1500;
@@ -9337,7 +9518,7 @@ function describeToolCall(step, c) {
9337
9518
  function workdirFiles(dir) {
9338
9519
  const out = [];
9339
9520
  const walk = (d) => {
9340
- for (const name of readdirSync4(d)) {
9521
+ for (const name of readdirSync5(d)) {
9341
9522
  if (SKIP_DIRS.has(name))
9342
9523
  continue;
9343
9524
  const p = join8(d, name);
@@ -9420,7 +9601,7 @@ function runTag(harness, model) {
9420
9601
  return parts.length > 0 ? ` [${parts.join(" ")}]` : "";
9421
9602
  }
9422
9603
  function notGradedWhy(reason) {
9423
- const said = /claude result is an error \(subtype=[^)]*\): (.*)$/s.exec(reason);
9604
+ const said = /claude reported an error(?: \([^)]*\))?: (.*)$/s.exec(reason);
9424
9605
  if (said)
9425
9606
  return `claude said: "${said[1]}"`;
9426
9607
  return reason.replace(/^not graded: /, "");
@@ -10077,6 +10258,7 @@ async function runOne(opts) {
10077
10258
  checksLog: join10(dir, "checks.txt"),
10078
10259
  cleanup: [],
10079
10260
  cleanupLog: join10(dir, "cleanup.txt"),
10261
+ leftover: [],
10080
10262
  num_turns: null,
10081
10263
  cost: null,
10082
10264
  duration_ms: 0,
@@ -10094,7 +10276,8 @@ async function runOne(opts) {
10094
10276
  FIREGOAT_REPO: opts.repo
10095
10277
  };
10096
10278
  const steps = renderRunSteps(test, opts, opts.vars, fill);
10097
- const [status, reason] = await attempt(opts, r, steps, env, stepEnv, envFile);
10279
+ const agent = {};
10280
+ const [status, reason] = await attempt(opts, r, steps, env, stepEnv, envFile, agent);
10098
10281
  if (steps.cleanup.length > 0)
10099
10282
  r.cleanup = await runAllSteps({
10100
10283
  commands: steps.cleanup,
@@ -10102,6 +10285,7 @@ async function runOne(opts) {
10102
10285
  env: stepEnv,
10103
10286
  logPath: r.cleanupLog
10104
10287
  });
10288
+ r.leftover = await stopLeftovers({ group: agent.group, workdir });
10105
10289
  r.status = status;
10106
10290
  r.reason = reason;
10107
10291
  r.duration_ms = Date.now() - started;
@@ -10111,7 +10295,7 @@ async function runOne(opts) {
10111
10295
  function withoutOldPwd(env) {
10112
10296
  return Object.fromEntries(Object.entries(env).filter(([k]) => k !== "OLDPWD"));
10113
10297
  }
10114
- async function attempt(opts, r, steps, env, stepEnv, homeEnvFile) {
10298
+ async function attempt(opts, r, steps, env, stepEnv, homeEnvFile, agent) {
10115
10299
  const { test, n } = opts;
10116
10300
  const { dir, workdir } = r;
10117
10301
  if (steps.install.length > 0) {
@@ -10127,7 +10311,7 @@ async function attempt(opts, r, steps, env, stepEnv, homeEnvFile) {
10127
10311
  }
10128
10312
  const stderr = createWriteStream2(r.stderr);
10129
10313
  const { harness } = opts;
10130
- const outcome = await runAgent(harness, {
10314
+ const { outcome, group } = await runAgent(harness, {
10131
10315
  bin: opts.bin,
10132
10316
  prompt: r.prompt,
10133
10317
  cwd: workdir,
@@ -10150,6 +10334,7 @@ async function attempt(opts, r, steps, env, stepEnv, homeEnvFile) {
10150
10334
  extraArgs: r.harnessArgs,
10151
10335
  onProgress: opts.onProgress
10152
10336
  });
10337
+ agent.group = group;
10153
10338
  if (outcome.version !== null)
10154
10339
  evalStats({ claudeVersion: claudeVersion(outcome.version) });
10155
10340
  await new Promise((res) => stderr.end(res));
@@ -10538,6 +10723,9 @@ async function evalCommand(args, root) {
10538
10723
  for (const c of r.cleanup)
10539
10724
  if (c.code !== 0)
10540
10725
  console.error(`firegoat eval: Warning: ${r.test}#${r.n} cleanup failed (${c.error ?? `exit ${c.code}`}): ${c.run} (log: ${r.cleanupLog})`);
10726
+ for (const r of results)
10727
+ if (r.leftover.length > 0)
10728
+ console.error(`firegoat eval: Warning: ${r.test}#${r.n} left ${r.leftover.length} process${r.leftover.length === 1 ? "" : "es"} running, stopped after cleanup: ${r.leftover.map((l) => `${l.pid} ${l.command}`).join("; ")} (run.json leftover)`);
10541
10729
  if (uploadsFailed > 0)
10542
10730
  console.error(`firegoat eval: ${uploadsFailed} upload${uploadsFailed === 1 ? "" : "s"} failed (each run's upload.error in summary.json)`);
10543
10731
  if (opts.json)
@@ -10822,12 +11010,12 @@ firegoat ${package_default.version}` : `firegoat ${package_default.version}`,
10822
11010
  }
10823
11011
 
10824
11012
  // src/init.ts
10825
- import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
11013
+ import { spawn as spawn4, spawnSync as spawnSync4 } from "node:child_process";
10826
11014
  import { existsSync as existsSync6, writeFileSync as writeFileSync5 } from "node:fs";
10827
11015
  import { basename as basename4, join as join11 } from "node:path";
10828
11016
 
10829
11017
  // src/login.ts
10830
- import { spawn as spawn2 } from "node:child_process";
11018
+ import { spawn as spawn3 } from "node:child_process";
10831
11019
  var TIMEOUT_MS3 = 30000;
10832
11020
  var MAX_EXPIRES_S = 900;
10833
11021
  var MAX_INTERVAL_S = 60;
@@ -10844,7 +11032,7 @@ function str3(o, k, step) {
10844
11032
  function openBrowser(url) {
10845
11033
  const cmd = process.platform === "darwin" ? "open" : "xdg-open";
10846
11034
  try {
10847
- const child = spawn2(cmd, [url], {
11035
+ const child = spawn3(cmd, [url], {
10848
11036
  env: hostEnv(),
10849
11037
  stdio: "ignore",
10850
11038
  detached: true
@@ -11168,10 +11356,12 @@ runs: 1
11168
11356
  # install: cp -R "$FIREGOAT_OUT/starter-app/." .
11169
11357
 
11170
11358
  # Optional: cleanup: runs in each run's folder once the run is over (after the
11171
- # checks, also after an error or timeout), e.g. to stop a server the agent left
11172
- # running. A failing cleanup step is a warning, never the verdict.
11359
+ # checks, also after an error or timeout), for what lives outside the run, e.g.
11360
+ # data install: seeded in a shared service. Servers the agent left running need
11361
+ # none: firegoat stops them after cleanup. A failing cleanup step is a warning,
11362
+ # never the verdict.
11173
11363
  # cleanup:
11174
- # - pkill -f "$FIREGOAT_WORKDIR" || true
11364
+ # - acme db drop "eval-{{run.id}}-{{run.n}}" || true
11175
11365
  #
11176
11366
  # A test can add its own install: (after this one) and cleanup: (before this one).
11177
11367
 
@@ -11273,7 +11463,7 @@ class Timeline {
11273
11463
  }
11274
11464
  function runQuiet(cmd, args, opts) {
11275
11465
  return new Promise((resolve) => {
11276
- const child = spawn3(cmd, args, {
11466
+ const child = spawn4(cmd, args, {
11277
11467
  ...opts,
11278
11468
  stdio: ["ignore", "pipe", "pipe"]
11279
11469
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firegoat",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/firegoat-ai/firegoat-cli.git"