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.
- package/README.md +2 -2
- package/dist/cli.js +51 -22
- 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
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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/
|
|
7371
|
-
and
|
|
7372
|
-
|
|
7373
|
-
|
|
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.
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 (
|
|
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;
|