firegoat 0.9.1 → 0.9.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 (3) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +51 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,7 @@ tests:
47
47
  - run: grep -q '"command":"acme deploy' "$FIREGOAT_SESSION"
48
48
  ```
49
49
 
50
- Whatever `setup` writes to `$FIREGOAT_OUT` is first on the agent's `PATH`.
50
+ Whatever `setup` writes to `$FIREGOAT_OUT` is first on the agent's `PATH`. That only helps for binaries called by bare name: if users run your CLI via `npx`/`uvx`, pack and install it like the npm example, or `npx` fetches the registry release.
51
51
 
52
52
  ### Can an agent find the right package from my docs alone?
53
53
 
@@ -85,7 +85,7 @@ npx firegoat eval --var SHA=$(git rev-parse HEAD)
85
85
  ## What eval does
86
86
 
87
87
  - claude runs on its own login (or `ANTHROPIC_API_KEY`, if set) with `CLAUDE_CONFIG_DIR=.firegoat/.claude` and `--setting-sources "" --strict-mcp-config --disable-slash-commands`: none of your settings, CLAUDE.md, plugins, skills or MCP servers. claude saves its session there, in its own format.
88
- - No sandbox: the agent's folder is inside `.firegoat/runs/`, and it can reach the rest of your machine. `.firegoat/` ignores itself in git.
88
+ - No sandbox: the agent's folder is inside `.firegoat/runs/`, and it can reach the rest of your machine. In `.firegoat/`, git ignores `runs/` and `.claude/`; keep test fixtures (scripts, sample data) in `.firegoat/fixtures/` and commit them with `firegoat.yaml`.
89
89
  - Script checks run in the agent's folder afterwards and set the exit code. `end_goal` is not graded by firegoat: it's for you, or your coding agent, to judge from `run.json`, the session and the folder.
90
90
  - Output per run, in `.firegoat/runs/<runId>/<test>/<n>/`: `run.json` (status, prompt, end_goal, check results, paths), `work/`, logs. `summary.json` covers the whole eval; `--json` prints it.
91
91
 
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.9.1",
6950
+ version: "0.9.3",
6951
6951
  repository: {
6952
6952
  type: "git",
6953
6953
  url: "git+https://github.com/firegoat-ai/firegoat-cli.git"
@@ -7167,7 +7167,7 @@ function loadConfig(dir) {
7167
7167
  import { randomBytes } from "node:crypto";
7168
7168
  import {
7169
7169
  createWriteStream as createWriteStream2,
7170
- existsSync as existsSync3,
7170
+ existsSync as existsSync2,
7171
7171
  mkdirSync as mkdirSync2,
7172
7172
  readdirSync,
7173
7173
  writeFileSync as writeFileSync2
@@ -7328,15 +7328,15 @@ function checkLogin(opts) {
7328
7328
  var EXIT = { pass: 0, fail: 1, notGraded: 2, usage: 3 };
7329
7329
 
7330
7330
  // src/firegoat-dir.ts
7331
- import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
7331
+ import { mkdirSync, writeFileSync } from "node:fs";
7332
7332
  import { join } from "node:path";
7333
+ var GITIGNORE = `runs/
7334
+ .claude/
7335
+ `;
7333
7336
  function ensureFiregoatDir(root) {
7334
7337
  const dir = join(root, ".firegoat");
7335
7338
  mkdirSync(dir, { recursive: true });
7336
- const ignore = join(dir, ".gitignore");
7337
- if (!existsSync2(ignore))
7338
- writeFileSync(ignore, `*
7339
- `);
7339
+ writeFileSync(join(dir, ".gitignore"), GITIGNORE);
7340
7340
  return dir;
7341
7341
  }
7342
7342
 
@@ -7367,10 +7367,11 @@ ${EXIT_CODES}`;
7367
7367
  }
7368
7368
  var INIT_HELP = `Usage: firegoat init [--force] [--no-skills]
7369
7369
 
7370
- In the current folder: creates .firegoat/ (ignored by git through its own .gitignore)
7371
- and a starter firegoat.yaml, installs the firegoat-onboarding and
7372
- suggesting-eval-prompts skills into .claude/skills/ (npx skills add firegoat-ai/skills;
7373
- asks first in a terminal), then prints a prompt to paste into your coding agent.
7370
+ In the current folder: creates .firegoat/ and a starter firegoat.yaml, installs the
7371
+ firegoat-onboarding and suggesting-eval-prompts skills into .claude/skills/ (npx skills
7372
+ add firegoat-ai/skills; asks first in a terminal), then prints a prompt to paste into
7373
+ your coding agent. In .firegoat/, git ignores runs/ and .claude/; keep test fixtures
7374
+ (helper scripts, sample data) in .firegoat/fixtures/ and commit them.
7374
7375
 
7375
7376
  --force replace an existing firegoat.yaml
7376
7377
  --no-skills don't install the skills
@@ -7391,7 +7392,8 @@ firegoat.yaml reference:
7391
7392
  - run: ... afterwards; exit 0 = pass; they set eval's exit code
7392
7393
  timeout: 600 optional, seconds
7393
7394
 
7394
- Unknown keys are errors. setup, workdir_setup and checks get the vars as env vars,
7395
+ Unknown keys are errors. Keep helper files out of firegoat.yaml: put them in
7396
+ .firegoat/fixtures/ and copy them in setup or workdir_setup. setup, workdir_setup and checks get the vars as env vars,
7395
7397
  plus $FIREGOAT_OUT and $FIREGOAT_WORKDIR; checks also get $FIREGOAT_SESSION (the
7396
7398
  claude session file, one JSON event per line).
7397
7399
 
@@ -7546,15 +7548,24 @@ function parseArgs(args) {
7546
7548
  }
7547
7549
  function findSession(configDir, sessionId) {
7548
7550
  const projects = join3(configDir, "projects");
7549
- if (!existsSync3(projects))
7551
+ if (!existsSync2(projects))
7550
7552
  return null;
7551
7553
  for (const d of readdirSync(projects)) {
7552
7554
  const f = join3(projects, d, `${sessionId}.jsonl`);
7553
- if (existsSync3(f))
7555
+ if (existsSync2(f))
7554
7556
  return f;
7555
7557
  }
7556
7558
  return null;
7557
7559
  }
7560
+ function usage(r) {
7561
+ const parts = [];
7562
+ if (r.cost !== null)
7563
+ parts.push(`$${r.cost.toFixed(2)}`);
7564
+ if (r.num_turns !== null)
7565
+ parts.push(`${r.num_turns} turns`);
7566
+ parts.push(`${Math.round(r.duration_ms / 1000)}s`);
7567
+ return parts.join(", ");
7568
+ }
7558
7569
  function newRunId() {
7559
7570
  const ts = new Date().toISOString().replace(/\.\d+Z$/, "Z").replace(/[-:]/g, "");
7560
7571
  return `${ts}-${randomBytes(2).toString("hex")}`;
@@ -7688,6 +7699,7 @@ async function evalCommand(args, root) {
7688
7699
  const firegoatDir = ensureFiregoatDir(root);
7689
7700
  const configDir = join3(firegoatDir, ".claude");
7690
7701
  mkdirSync2(configDir, { recursive: true });
7702
+ say(process.env.ANTHROPIC_API_KEY ? "auth: ANTHROPIC_API_KEY (claude bills the API key; unset it to use your claude login)" : "auth: your claude login");
7691
7703
  const loginProblem = checkLogin({
7692
7704
  bin: claudePath,
7693
7705
  env: claudeEnv(configDir),
@@ -7731,7 +7743,7 @@ async function evalCommand(args, root) {
7731
7743
  vars
7732
7744
  });
7733
7745
  results.push(r);
7734
- say(`${r.status.toUpperCase()} ${r.test}#${r.n} ${r.reason}`);
7746
+ say(`${r.status.toUpperCase()} ${r.test}#${r.n} ${r.reason} (${usage(r)})`);
7735
7747
  }
7736
7748
  const totals = { pass: 0, fail: 0, error: 0 };
7737
7749
  for (const r of results)
@@ -7739,8 +7751,9 @@ async function evalCommand(args, root) {
7739
7751
  const doc = { runId, runsDir: runDir, totals, runs: results };
7740
7752
  writeFileSync2(join3(runDir, "summary.json"), `${JSON.stringify(doc, null, 2)}
7741
7753
  `);
7754
+ const cost = results.reduce((sum, r) => sum + (r.cost ?? 0), 0);
7742
7755
  say(`
7743
- ${totals.pass} passed, ${totals.fail} failed, ${totals.error} not graded (${results.length} runs)`);
7756
+ ${totals.pass} passed, ${totals.fail} failed, ${totals.error} not graded (${results.length} runs, $${cost.toFixed(2)})`);
7744
7757
  say(`runs: ${runDir} (end_goal is for you to judge: see run.json per run)`);
7745
7758
  if (opts.json)
7746
7759
  console.log(JSON.stringify(doc, null, 2));
@@ -7749,7 +7762,7 @@ ${totals.pass} passed, ${totals.fail} failed, ${totals.error} not graded (${resu
7749
7762
 
7750
7763
  // src/init.ts
7751
7764
  import { spawnSync as spawnSync2 } from "node:child_process";
7752
- import { existsSync as existsSync4, writeFileSync as writeFileSync3 } from "node:fs";
7765
+ import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "node:fs";
7753
7766
  import { basename, join as join4 } from "node:path";
7754
7767
  import { createInterface } from "node:readline/promises";
7755
7768
  var SKILLS_ARGV = [
@@ -7785,8 +7798,14 @@ runs: 1
7785
7798
  # setup: uv build --wheel --out-dir "$FIREGOAT_OUT"
7786
7799
  # workdir_setup: uv venv && uv pip install "$FIREGOAT_OUT"/*.whl
7787
7800
  #
7788
- # a CLI binary:
7801
+ # a CLI binary users call by bare name (on PATH as is):
7789
7802
  # setup: go build -o "$FIREGOAT_OUT/mycli" ./cmd/mycli
7803
+ # (an npm/pypi CLI that users run via npx/uvx: pack + install it as above instead;
7804
+ # a binary on PATH does not stop "npx mycli" from fetching the registry release)
7805
+ #
7806
+ # Helper files (scripts, sample data) go in .firegoat/fixtures/ (committed), not inline here.
7807
+ # setup runs in this folder, so: setup: cp -R .firegoat/fixtures/. "$FIREGOAT_OUT"
7808
+ # then e.g. workdir_setup: cp "$FIREGOAT_OUT/wizard.mjs" .
7790
7809
 
7791
7810
  tests:
7792
7811
  - name: quickstart
@@ -7849,7 +7868,7 @@ ${INIT_HELP}`);
7849
7868
  }
7850
7869
  ensureFiregoatDir(cwd);
7851
7870
  const configPath = join4(cwd, CONFIG_FILE);
7852
- if (existsSync4(configPath) && !force)
7871
+ if (existsSync3(configPath) && !force)
7853
7872
  console.log(`kept ${CONFIG_FILE} (--force replaces it)`);
7854
7873
  else {
7855
7874
  writeFileSync3(configPath, template(basename(cwd)));
@@ -7865,18 +7884,28 @@ ${INIT_HELP}`);
7865
7884
  const r = spawnSync2("npx", ["-y", ...SKILLS_ARGV], {
7866
7885
  cwd,
7867
7886
  env,
7868
- stdio: "inherit"
7887
+ stdio: interactive ? "inherit" : "pipe",
7888
+ encoding: "utf8"
7869
7889
  });
7870
7890
  if (r.error || r.status !== 0) {
7891
+ if (!interactive)
7892
+ process.stderr.write(`${r.stdout ?? ""}${r.stderr ?? ""}`);
7871
7893
  console.error(`firegoat init: installing the skills failed (${r.error?.message ?? `exit ${r.status}`}). Run it yourself: npx ${SKILLS_ARGV.join(" ")}`);
7872
7894
  return EXIT.notGraded;
7873
7895
  }
7896
+ console.log("installed skills firegoat-onboarding, suggesting-eval-prompts in .claude/skills/");
7897
+ const ignored = spawnSync2("git", ["check-ignore", "-q", ".claude/skills"], {
7898
+ cwd
7899
+ });
7900
+ if (ignored.status === 0)
7901
+ console.error("warning: git ignores .claude/skills, so teammates and CI won't get the skills. Commit them with: git add -f .claude/skills");
7874
7902
  }
7875
- console.log(`
7903
+ console.log(interactive ? `
7876
7904
  Paste this into your coding agent:
7877
7905
 
7878
7906
  ${AGENT_PROMPT}
7879
- `);
7907
+ ` : `
7908
+ Prompt for your coding agent: ${AGENT_PROMPT}`);
7880
7909
  if (interactive && copyToClipboard(AGENT_PROMPT))
7881
7910
  console.log("\x1B[32mPrompt copied!\x1B[0m");
7882
7911
  return EXIT.pass;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firegoat",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/firegoat-ai/firegoat-cli.git"