firegoat 0.13.0 → 0.14.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/README.md +3 -1
- package/dist/cli.js +118 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ npx firegoat eval --var SHA=$(git rev-parse HEAD)
|
|
|
88
88
|
env:
|
|
89
89
|
ACME_HOME: "/tmp/acme-{{run.id}}-{{run.n}}" # sessions/config never collide across runs
|
|
90
90
|
ACME_TELEMETRY: "off"
|
|
91
|
+
UV_FIND_LINKS: "$FIREGOAT_OUT" # resolve the wheel setup built
|
|
91
92
|
tests:
|
|
92
93
|
- name: login
|
|
93
94
|
prompt: Log in to acme with the CLI and print the current user.
|
|
@@ -98,12 +99,13 @@ tests:
|
|
|
98
99
|
- run: ls "$ACME_HOME"/sessions | grep -q .
|
|
99
100
|
```
|
|
100
101
|
|
|
101
|
-
`env:` goes to the agent, and to `workdir_setup` and checks of the same run, so checks see the same values. Placeholders: `{{vars.KEY}}`, `{{run.id}}` (the eval's run id), `{{run.n}}` (1..runs), `{{
|
|
102
|
+
`env:` goes to the agent, and to `workdir_setup` and checks of the same run, so checks see the same values. Placeholders: `{{vars.KEY}}`, `{{run.id}}` (the eval's run id), `{{run.n}}` (1..runs), `{{test.name}}`, and `$FIREGOAT_OUT` / `$FIREGOAT_WORKDIR` (also `${...}`), the same dirs `setup`, `workdir_setup` and checks see. There is no other `$` expansion: `$HOME` stays the literal text `$HOME`. `setup` runs once, before any run: it gets only the top-level entries that use nothing but `{{vars.*}}`, `{{run.id}}` and `$FIREGOAT_OUT`. Values are strings (quote numbers). `PATH`, `CLAUDE_CONFIG_DIR` and `FIREGOAT_*` are firegoat's and can't be set.
|
|
102
103
|
|
|
103
104
|
## What eval does
|
|
104
105
|
|
|
105
106
|
- 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.
|
|
106
107
|
- 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`.
|
|
108
|
+
- In a linked git worktree (`git worktree add`), `.firegoat/runs/` and `.firegoat/.claude/` are in the main checkout, so runs (and their session files) survive `git worktree remove`; `firegoat.yaml`, fixtures and `setup:` stay in the worktree. Each `summary.json` records `dir` (where eval ran) and `git: {branch, commit, dirty}`, and `.firegoat/runs/index.jsonl` gets one line per eval (`runId, startedAt, dir, branch, commit, passed, failed, notGraded, cost`) to tell runs from different branches apart.
|
|
107
109
|
- The agent gets your environment plus `env:`, never `vars:`. Vars that npm/npx set for firegoat itself (`npm_config_*`, `npm_lifecycle_*`, `npm_package_*`, `npm_execpath`, `npm_node_execpath`, `npm_command`, `INIT_CWD`) are removed from every env firegoat starts (agent, setup, workdir_setup, checks): `npx -y firegoat` sets `npm_config_yes=true`, which would make the agent's `npm init` / `npx create-*` skip their prompts. Set one under `env:` to keep it.
|
|
108
110
|
- Runs (tests × `runs`) go 4 at a time, each in its own folder with its own session; `--parallel N` changes that, `--parallel 1` runs them one after another. `summary.json` lists them in `firegoat.yaml` order.
|
|
109
111
|
- 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.
|
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.
|
|
6950
|
+
version: "0.14.0",
|
|
6951
6951
|
repository: {
|
|
6952
6952
|
type: "git",
|
|
6953
6953
|
url: "git+https://github.com/firegoat-ai/firegoat-cli.git"
|
|
@@ -7160,9 +7160,11 @@ var RESERVED_ENV = new Set([
|
|
|
7160
7160
|
"CLAUDE_CONFIG_DIR",
|
|
7161
7161
|
"CLAUDE_SECURESTORAGE_CONFIG_DIR"
|
|
7162
7162
|
]);
|
|
7163
|
-
var RUN_PLACEHOLDERS = ["run.id", "run.n", "
|
|
7163
|
+
var RUN_PLACEHOLDERS = ["run.id", "run.n", "test.name"];
|
|
7164
7164
|
var EVAL_PLACEHOLDERS = ["run.id"];
|
|
7165
7165
|
var ANY_PLACEHOLDER = /\{\{\s*([^{}]*?)\s*\}\}/g;
|
|
7166
|
+
var DIR_REF = /\$(?:\{(FIREGOAT_OUT|FIREGOAT_WORKDIR)\}|(FIREGOAT_OUT|FIREGOAT_WORKDIR)(?![A-Za-z0-9_]))/g;
|
|
7167
|
+
var ENV_REF = new RegExp(`${ANY_PLACEHOLDER.source}|${DIR_REF.source}`, "g");
|
|
7166
7168
|
function parseEnv(raw, path) {
|
|
7167
7169
|
if (raw === undefined)
|
|
7168
7170
|
return {};
|
|
@@ -7187,7 +7189,8 @@ function parseEnv(raw, path) {
|
|
|
7187
7189
|
return out;
|
|
7188
7190
|
}
|
|
7189
7191
|
function isEvalLevel(value) {
|
|
7190
|
-
|
|
7192
|
+
const dirsOk = [...value.matchAll(DIR_REF)].every((m) => (m[1] ?? m[2]) === "FIREGOAT_OUT");
|
|
7193
|
+
return dirsOk && [...value.matchAll(ANY_PLACEHOLDER)].every((m) => {
|
|
7191
7194
|
const name = m[1];
|
|
7192
7195
|
return name.startsWith("vars.") || EVAL_PLACEHOLDERS.includes(name);
|
|
7193
7196
|
});
|
|
@@ -7195,8 +7198,9 @@ function isEvalLevel(value) {
|
|
|
7195
7198
|
function renderEnv(env, vars, run) {
|
|
7196
7199
|
const out = {};
|
|
7197
7200
|
for (const [k, v] of Object.entries(env))
|
|
7198
|
-
out[k] = v.replace(
|
|
7199
|
-
const
|
|
7201
|
+
out[k] = v.replace(ENV_REF, (whole, name, braced, bare) => {
|
|
7202
|
+
const key = name ?? braced ?? bare;
|
|
7203
|
+
const filled = key.startsWith("vars.") ? vars[key.slice("vars.".length)] : run[key];
|
|
7200
7204
|
if (filled === undefined)
|
|
7201
7205
|
throw new ConfigError(`env.${k}: no value for ${whole}`);
|
|
7202
7206
|
return filled;
|
|
@@ -7225,13 +7229,14 @@ function loadConfig(dir) {
|
|
|
7225
7229
|
// src/eval.ts
|
|
7226
7230
|
import { randomBytes } from "node:crypto";
|
|
7227
7231
|
import {
|
|
7232
|
+
appendFileSync,
|
|
7228
7233
|
createWriteStream as createWriteStream2,
|
|
7229
7234
|
existsSync as existsSync2,
|
|
7230
7235
|
mkdirSync as mkdirSync2,
|
|
7231
7236
|
readdirSync as readdirSync2,
|
|
7232
7237
|
writeFileSync as writeFileSync3
|
|
7233
7238
|
} from "node:fs";
|
|
7234
|
-
import { basename, delimiter as delimiter2, join as join4 } from "node:path";
|
|
7239
|
+
import { basename as basename2, delimiter as delimiter2, join as join4 } from "node:path";
|
|
7235
7240
|
|
|
7236
7241
|
// src/claude.ts
|
|
7237
7242
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -7425,6 +7430,51 @@ function ensureFiregoatDir(root) {
|
|
|
7425
7430
|
return dir;
|
|
7426
7431
|
}
|
|
7427
7432
|
|
|
7433
|
+
// src/git.ts
|
|
7434
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7435
|
+
import { basename, dirname } from "node:path";
|
|
7436
|
+
function git(dir, args) {
|
|
7437
|
+
const r = spawnSync2("git", args, { cwd: dir, encoding: "utf8" });
|
|
7438
|
+
if (r.error)
|
|
7439
|
+
throw r.error;
|
|
7440
|
+
return { code: r.status ?? -1, stdout: r.stdout.trim() };
|
|
7441
|
+
}
|
|
7442
|
+
function gitRepo(dir) {
|
|
7443
|
+
const r = spawnSync2("git", ["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"], { cwd: dir, encoding: "utf8" });
|
|
7444
|
+
if (r.error?.code === "ENOENT")
|
|
7445
|
+
return null;
|
|
7446
|
+
if (r.error)
|
|
7447
|
+
throw r.error;
|
|
7448
|
+
if (r.status !== 0) {
|
|
7449
|
+
if (/not a git repository/.test(r.stderr))
|
|
7450
|
+
return null;
|
|
7451
|
+
throw new Error(`git rev-parse in ${dir} failed: ${r.stderr.trim()}`);
|
|
7452
|
+
}
|
|
7453
|
+
const [gitDir, commonDir] = r.stdout.trim().split(`
|
|
7454
|
+
`);
|
|
7455
|
+
if (!gitDir || !commonDir)
|
|
7456
|
+
throw new Error(`git rev-parse in ${dir} printed ${r.stdout}`);
|
|
7457
|
+
let mainCheckout = null;
|
|
7458
|
+
if (gitDir !== commonDir) {
|
|
7459
|
+
if (basename(commonDir) !== ".git")
|
|
7460
|
+
throw new Error(`${dir} is a linked git worktree, but its common git dir ${commonDir} is not a checkout's .git (a bare repo?), so there is no main checkout to keep .firegoat/runs in`);
|
|
7461
|
+
mainCheckout = dirname(commonDir);
|
|
7462
|
+
}
|
|
7463
|
+
const branch = git(dir, ["symbolic-ref", "-q", "--short", "HEAD"]);
|
|
7464
|
+
const commit = git(dir, ["rev-parse", "-q", "--verify", "HEAD"]);
|
|
7465
|
+
const status = git(dir, ["status", "--porcelain"]);
|
|
7466
|
+
if (status.code !== 0)
|
|
7467
|
+
throw new Error(`git status in ${dir} failed`);
|
|
7468
|
+
return {
|
|
7469
|
+
info: {
|
|
7470
|
+
branch: branch.code === 0 ? branch.stdout : null,
|
|
7471
|
+
commit: commit.code === 0 ? commit.stdout : null,
|
|
7472
|
+
dirty: status.stdout !== ""
|
|
7473
|
+
},
|
|
7474
|
+
mainCheckout
|
|
7475
|
+
};
|
|
7476
|
+
}
|
|
7477
|
+
|
|
7428
7478
|
// src/help.ts
|
|
7429
7479
|
var EXIT_CODES = `Exit codes:
|
|
7430
7480
|
0 every run's script checks passed
|
|
@@ -7472,8 +7522,10 @@ firegoat.yaml reference:
|
|
|
7472
7522
|
env: {NAME: value} env vars for the agent, and for workdir_setup and
|
|
7473
7523
|
checks of the same run; values are strings and can
|
|
7474
7524
|
use {{vars.KEY}}, {{run.id}}, {{run.n}},
|
|
7475
|
-
{{
|
|
7476
|
-
|
|
7525
|
+
{{test.name}}, $FIREGOAT_OUT, $FIREGOAT_WORKDIR
|
|
7526
|
+
(no other $ expansion: $HOME stays literal).
|
|
7527
|
+
setup gets only the entries that use nothing but
|
|
7528
|
+
{{vars.*}}, {{run.id}} and $FIREGOAT_OUT.
|
|
7477
7529
|
PATH, CLAUDE_CONFIG_DIR and FIREGOAT_* are reserved.
|
|
7478
7530
|
tests:
|
|
7479
7531
|
- name: quickstart letters, digits, '.', '_', '-'; unique
|
|
@@ -7526,6 +7578,10 @@ with a fresh config dir, .firegoat/.claude: none of your settings, CLAUDE.md,
|
|
|
7526
7578
|
plugins, skills or MCP servers. claude saves its session there. There is no
|
|
7527
7579
|
sandbox: the agent can reach the rest of your machine, including the repo.
|
|
7528
7580
|
|
|
7581
|
+
In a linked git worktree (git worktree add), .firegoat/runs/ and .firegoat/.claude
|
|
7582
|
+
live in the main checkout instead, so runs outlive the worktree; firegoat.yaml,
|
|
7583
|
+
fixtures and setup: stay in the current folder.
|
|
7584
|
+
|
|
7529
7585
|
test.. only these tests (by name)
|
|
7530
7586
|
--runs N runs per test (overrides runs: in firegoat.yaml)
|
|
7531
7587
|
--parallel N runs at once (default 4; 1 runs them one after another)
|
|
@@ -7535,7 +7591,10 @@ sandbox: the agent can reach the rest of your machine, including the repo.
|
|
|
7535
7591
|
--claude-bin PATH claude binary (env FIREGOAT_CLAUDE_BIN; default: claude on PATH)
|
|
7536
7592
|
|
|
7537
7593
|
Output: .firegoat/runs/<runId>/
|
|
7538
|
-
summary.json every run, plus totals
|
|
7594
|
+
summary.json every run, plus totals, dir (where eval ran) and
|
|
7595
|
+
git {branch, commit, dirty} (no git outside a repo)
|
|
7596
|
+
../index.jsonl one line per eval: runId, startedAt, dir, branch,
|
|
7597
|
+
commit, passed, failed, notGraded, cost
|
|
7539
7598
|
<test>/<n>/run.json one run: status and reason (from the checks), prompt,
|
|
7540
7599
|
end_goal, checks [{run, code}], and paths: workdir,
|
|
7541
7600
|
transcript, session (claude's session file), stderr,
|
|
@@ -7692,10 +7751,14 @@ ${indent(truncate(text, OUTPUT_LINES, OUTPUT_CHARS))}` : ` → ${status}`);
|
|
|
7692
7751
|
}
|
|
7693
7752
|
|
|
7694
7753
|
// src/ui.ts
|
|
7695
|
-
import { relative as relative2 } from "node:path";
|
|
7754
|
+
import { isAbsolute, relative as relative2 } from "node:path";
|
|
7696
7755
|
var RAMP = "▁▂▃▄▅▆▇";
|
|
7697
7756
|
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
7698
7757
|
var SPIN = ["◐", "◓", "◑", "◒"];
|
|
7758
|
+
function shortPath(path) {
|
|
7759
|
+
const rel = relative2(process.cwd(), path);
|
|
7760
|
+
return rel.startsWith("..") || isAbsolute(rel) ? path : rel;
|
|
7761
|
+
}
|
|
7699
7762
|
function painter(color) {
|
|
7700
7763
|
return (code, text) => color ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
7701
7764
|
}
|
|
@@ -7891,7 +7954,7 @@ function ttyUI(out, opts) {
|
|
|
7891
7954
|
const right = goated ? `GOATED · tier ${tr}` : tr ? `tier ${tr}` : "";
|
|
7892
7955
|
out.push(` ${goated ? p(C.green, left) : left}${" ".repeat(Math.max(2, w - left.length - right.length - 3))}${goated ? p(C.ember, right) : right}`);
|
|
7893
7956
|
out.push(p(C.gray, ` total $${t.cost.toFixed(2)} · ${t.turns} turns · ${duration(finished.seconds)}`));
|
|
7894
|
-
out.push(p(C.gray, ` runs ${
|
|
7957
|
+
out.push(p(C.gray, ` runs ${shortPath(finished.runsDir)}/`));
|
|
7895
7958
|
out.push(` checks are a score, not the verdict: judge each end_goal from ${p(C.cyan, "<test>/<n>/transcript.txt")}`);
|
|
7896
7959
|
return out;
|
|
7897
7960
|
};
|
|
@@ -8061,8 +8124,9 @@ async function runOne(opts) {
|
|
|
8061
8124
|
const env = renderEnv(test.env, opts.vars, {
|
|
8062
8125
|
"run.id": opts.runId,
|
|
8063
8126
|
"run.n": String(n),
|
|
8064
|
-
"
|
|
8065
|
-
|
|
8127
|
+
"test.name": test.name,
|
|
8128
|
+
FIREGOAT_OUT: opts.out,
|
|
8129
|
+
FIREGOAT_WORKDIR: workdir
|
|
8066
8130
|
});
|
|
8067
8131
|
const stepEnv = { ...opts.stepEnv, ...env, FIREGOAT_WORKDIR: workdir };
|
|
8068
8132
|
if (opts.workdirSetup.length > 0) {
|
|
@@ -8146,7 +8210,7 @@ async function evalCommand(args, root) {
|
|
|
8146
8210
|
}
|
|
8147
8211
|
const say = opts.json ? console.error : console.log;
|
|
8148
8212
|
const stream = opts.json ? process.stderr : process.stdout;
|
|
8149
|
-
const ui = stream.isTTY ? ttyUI(stream, { project:
|
|
8213
|
+
const ui = stream.isTTY ? ttyUI(stream, { project: basename2(root), color: !process.env.NO_COLOR }) : plainUI(say);
|
|
8150
8214
|
let config;
|
|
8151
8215
|
try {
|
|
8152
8216
|
config = loadConfig(root);
|
|
@@ -8177,7 +8241,13 @@ async function evalCommand(args, root) {
|
|
|
8177
8241
|
const claudePath = resolveBin(opts.claudeBin);
|
|
8178
8242
|
if (!claudePath)
|
|
8179
8243
|
return fatal(EXIT.notGraded, `not graded: claude binary ${JSON.stringify(opts.claudeBin)} not found. Install Claude Code (https://claude.com/claude-code) or pass --claude-bin PATH / FIREGOAT_CLAUDE_BIN.`);
|
|
8180
|
-
|
|
8244
|
+
let repo;
|
|
8245
|
+
try {
|
|
8246
|
+
repo = gitRepo(root);
|
|
8247
|
+
} catch (e) {
|
|
8248
|
+
return fatal(EXIT.usage, e.message);
|
|
8249
|
+
}
|
|
8250
|
+
const firegoatDir = ensureFiregoatDir(repo?.mainCheckout ?? root);
|
|
8181
8251
|
const configDir = join4(firegoatDir, ".claude");
|
|
8182
8252
|
mkdirSync2(configDir, { recursive: true });
|
|
8183
8253
|
ui.auth(process.env.ANTHROPIC_API_KEY ? "ANTHROPIC_API_KEY (claude bills the API key; unset it to use your claude login)" : "your claude login");
|
|
@@ -8190,7 +8260,8 @@ async function evalCommand(args, root) {
|
|
|
8190
8260
|
return fatal(EXIT.notGraded, `not graded: ${loginProblem}`);
|
|
8191
8261
|
const evalStarted = Date.now();
|
|
8192
8262
|
const runId = newRunId();
|
|
8193
|
-
const
|
|
8263
|
+
const runsRoot = join4(firegoatDir, "runs");
|
|
8264
|
+
const runDir = join4(runsRoot, runId);
|
|
8194
8265
|
const out = join4(runDir, "out");
|
|
8195
8266
|
mkdirSync2(out, { recursive: true });
|
|
8196
8267
|
const stepEnv = {
|
|
@@ -8214,7 +8285,7 @@ async function evalCommand(args, root) {
|
|
|
8214
8285
|
cwd: root,
|
|
8215
8286
|
env: {
|
|
8216
8287
|
...stepEnv,
|
|
8217
|
-
...renderEnv(Object.fromEntries(Object.entries(config.env).filter(([, v]) => isEvalLevel(v))), vars, { "run.id": runId })
|
|
8288
|
+
...renderEnv(Object.fromEntries(Object.entries(config.env).filter(([, v]) => isEvalLevel(v))), vars, { "run.id": runId, FIREGOAT_OUT: out })
|
|
8218
8289
|
},
|
|
8219
8290
|
logPath: join4(runDir, "setup.txt")
|
|
8220
8291
|
});
|
|
@@ -8252,6 +8323,7 @@ async function evalCommand(args, root) {
|
|
|
8252
8323
|
claudeBin: claudePath,
|
|
8253
8324
|
configDir,
|
|
8254
8325
|
workdirSetup: config.workdirSetup,
|
|
8326
|
+
out,
|
|
8255
8327
|
stepEnv,
|
|
8256
8328
|
vars
|
|
8257
8329
|
});
|
|
@@ -8276,8 +8348,27 @@ async function evalCommand(args, root) {
|
|
|
8276
8348
|
const totals = { pass: 0, fail: 0, error: 0 };
|
|
8277
8349
|
for (const r of results)
|
|
8278
8350
|
totals[r.status]++;
|
|
8279
|
-
const doc = {
|
|
8351
|
+
const doc = {
|
|
8352
|
+
runId,
|
|
8353
|
+
runsDir: runDir,
|
|
8354
|
+
dir: root,
|
|
8355
|
+
...repo && { git: repo.info },
|
|
8356
|
+
totals,
|
|
8357
|
+
runs: results
|
|
8358
|
+
};
|
|
8280
8359
|
writeFileSync3(join4(runDir, "summary.json"), `${JSON.stringify(doc, null, 2)}
|
|
8360
|
+
`);
|
|
8361
|
+
appendFileSync(join4(runsRoot, "index.jsonl"), `${JSON.stringify({
|
|
8362
|
+
runId,
|
|
8363
|
+
startedAt: new Date(evalStarted).toISOString(),
|
|
8364
|
+
dir: root,
|
|
8365
|
+
branch: repo?.info.branch ?? null,
|
|
8366
|
+
commit: repo?.info.commit ?? null,
|
|
8367
|
+
passed: totals.pass,
|
|
8368
|
+
failed: totals.fail,
|
|
8369
|
+
notGraded: totals.error,
|
|
8370
|
+
cost: results.reduce((sum, r) => sum + (r.cost ?? 0), 0)
|
|
8371
|
+
})}
|
|
8281
8372
|
`);
|
|
8282
8373
|
ui.done({
|
|
8283
8374
|
runId,
|
|
@@ -8466,9 +8557,9 @@ firegoat ${package_default.version}` : `firegoat ${package_default.version}`,
|
|
|
8466
8557
|
}
|
|
8467
8558
|
|
|
8468
8559
|
// src/init.ts
|
|
8469
|
-
import { spawnSync as
|
|
8560
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8470
8561
|
import { existsSync as existsSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8471
|
-
import { basename as
|
|
8562
|
+
import { basename as basename3, join as join5 } from "node:path";
|
|
8472
8563
|
import { createInterface } from "node:readline/promises";
|
|
8473
8564
|
var SKILLS_ARGV = [
|
|
8474
8565
|
"skills",
|
|
@@ -8514,9 +8605,11 @@ runs: 1
|
|
|
8514
8605
|
|
|
8515
8606
|
# Optional: env vars for the agent (and for workdir_setup and checks of the same
|
|
8516
8607
|
# run). The agent gets none of vars:, only these. Placeholders: {{vars.KEY}},
|
|
8517
|
-
# {{run.id}}, {{run.n}}, {{
|
|
8608
|
+
# {{run.id}}, {{run.n}}, {{test.name}}, and $FIREGOAT_OUT / $FIREGOAT_WORKDIR (no
|
|
8609
|
+
# other $ expansion: $HOME stays literal). A test can add its own env:.
|
|
8518
8610
|
# env:
|
|
8519
8611
|
# MYTOOL_HOME: "/tmp/mytool-{{run.id}}-{{run.n}}"
|
|
8612
|
+
# UV_FIND_LINKS: "$FIREGOAT_OUT"
|
|
8520
8613
|
|
|
8521
8614
|
tests:
|
|
8522
8615
|
- name: quickstart
|
|
@@ -8545,7 +8638,7 @@ var CLIPBOARD = [
|
|
|
8545
8638
|
];
|
|
8546
8639
|
function copyToClipboard(text) {
|
|
8547
8640
|
return CLIPBOARD.some(([cmd, args]) => {
|
|
8548
|
-
const r =
|
|
8641
|
+
const r = spawnSync3(cmd, args, { input: text });
|
|
8549
8642
|
return !r.error && r.status === 0;
|
|
8550
8643
|
});
|
|
8551
8644
|
}
|
|
@@ -8592,7 +8685,7 @@ ${INIT_HELP}`);
|
|
|
8592
8685
|
const configPath = join5(cwd, CONFIG_FILE);
|
|
8593
8686
|
const keep = existsSync3(configPath) && !force;
|
|
8594
8687
|
if (!keep)
|
|
8595
|
-
writeFileSync4(configPath, template(
|
|
8688
|
+
writeFileSync4(configPath, template(basename3(cwd)));
|
|
8596
8689
|
if (interactive) {
|
|
8597
8690
|
step(1, CONFIG_FILE, keep ? "kept (--force replaces it)" : "created");
|
|
8598
8691
|
console.log(paint("90", " │"));
|
|
@@ -8609,7 +8702,7 @@ ${INIT_HELP}`);
|
|
|
8609
8702
|
console.log(`installing skills: npx ${SKILLS_ARGV.join(" ")}`);
|
|
8610
8703
|
const env = { ...process.env };
|
|
8611
8704
|
delete env.npm_config_package;
|
|
8612
|
-
const r =
|
|
8705
|
+
const r = spawnSync3("npx", ["-y", ...SKILLS_ARGV], {
|
|
8613
8706
|
cwd,
|
|
8614
8707
|
env,
|
|
8615
8708
|
stdio: "pipe",
|
|
@@ -8624,7 +8717,7 @@ ${INIT_HELP}`);
|
|
|
8624
8717
|
step(3, "firegoat-onboarding skill", "installed → .claude/skills/");
|
|
8625
8718
|
else
|
|
8626
8719
|
console.log("installed skills firegoat-onboarding, suggesting-eval-prompts in .claude/skills/");
|
|
8627
|
-
const ignored =
|
|
8720
|
+
const ignored = spawnSync3("git", ["check-ignore", "-q", ".claude/skills"], {
|
|
8628
8721
|
cwd
|
|
8629
8722
|
});
|
|
8630
8723
|
if (ignored.status === 0)
|