@boldblackai/harness 1.8.6 → 1.9.1
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 +39 -5
- package/bin/harness.js +168 -52
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ and [`hermes`](https://github.com/NousResearch/hermes-agent) — so you can poin
|
|
|
25
25
|
|
|
26
26
|
## Quickstart
|
|
27
27
|
|
|
28
|
-
[Docker](https://www.docker.com)
|
|
28
|
+
A container runtime is required. By default harness uses [Docker](https://www.docker.com); on macOS 26 / Apple Silicon you can also use Apple's native [`container`](https://github.com/apple/container) CLI — see [Container runtime](#container-runtime). With the default runtime and LM Studio locally:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
lms daemon up
|
|
@@ -174,7 +174,9 @@ npx @boldblackai/harness -a hermes -e .env -m anthropic/claude-sonnet-4-5 -p "ad
|
|
|
174
174
|
npx @boldblackai/harness -a hermes -e .env -m openrouter/auto -p "add tests"
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
Common keys: `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`, [and others](https://github.com/NousResearch/hermes-agent/blob/main/.env.example). LM Studio context length should be at least 64k tokens.
|
|
177
|
+
Common keys: `ANTHROPIC_API_KEY`, `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`, `GLM_API_KEY` (Z.AI), [and others](https://github.com/NousResearch/hermes-agent/blob/main/.env.example). LM Studio context length should be at least 64k tokens.
|
|
178
|
+
|
|
179
|
+
**Multiple API keys in one env file.** With `--env-file`, each agent auto-detects a provider from the keys present. If the file contains keys for more than one provider (e.g. `OPENROUTER_API_KEY` and `GLM_API_KEY`), the agent may not pick the one you expect — `pi` and `opencode` follow their own detection order. For one-shot runs, use a single-provider env file, or select the agent and model explicitly (e.g. `harness -a hermes -e .env -m zai/glm-4.7 -p "..."`).
|
|
178
180
|
|
|
179
181
|
## Security model
|
|
180
182
|
|
|
@@ -189,6 +191,8 @@ Each run starts the container with:
|
|
|
189
191
|
- `--security-opt seccomp=...` — inline seccomp profile blocks `socket(AF_ALG)` to prevent kernel crypto API access (a known container escape vector)
|
|
190
192
|
- Only your mounted directory (or single file with `-f`) is visible to the agent
|
|
191
193
|
|
|
194
|
+
These hardening flags are docker-specific. Under `HARNESS_CONTAINER_RUNTIME=apple`, `--security-opt` is not applied (apple/container workloads are microVMs with their own guest kernel, so the seccomp profile's host-kernel role is subsumed by the VM boundary); capability restrictions remain. See [Container runtime](#container-runtime).
|
|
195
|
+
|
|
192
196
|
### Image verification
|
|
193
197
|
|
|
194
198
|
By default, harness verifies that the container image was signed by the official CI workflow and carries a valid SLSA provenance attestation. This requires [cosign](https://github.com/sigstore/cosign):
|
|
@@ -241,9 +245,39 @@ If an old `.harness/` directory exists in your working directory, harness will e
|
|
|
241
245
|
|
|
242
246
|
### Environment variables
|
|
243
247
|
|
|
244
|
-
| Variable
|
|
245
|
-
|
|
246
|
-
| `HARNESS_IMAGE_TAG`
|
|
248
|
+
| Variable | Description |
|
|
249
|
+
|-----------------------------|-------------|
|
|
250
|
+
| `HARNESS_IMAGE_TAG` | Override the Docker image tag (defaults to the package version). Setting this implies `--no-verify`. |
|
|
251
|
+
| `HARNESS_CONTAINER_RUNTIME` | Container runtime to use: `docker` (default) or `apple` (Apple's [`container`](https://github.com/apple/container) CLI). |
|
|
252
|
+
| `XDG_DATA_HOME` | Override the base directory for persistence data (defaults to `~/.local/share`). |
|
|
253
|
+
| `XDG_CACHE_HOME` | Override the base directory for the cosign cache (defaults to `~/.cache`). |
|
|
254
|
+
|
|
255
|
+
#### Container runtime
|
|
256
|
+
|
|
257
|
+
By default harness runs images with `docker`. On macOS 26 / Apple Silicon you can opt into Apple's native [`container`](https://github.com/apple/container) CLI (v1.0.0+) instead, which runs OCI images as lightweight Linux microVMs:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
brew install container # install Apple's container CLI (v1.0.0+)
|
|
261
|
+
container system start # one-time: start the container system service
|
|
262
|
+
container system kernel set --recommended --arch arm64 # one-time on Apple Silicon (required before first run)
|
|
263
|
+
export HARNESS_CONTAINER_RUNTIME=apple
|
|
264
|
+
harness -p "write me a fizzbuzz in Go"
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
On arm64 Macs, `container system kernel set --recommended` is required — without it the first `harness` run fails with `default kernel not configured for architecture arm64`. See [apple/container](https://github.com/apple/container) for details.
|
|
268
|
+
|
|
269
|
+
The value is **named, not boolean** (`apple` or `docker`, case-insensitive); any other value is a hard error. harness never auto-detects the runtime — you must opt in. Image verification (cosign + SLSA provenance) works identically under both runtimes; the verified-digest cache is keyed by digest, so a digest verified under one runtime is a cache hit under the other.
|
|
270
|
+
|
|
271
|
+
**Security note.** Under `=apple`, harness does not apply the `--security-opt no-new-privileges` and `--security-opt seccomp=...` flags it uses under docker, because `apple/container` has no `--security-opt` option. This is not a security regression: each apple/container workload is a microVM with its own ephemeral guest kernel (Apple Virtualization framework), so the `block-af-alg.json` profile's host-kernel role — blocking `socket(AF_ALG)` — is subsumed by the VM boundary itself (hardware-assisted isolation, strictly stronger than a syscall filter). Capability restrictions (`--cap-drop=ALL --cap-add=NET_RAW`) **are** supported and stay on. Only `ro`/`readonly` is honored for volume options under the apple path (SELinux relabel flags like `:Z` are meaningless under macOS virtiofs).
|
|
272
|
+
|
|
273
|
+
**Optional tuning — resource limits.** Apple's container CLI reads defaults from `~/.config/container/config.toml`. Create this file to control CPU and memory allocation for all containers (see [container-system-config.md](https://github.com/apple/container/blob/main/docs/container-system-config.md) for the full schema):
|
|
274
|
+
|
|
275
|
+
```toml
|
|
276
|
+
# example values; adjust to your machine
|
|
277
|
+
[container]
|
|
278
|
+
cpus = 8
|
|
279
|
+
memory = "4g"
|
|
280
|
+
```
|
|
247
281
|
|
|
248
282
|
### Agent-specific behavior
|
|
249
283
|
|
package/bin/harness.js
CHANGED
|
@@ -10,6 +10,150 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
11
|
const minimist_1 = __importDefault(require("minimist"));
|
|
12
12
|
const SECCOMP_PROFILE = node_path_1.default.join(__dirname, "seccomp-profiles", "block-af-alg.json");
|
|
13
|
+
class DockerRuntime {
|
|
14
|
+
binary() {
|
|
15
|
+
return "docker";
|
|
16
|
+
}
|
|
17
|
+
pullArgs(image) {
|
|
18
|
+
return ["pull", image];
|
|
19
|
+
}
|
|
20
|
+
inspectImage(image) {
|
|
21
|
+
try {
|
|
22
|
+
const out = (0, node_child_process_1.execFileSync)("docker", [
|
|
23
|
+
"image",
|
|
24
|
+
"inspect",
|
|
25
|
+
"--format",
|
|
26
|
+
"{{if .RepoDigests}}{{index .RepoDigests 0}}{{end}}",
|
|
27
|
+
image,
|
|
28
|
+
], { stdio: ["ignore", "pipe", "ignore"], timeout: 5000 })
|
|
29
|
+
.toString()
|
|
30
|
+
.trim();
|
|
31
|
+
return {
|
|
32
|
+
exists: true,
|
|
33
|
+
digest: /@sha256:[0-9a-f]{64}$/.test(out) ? out : null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { exists: false, digest: null };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
runArgs(input) {
|
|
41
|
+
const ttyFlags = input.interactive ? ["-it"] : ["-i"];
|
|
42
|
+
return [
|
|
43
|
+
"run",
|
|
44
|
+
"--rm",
|
|
45
|
+
...ttyFlags,
|
|
46
|
+
"--cap-drop=ALL",
|
|
47
|
+
"--cap-add=NET_RAW",
|
|
48
|
+
"--security-opt",
|
|
49
|
+
"no-new-privileges:true",
|
|
50
|
+
"--security-opt",
|
|
51
|
+
`seccomp=${SECCOMP_PROFILE}`,
|
|
52
|
+
...input.envFileArgs,
|
|
53
|
+
...input.envArgs,
|
|
54
|
+
...input.volumeArgs,
|
|
55
|
+
...input.userVolumeArgs,
|
|
56
|
+
"-w",
|
|
57
|
+
input.workdir,
|
|
58
|
+
input.image,
|
|
59
|
+
...input.containerCmd,
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
ensureReady() {
|
|
63
|
+
// docker is the default; assume present. (A missing docker surfaces as a
|
|
64
|
+
// clear spawn ENOENT at run time, same as pre-runtime-abstraction behavior.)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class AppleContainerRuntime {
|
|
68
|
+
binary() {
|
|
69
|
+
return "container";
|
|
70
|
+
}
|
|
71
|
+
pullArgs(image) {
|
|
72
|
+
// apple/container nests pull under the `image` subgroup.
|
|
73
|
+
return ["image", "pull", image];
|
|
74
|
+
}
|
|
75
|
+
inspectImage(image) {
|
|
76
|
+
let out;
|
|
77
|
+
try {
|
|
78
|
+
out = (0, node_child_process_1.execFileSync)("container", ["image", "inspect", image], {
|
|
79
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
80
|
+
timeout: 5000,
|
|
81
|
+
}).toString();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { exists: false, digest: null };
|
|
85
|
+
}
|
|
86
|
+
// apple/container's `image inspect` always emits a JSON array (no
|
|
87
|
+
// --format flag). The image-index/manifest-list digest it reports at
|
|
88
|
+
// data[0].configuration.descriptor.digest is exactly what cosign signs
|
|
89
|
+
// for a multi-arch image and what docker exposes as the RepoDigest.
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(out);
|
|
92
|
+
const digest = Array.isArray(parsed)
|
|
93
|
+
? parsed[0]?.configuration?.descriptor?.digest
|
|
94
|
+
: null;
|
|
95
|
+
if (typeof digest === "string" && /^sha256:[0-9a-f]{64}$/.test(digest)) {
|
|
96
|
+
const repo = image.split(":")[0];
|
|
97
|
+
return { exists: true, digest: `${repo}@${digest}` };
|
|
98
|
+
}
|
|
99
|
+
return { exists: true, digest: null };
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return { exists: true, digest: null };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
runArgs(input) {
|
|
106
|
+
// apple/container (Swift ArgumentParser) wants -i and -t as separate
|
|
107
|
+
// tokens (no clustered -it) and space-separated capability values (no
|
|
108
|
+
// `=` join). It has no --security-opt at all: each workload is a microVM
|
|
109
|
+
// with its own guest kernel, so the block-af-alg seccomp profile's
|
|
110
|
+
// host-kernel role is subsumed by the VM boundary and no-new-privileges
|
|
111
|
+
// is only a minor in-guest defense-in-depth loss. Capability
|
|
112
|
+
// restrictions ARE supported, so --cap-drop=ALL --cap-add=NET_RAW stays.
|
|
113
|
+
const ttyFlags = input.interactive ? ["-i", "-t"] : ["-i"];
|
|
114
|
+
return [
|
|
115
|
+
"run",
|
|
116
|
+
"--rm",
|
|
117
|
+
...ttyFlags,
|
|
118
|
+
"--cap-drop",
|
|
119
|
+
"ALL",
|
|
120
|
+
"--cap-add",
|
|
121
|
+
"NET_RAW",
|
|
122
|
+
...input.envFileArgs,
|
|
123
|
+
...input.envArgs,
|
|
124
|
+
...input.volumeArgs,
|
|
125
|
+
...input.userVolumeArgs,
|
|
126
|
+
"-w",
|
|
127
|
+
input.workdir,
|
|
128
|
+
input.image,
|
|
129
|
+
...input.containerCmd,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
ensureReady() {
|
|
133
|
+
try {
|
|
134
|
+
(0, node_child_process_1.execFileSync)("container", ["--version"], {
|
|
135
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
136
|
+
timeout: 5000,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
console.error("harness: HARNESS_CONTAINER_RUNTIME=apple requires the `container` CLI (Apple container, v1.0.0+). Install from https://github.com/apple/container/releases and run `container system start`, or unset HARNESS_CONTAINER_RUNTIME to use docker.");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function selectRuntime() {
|
|
146
|
+
const raw = (process.env.HARNESS_CONTAINER_RUNTIME ?? "docker").toLowerCase();
|
|
147
|
+
switch (raw) {
|
|
148
|
+
case "docker":
|
|
149
|
+
return new DockerRuntime();
|
|
150
|
+
case "apple":
|
|
151
|
+
return new AppleContainerRuntime();
|
|
152
|
+
default:
|
|
153
|
+
console.error(`harness: unknown HARNESS_CONTAINER_RUNTIME="${raw}". Valid values: apple, docker (or unset for docker).`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
13
157
|
class PiAdapter {
|
|
14
158
|
buildCommand({ prompt, model, envFilePath }) {
|
|
15
159
|
// In local mode (no env file), pass --provider ollama so pi routes
|
|
@@ -87,26 +231,6 @@ function cachePath() {
|
|
|
87
231
|
const base = process.env.XDG_CACHE_HOME || node_path_1.default.join(node_os_1.default.homedir(), ".cache");
|
|
88
232
|
return node_path_1.default.join(base, "harness", "cosign-verified.json");
|
|
89
233
|
}
|
|
90
|
-
function inspectLocalImage(image) {
|
|
91
|
-
try {
|
|
92
|
-
const out = (0, node_child_process_1.execFileSync)("docker", [
|
|
93
|
-
"image",
|
|
94
|
-
"inspect",
|
|
95
|
-
"--format",
|
|
96
|
-
"{{if .RepoDigests}}{{index .RepoDigests 0}}{{end}}",
|
|
97
|
-
image,
|
|
98
|
-
], { stdio: ["ignore", "pipe", "ignore"], timeout: 5000 })
|
|
99
|
-
.toString()
|
|
100
|
-
.trim();
|
|
101
|
-
return {
|
|
102
|
-
exists: true,
|
|
103
|
-
digest: /@sha256:[0-9a-f]{64}$/.test(out) ? out : null,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return { exists: false, digest: null };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
234
|
function readCache() {
|
|
111
235
|
try {
|
|
112
236
|
const raw = node_fs_1.default.readFileSync(cachePath(), "utf8");
|
|
@@ -147,31 +271,31 @@ function cosign(args) {
|
|
|
147
271
|
});
|
|
148
272
|
});
|
|
149
273
|
}
|
|
150
|
-
async function verifyImage(image) {
|
|
151
|
-
let { exists, digest: digestRef } =
|
|
274
|
+
async function verifyImage(runtime, image) {
|
|
275
|
+
let { exists, digest: digestRef } = runtime.inspectImage(image);
|
|
152
276
|
const cache = readCache();
|
|
153
277
|
if (digestRef && cache.verified[digestRef]) {
|
|
154
278
|
return;
|
|
155
279
|
}
|
|
156
280
|
if (exists && !digestRef) {
|
|
157
281
|
console.error(`harness: refusing to verify ${image}: image exists locally but has no registry digest (locally-built?).`);
|
|
158
|
-
console.error("harness: verifying the tag would check registry bytes, not the local image
|
|
282
|
+
console.error("harness: verifying the tag would check registry bytes, not the local image the runtime would run.");
|
|
159
283
|
console.error("harness: use --no-verify, or set HARNESS_IMAGE_TAG for an implicit skip.");
|
|
160
284
|
process.exit(1);
|
|
161
285
|
}
|
|
162
286
|
if (!digestRef) {
|
|
163
287
|
console.error(`harness: pulling ${image} for verification...`);
|
|
164
288
|
try {
|
|
165
|
-
(0, node_child_process_1.execFileSync)(
|
|
289
|
+
(0, node_child_process_1.execFileSync)(runtime.binary(), runtime.pullArgs(image), {
|
|
166
290
|
stdio: ["ignore", "inherit", "inherit"],
|
|
167
291
|
timeout: 600000,
|
|
168
292
|
});
|
|
169
293
|
}
|
|
170
294
|
catch {
|
|
171
|
-
console.error(`harness:
|
|
295
|
+
console.error(`harness: ${runtime.binary()} pull failed for ${image}`);
|
|
172
296
|
process.exit(1);
|
|
173
297
|
}
|
|
174
|
-
digestRef =
|
|
298
|
+
digestRef = runtime.inspectImage(image).digest;
|
|
175
299
|
if (!digestRef) {
|
|
176
300
|
console.error(`harness: failed to resolve digest for ${image} after pull`);
|
|
177
301
|
process.exit(1);
|
|
@@ -244,9 +368,10 @@ Options:
|
|
|
244
368
|
-h, --help Show this help message
|
|
245
369
|
|
|
246
370
|
Environment variables:
|
|
247
|
-
HARNESS_IMAGE_TAG
|
|
248
|
-
|
|
249
|
-
|
|
371
|
+
HARNESS_IMAGE_TAG Override the Docker image tag (defaults to package version)
|
|
372
|
+
HARNESS_CONTAINER_RUNTIME Container runtime to use: docker (default) or apple (Apple container CLI)
|
|
373
|
+
XDG_DATA_HOME Override the base directory for persistence data (defaults to ~/.local/share)
|
|
374
|
+
XDG_CACHE_HOME Override the base directory for cosign cache (defaults to ~/.cache)
|
|
250
375
|
|
|
251
376
|
Persistence data is stored at $XDG_DATA_HOME/harness/<project>/<agent>/.
|
|
252
377
|
|
|
@@ -375,13 +500,15 @@ for (const spec of volumeArgList) {
|
|
|
375
500
|
}
|
|
376
501
|
}
|
|
377
502
|
async function run(prompt) {
|
|
503
|
+
const runtime = selectRuntime();
|
|
504
|
+
runtime.ensureReady();
|
|
378
505
|
const image = getImage(agentName);
|
|
379
506
|
if (!noVerify) {
|
|
380
507
|
if (process.env.HARNESS_IMAGE_TAG) {
|
|
381
508
|
console.error(`harness: HARNESS_IMAGE_TAG is set; skipping cosign verification for ${image}`);
|
|
382
509
|
}
|
|
383
510
|
else {
|
|
384
|
-
await verifyImage(image);
|
|
511
|
+
await verifyImage(runtime, image);
|
|
385
512
|
}
|
|
386
513
|
}
|
|
387
514
|
const envFileArgs = envFilePath
|
|
@@ -395,7 +522,6 @@ async function run(prompt) {
|
|
|
395
522
|
const containerCmd = adapter.buildCommand(adapterOptions);
|
|
396
523
|
const adapterDockerArgs = adapter.extraDockerArgs?.(adapterOptions) ?? [];
|
|
397
524
|
const interactive = process.stdin.isTTY;
|
|
398
|
-
const ttyFlags = interactive ? ["-it"] : ["-i"];
|
|
399
525
|
let volumeArgs;
|
|
400
526
|
if (fileArg) {
|
|
401
527
|
const absFile = node_path_1.default.resolve(fileArg);
|
|
@@ -480,28 +606,18 @@ async function run(prompt) {
|
|
|
480
606
|
for (const spec of volumeArgList) {
|
|
481
607
|
userVolumeArgs.push("-v", `${node_path_1.default.resolve(spec.split(":")[0])}:${spec.split(":").slice(1).join(":")}`);
|
|
482
608
|
}
|
|
483
|
-
const args =
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
...
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
"
|
|
490
|
-
"no-new-privileges:true",
|
|
491
|
-
"--security-opt",
|
|
492
|
-
`seccomp=${SECCOMP_PROFILE}`,
|
|
493
|
-
...envFileArgs,
|
|
494
|
-
...cloudModeEnv,
|
|
495
|
-
...adapterDockerArgs,
|
|
496
|
-
...volumeArgs,
|
|
497
|
-
...userVolumeArgs,
|
|
498
|
-
"-w",
|
|
499
|
-
"/workspace",
|
|
609
|
+
const args = runtime.runArgs({
|
|
610
|
+
interactive,
|
|
611
|
+
envFileArgs,
|
|
612
|
+
envArgs: [...cloudModeEnv, ...adapterDockerArgs],
|
|
613
|
+
volumeArgs,
|
|
614
|
+
userVolumeArgs,
|
|
615
|
+
workdir: "/workspace",
|
|
500
616
|
image,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
617
|
+
containerCmd,
|
|
618
|
+
});
|
|
619
|
+
const child = (0, node_child_process_1.spawn)(runtime.binary(), args, { stdio: "inherit" });
|
|
620
|
+
child.on("exit", (code) => process.exit(code ?? 1));
|
|
505
621
|
}
|
|
506
622
|
if (!process.stdin.isTTY && promptArg === null) {
|
|
507
623
|
let input = "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boldblackai/harness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Portable environment for running coding agents in a container",
|
|
5
5
|
"bin": {
|
|
6
6
|
"harness": "bin/harness.js"
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"lint:docker": "hadolint Dockerfile Dockerfile.opencode Dockerfile.hermes",
|
|
26
26
|
"lint:actions": "actionlint",
|
|
27
27
|
"format": "biome format --write .",
|
|
28
|
-
"test:e2e:cli": "node --test tests/e2e
|
|
29
|
-
"test:e2e": "node --test tests/e2e
|
|
30
|
-
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**\" --test-coverage-lines=80 --test-coverage-functions=80 --test-coverage-branches=80 tests/e2e
|
|
28
|
+
"test:e2e:cli": "node --test tests/e2e/*.test.mjs",
|
|
29
|
+
"test:e2e": "node --test tests/e2e/*.test.mjs",
|
|
30
|
+
"test:coverage": "node --experimental-test-coverage --test-coverage-include=\"src/**\" --test-coverage-lines=80 --test-coverage-functions=80 --test-coverage-branches=80 tests/e2e/*.test.mjs"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"minimist": "^1.2.8"
|