@boldblackai/harness 1.9.13 → 1.10.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 +25 -9
- package/bin/harness.js +129 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
Harness conveniently wraps Docker around three open-source coding agents — [`pi`](https://pi.dev/), [`opencode`](https://opencode.ai),
|
|
6
6
|
and [`hermes`](https://github.com/NousResearch/hermes-agent) — so you can point one at a directory (or file) without giving it access to your entire machine.
|
|
7
7
|
|
|
8
|
-
> **Documentation:** [
|
|
8
|
+
> **Documentation:** [harness.boldblack.ai/docs](https://harness.boldblack.ai/docs/)
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ and [`hermes`](https://github.com/NousResearch/hermes-agent) — so you can poin
|
|
|
20
20
|
|
|
21
21
|
## Quickstart
|
|
22
22
|
|
|
23
|
-
A container runtime is required. By default harness uses [Docker](https://www.docker.com); on macOS 26 / Apple Silicon
|
|
23
|
+
A container runtime is required. By default harness uses [Docker](https://www.docker.com); on macOS 26 / Apple Silicon, Apple's native [`container`](https://github.com/apple/container) CLI is preferred automatically if installed — see [Container runtime](#container-runtime). With the default runtime and LM Studio locally:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
lms daemon up
|
|
@@ -188,7 +188,7 @@ Each run starts the container with:
|
|
|
188
188
|
|
|
189
189
|
Running harness from your home directory is blocked by default, since that would mount all of `$HOME` into the container — exposing dotfiles, credentials, and SSH keys to the agent and giving it a noisy, unfocused workspace. If the resolved working directory equals your home, harness exits with an error. Pass `--mount-entire-home` to opt in for the rare case where mounting all of `$HOME` is genuinely intended. `--file` mode is unaffected (it mounts a single file, not the working directory).
|
|
190
190
|
|
|
191
|
-
These hardening flags are docker-specific.
|
|
191
|
+
These hardening flags are docker-specific. When running under Apple's `container` CLI (auto-detected on macOS 26 / Apple Silicon), `--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).
|
|
192
192
|
|
|
193
193
|
### Image verification
|
|
194
194
|
|
|
@@ -228,11 +228,12 @@ If an old `.harness/` directory exists in your working directory, harness will e
|
|
|
228
228
|
| Flag | Alias | Description |
|
|
229
229
|
|---------------|-------|-------------|
|
|
230
230
|
| `--prompt` | `-p` | Pass a prompt directly to the agent |
|
|
231
|
-
| `--env-file` | `-e` | Load environment variables into the container |
|
|
231
|
+
| `--env-file` | `-e` | Load environment variables into the container; may be repeated |
|
|
232
232
|
| `--file` | `-f` | Mount a single file instead of the current directory |
|
|
233
233
|
| `--model` | `-m` | Override the model used by the agent |
|
|
234
234
|
| `--agent` | `-a` | Select agent: `pi`, `opencode`, `hermes` (default: `pi`) |
|
|
235
235
|
| `--volumes` | `-v` | Additional volume mount (`host:container[:opts]`); may be repeated |
|
|
236
|
+
| `--port` | | Publish a container port to the host (`[host-ip:]host-port:container-port[/protocol]`); may be repeated |
|
|
236
237
|
| `--no-verify` | | Skip cosign signature and provenance verification |
|
|
237
238
|
| `--no-skills` | | Disable mounting user skills directories (`~/.agents/skills`, `~/.claude/skills`) |
|
|
238
239
|
| `--no-context-files` | `-nc` | Disable mounting global context files (`~/.agents/AGENTS.md`, `~/.claude/CLAUDE.md`) |
|
|
@@ -246,27 +247,34 @@ If an old `.harness/` directory exists in your working directory, harness will e
|
|
|
246
247
|
| Variable | Description |
|
|
247
248
|
|-----------------------------|-------------|
|
|
248
249
|
| `HARNESS_IMAGE_TAG` | Override the Docker image tag (defaults to the package version). Setting this implies `--no-verify`. |
|
|
249
|
-
| `HARNESS_CONTAINER_RUNTIME` | Container runtime
|
|
250
|
+
| `HARNESS_CONTAINER_RUNTIME` | Container runtime: `auto` (default — on macOS, prefers Apple's `container` CLI if on PATH; docker everywhere else), `docker` (force docker), or `apple` (deprecated explicit alias; warns). |
|
|
250
251
|
| `XDG_DATA_HOME` | Override the base directory for persistence data (defaults to `~/.local/share`). |
|
|
251
252
|
| `XDG_CACHE_HOME` | Override the base directory for the cosign cache (defaults to `~/.cache`). |
|
|
252
253
|
|
|
253
254
|
#### Container runtime
|
|
254
255
|
|
|
255
|
-
By default harness
|
|
256
|
+
By default harness auto-detects the container runtime: on macOS, if Apple's native [`container`](https://github.com/apple/container) CLI (v1.0.0+) is on PATH, it is preferred; on every other platform docker is used. Auto-detection is gated to macOS because apple/container ships for macOS only — a binary named `container` on a Linux PATH is an unrelated tool and is ignored. No configuration is needed.
|
|
256
257
|
|
|
257
258
|
```bash
|
|
258
259
|
brew install container # install Apple's container CLI (v1.0.0+)
|
|
259
260
|
container system start # one-time: start the container system service
|
|
260
261
|
container system kernel set --recommended --arch arm64 # one-time on Apple Silicon (required before first run)
|
|
261
|
-
export HARNESS_CONTAINER_RUNTIME=apple
|
|
262
262
|
harness -p "write me a fizzbuzz in Go"
|
|
263
263
|
```
|
|
264
264
|
|
|
265
265
|
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.
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
To force docker even when `container` is available, set:
|
|
268
268
|
|
|
269
|
-
|
|
269
|
+
```bash
|
|
270
|
+
export HARNESS_CONTAINER_RUNTIME=docker
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`HARNESS_CONTAINER_RUNTIME=apple` (the pre-auto-detect opt-in) still works but is deprecated: it selects the apple runtime explicitly, prints a deprecation warning, and will be removed in a future release.
|
|
274
|
+
|
|
275
|
+
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.
|
|
276
|
+
|
|
277
|
+
**Security note.** When running under Apple's `container` CLI, 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).
|
|
270
278
|
|
|
271
279
|
**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):
|
|
272
280
|
|
|
@@ -277,6 +285,14 @@ cpus = 8
|
|
|
277
285
|
memory = "4g"
|
|
278
286
|
```
|
|
279
287
|
|
|
288
|
+
**Container → host (`host.docker.internal`).** Docker Desktop provides `host.docker.internal` automatically; Apple's `container` CLI does not. Local-mode agents (pi, opencode, hermes without `-e`) reach LM Studio and other host services via that hostname. One-time setup on Apple Silicon (requires administrator):
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
sudo container system dns create host.docker.internal --localhost 203.0.113.113
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Harness prints a reminder on `HARNESS_CONTAINER_RUNTIME=apple` runs if this mapping is missing. See [apple/container — access a host service](https://github.com/apple/container/blob/main/docs/how-to.md#access-a-host-service-from-a-container).
|
|
295
|
+
|
|
280
296
|
### Agent-specific behavior
|
|
281
297
|
|
|
282
298
|
- **pi** — `-m` is passed straight to the binary as `--model`.
|
package/bin/harness.js
CHANGED
|
@@ -53,6 +53,7 @@ class DockerRuntime {
|
|
|
53
53
|
...input.envArgs,
|
|
54
54
|
...input.volumeArgs,
|
|
55
55
|
...input.userVolumeArgs,
|
|
56
|
+
...input.portArgs,
|
|
56
57
|
"-w",
|
|
57
58
|
input.workdir,
|
|
58
59
|
input.image,
|
|
@@ -60,10 +61,28 @@ class DockerRuntime {
|
|
|
60
61
|
];
|
|
61
62
|
}
|
|
62
63
|
ensureReady() {
|
|
63
|
-
// docker is the
|
|
64
|
+
// docker is the fallback; assume present. (A missing docker surfaces as a
|
|
64
65
|
// clear spawn ENOENT at run time, same as pre-runtime-abstraction behavior.)
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
const HOST_DOCKER_INTERNAL = "host.docker.internal";
|
|
69
|
+
// Documentation-range IP apple/container uses to route a custom DNS name to the
|
|
70
|
+
// host's localhost. See apple/container how-to: "Access a host service from a
|
|
71
|
+
// container".
|
|
72
|
+
const APPLE_HOST_LOCALHOST_IP = "203.0.113.113";
|
|
73
|
+
function appleHostDockerInternalDnsConfigured() {
|
|
74
|
+
try {
|
|
75
|
+
const out = (0, node_child_process_1.execFileSync)("container", ["system", "dns", "list"], {
|
|
76
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
77
|
+
timeout: 5000,
|
|
78
|
+
}).toString();
|
|
79
|
+
return out.includes(HOST_DOCKER_INTERNAL);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// If we cannot inspect DNS config, do not block the run.
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
67
86
|
class AppleContainerRuntime {
|
|
68
87
|
binary() {
|
|
69
88
|
return "container";
|
|
@@ -130,6 +149,7 @@ class AppleContainerRuntime {
|
|
|
130
149
|
...input.envArgs,
|
|
131
150
|
...input.volumeArgs,
|
|
132
151
|
...input.userVolumeArgs,
|
|
152
|
+
...input.portArgs,
|
|
133
153
|
"-w",
|
|
134
154
|
input.workdir,
|
|
135
155
|
input.image,
|
|
@@ -137,38 +157,75 @@ class AppleContainerRuntime {
|
|
|
137
157
|
];
|
|
138
158
|
}
|
|
139
159
|
ensureReady() {
|
|
160
|
+
// selectRuntime() already probed `container --version` (memoized); a false
|
|
161
|
+
// here means the probe failed there too — same evidence, one exec.
|
|
162
|
+
if (!appleContainerAvailable()) {
|
|
163
|
+
console.error("harness: the `container` CLI failed its version probe (Apple container, v1.0.0+). Install from https://github.com/apple/container/releases and run `container system start`, or set HARNESS_CONTAINER_RUNTIME=docker to use docker instead.");
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
if (!appleHostDockerInternalDnsConfigured()) {
|
|
167
|
+
console.error(`harness: ${HOST_DOCKER_INTERNAL} is not configured for Apple's container runtime.\nLocal services on the Mac (e.g. LM Studio on :1234) will not be reachable from the container.\nOne-time fix (requires administrator):\n sudo container system dns create ${HOST_DOCKER_INTERNAL} --localhost ${APPLE_HOST_LOCALHOST_IP}\nSee https://github.com/apple/container/blob/main/docs/how-to.md#access-a-host-service-from-a-container`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function selectRuntime() {
|
|
172
|
+
const raw = (process.env.HARNESS_CONTAINER_RUNTIME ?? "").toLowerCase();
|
|
173
|
+
if (raw === "docker") {
|
|
174
|
+
return new DockerRuntime();
|
|
175
|
+
}
|
|
176
|
+
if (raw === "apple") {
|
|
177
|
+
// Deprecated: the apple runtime is now auto-detected on macOS. Keep the
|
|
178
|
+
// value working (with a warning) so existing configs don't hard-fail.
|
|
179
|
+
console.error('harness: HARNESS_CONTAINER_RUNTIME=apple is deprecated (the apple runtime is now auto-detected on macOS); unset the variable, or use "auto" or "docker".');
|
|
180
|
+
return new AppleContainerRuntime();
|
|
181
|
+
}
|
|
182
|
+
if (raw !== "" && raw !== "auto") {
|
|
183
|
+
console.error(`harness: unknown HARNESS_CONTAINER_RUNTIME="${raw}". Valid values: auto (default), docker, apple (deprecated) — or unset for auto-detect.`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
// Auto-detect: prefer Apple's container CLI when it actually executes —
|
|
187
|
+
// but only on macOS. apple/container ships for macOS only, so on any other
|
|
188
|
+
// platform a binary named `container` is an unrelated tool and docker stays
|
|
189
|
+
// default. Probing `--version` (not just a PATH lookup) proves the binary
|
|
190
|
+
// runs at all; broken installs, quarantine attributes, and wrong-arch
|
|
191
|
+
// builds all fail here, and execution is what selection depends on.
|
|
192
|
+
if (process.platform === "darwin" && appleContainerAvailable()) {
|
|
193
|
+
return new AppleContainerRuntime();
|
|
194
|
+
}
|
|
195
|
+
return new DockerRuntime();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Whether Apple's `container` CLI exists and executes. Reuses the same
|
|
199
|
+
* `--version` probe `AppleContainerRuntime.ensureReady()` runs, which is
|
|
200
|
+
* strictly stronger evidence than a PATH lookup. The result is memoized for
|
|
201
|
+
* the process lifetime so `selectRuntime()` and `ensureReady()` share one
|
|
202
|
+
* probe instead of running it twice; it is never persisted across runs, since
|
|
203
|
+
* the CLI's install state can change between invocations.
|
|
204
|
+
*/
|
|
205
|
+
let appleContainerAvailableMemo = null;
|
|
206
|
+
function appleContainerAvailable() {
|
|
207
|
+
if (appleContainerAvailableMemo === null) {
|
|
140
208
|
try {
|
|
141
209
|
(0, node_child_process_1.execFileSync)("container", ["--version"], {
|
|
142
|
-
stdio:
|
|
210
|
+
stdio: "ignore",
|
|
143
211
|
timeout: 5000,
|
|
144
212
|
});
|
|
213
|
+
appleContainerAvailableMemo = true;
|
|
145
214
|
}
|
|
146
215
|
catch {
|
|
147
|
-
|
|
148
|
-
process.exit(1);
|
|
216
|
+
appleContainerAvailableMemo = false;
|
|
149
217
|
}
|
|
150
218
|
}
|
|
151
|
-
|
|
152
|
-
function selectRuntime() {
|
|
153
|
-
const raw = (process.env.HARNESS_CONTAINER_RUNTIME ?? "docker").toLowerCase();
|
|
154
|
-
switch (raw) {
|
|
155
|
-
case "docker":
|
|
156
|
-
return new DockerRuntime();
|
|
157
|
-
case "apple":
|
|
158
|
-
return new AppleContainerRuntime();
|
|
159
|
-
default:
|
|
160
|
-
console.error(`harness: unknown HARNESS_CONTAINER_RUNTIME="${raw}". Valid values: apple, docker (or unset for docker).`);
|
|
161
|
-
process.exit(1);
|
|
162
|
-
}
|
|
219
|
+
return appleContainerAvailableMemo;
|
|
163
220
|
}
|
|
164
221
|
class PiAdapter {
|
|
165
|
-
buildCommand({ prompt, model,
|
|
222
|
+
buildCommand({ prompt, model, envFilePaths }) {
|
|
166
223
|
// In local mode (no env file), pass --provider ollama so pi routes
|
|
167
224
|
// the model to the local LM Studio provider. Without this, model names
|
|
168
225
|
// containing slashes (e.g. HuggingFace IDs like "qwen/qwen3.5-9b") are
|
|
169
226
|
// misinterpreted as provider/model format, causing pi to silently ignore
|
|
170
227
|
// --model and fall back to a default that may require cloud credentials.
|
|
171
|
-
const providerArgs =
|
|
228
|
+
const providerArgs = envFilePaths.length === 0 && model ? ["--provider", "ollama"] : [];
|
|
172
229
|
const modelArgs = model ? ["--model", model] : [];
|
|
173
230
|
if (prompt !== null) {
|
|
174
231
|
return ["pi", "-p", prompt, ...providerArgs, ...modelArgs];
|
|
@@ -362,11 +419,12 @@ const USAGE = `Usage: harness [options]
|
|
|
362
419
|
|
|
363
420
|
Options:
|
|
364
421
|
-p, --prompt <text> Pass a prompt directly to the coding agent
|
|
365
|
-
-e, --env-file <file> Load environment variables from a file into the container
|
|
422
|
+
-e, --env-file <file> Load environment variables from a file into the container; may be repeated
|
|
366
423
|
-f, --file <file> Mount a single file into the container instead of the current directory
|
|
367
424
|
-m, --model <model> Override the model used by the agent
|
|
368
425
|
-a, --agent <name> Select the coding agent adapter: pi, opencode, hermes (default: pi)
|
|
369
426
|
-v, --volumes <spec> Additional volume mount (host:container[:opts]); may be repeated
|
|
427
|
+
--port <spec> Publish a container port to the host ([host-ip:]host-port:container-port[/protocol]); may be repeated
|
|
370
428
|
--no-verify Skip cosign image signature and provenance verification
|
|
371
429
|
--no-skills Disable mounting user skills directories (~/.agents/skills, ~/.claude/skills)
|
|
372
430
|
--no-context-files Disable mounting global context files (~/.agents/AGENTS.md, ~/.claude/CLAUDE.md); alias -nc
|
|
@@ -378,7 +436,7 @@ Options:
|
|
|
378
436
|
Environment variables:
|
|
379
437
|
HARNESS_IMAGE_TAG Override the Docker image tag (defaults to package version)
|
|
380
438
|
HARNESS_REGISTRY Override the container registry (defaults to ghcr.io/boldblackai/harness)
|
|
381
|
-
HARNESS_CONTAINER_RUNTIME Container runtime
|
|
439
|
+
HARNESS_CONTAINER_RUNTIME Container runtime: auto (default; prefers Apple container on macOS), docker, apple (deprecated)
|
|
382
440
|
XDG_DATA_HOME Override the base directory for persistence data (defaults to ~/.local/share)
|
|
383
441
|
XDG_CACHE_HOME Override the base directory for cosign cache (defaults to ~/.cache)
|
|
384
442
|
|
|
@@ -424,6 +482,7 @@ const MINIMIST_OPTS = {
|
|
|
424
482
|
"agent",
|
|
425
483
|
"a",
|
|
426
484
|
"volumes",
|
|
485
|
+
"port",
|
|
427
486
|
],
|
|
428
487
|
alias: {
|
|
429
488
|
e: "env-file",
|
|
@@ -468,7 +527,11 @@ const noSkills = argv.skills === false;
|
|
|
468
527
|
const noContextFiles = argv["context-files"] === false;
|
|
469
528
|
const mountEntireHome = argv["mount-entire-home"] === true;
|
|
470
529
|
const localMode = argv.local;
|
|
471
|
-
const
|
|
530
|
+
const envFilePaths = Array.isArray(argv["env-file"])
|
|
531
|
+
? argv["env-file"]
|
|
532
|
+
: argv["env-file"]
|
|
533
|
+
? [argv["env-file"]]
|
|
534
|
+
: [];
|
|
472
535
|
const fileArg = argv.file || null;
|
|
473
536
|
const promptArg = argv.prompt || null;
|
|
474
537
|
const modelArg = argv.model || null;
|
|
@@ -481,9 +544,11 @@ const agentName = (() => {
|
|
|
481
544
|
}
|
|
482
545
|
return name;
|
|
483
546
|
})();
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
547
|
+
for (const ep of envFilePaths) {
|
|
548
|
+
if (!node_fs_1.default.existsSync(ep)) {
|
|
549
|
+
console.error(`harness: env file not found: ${ep}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
487
552
|
}
|
|
488
553
|
if (fileArg && !node_fs_1.default.existsSync(fileArg)) {
|
|
489
554
|
console.error(`harness: file not found: ${fileArg}`);
|
|
@@ -522,6 +587,33 @@ for (const spec of volumeArgList) {
|
|
|
522
587
|
process.exit(1);
|
|
523
588
|
}
|
|
524
589
|
}
|
|
590
|
+
// Publish specs use the shared docker/apple-container syntax
|
|
591
|
+
// [host-ip:]host-port:container-port[/protocol]. Validate up front so a
|
|
592
|
+
// typo fails fast with a clear message instead of an opaque runtime error
|
|
593
|
+
// after image verification/pulling.
|
|
594
|
+
const PORT_SPEC_REGEXP = /^(?:(?:\d{1,3}(?:\.\d{1,3}){3}|\[[0-9a-fA-F:.]+\]):)?(\d{1,5}):(\d{1,5})(?:\/(tcp|udp|sctp))?$/;
|
|
595
|
+
function isValidPortSpec(spec) {
|
|
596
|
+
const match = spec.match(PORT_SPEC_REGEXP);
|
|
597
|
+
if (!match) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
const portInRange = (p) => {
|
|
601
|
+
const n = Number.parseInt(p, 10);
|
|
602
|
+
return n >= 1 && n <= 65535;
|
|
603
|
+
};
|
|
604
|
+
return portInRange(match[1]) && portInRange(match[2]);
|
|
605
|
+
}
|
|
606
|
+
const portArgList = Array.isArray(argv.port)
|
|
607
|
+
? argv.port
|
|
608
|
+
: argv.port
|
|
609
|
+
? [argv.port]
|
|
610
|
+
: [];
|
|
611
|
+
for (const spec of portArgList) {
|
|
612
|
+
if (!isValidPortSpec(spec)) {
|
|
613
|
+
console.error(`harness: invalid port spec "${spec}" (expected [host-ip:]host-port:container-port[/protocol], e.g. 8080:3000)`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
525
617
|
async function run(prompt) {
|
|
526
618
|
const runtime = selectRuntime();
|
|
527
619
|
runtime.ensureReady();
|
|
@@ -534,14 +626,15 @@ async function run(prompt) {
|
|
|
534
626
|
await verifyImage(runtime, image);
|
|
535
627
|
}
|
|
536
628
|
}
|
|
537
|
-
const envFileArgs =
|
|
538
|
-
|
|
539
|
-
|
|
629
|
+
const envFileArgs = envFilePaths.flatMap((f) => [
|
|
630
|
+
"--env-file",
|
|
631
|
+
node_path_1.default.resolve(f),
|
|
632
|
+
]);
|
|
540
633
|
// Cloud mode: -e without --local signals entrypoints to skip local/defaults
|
|
541
634
|
// and let agents auto-detect providers from env vars in the file.
|
|
542
|
-
const cloudModeEnv =
|
|
635
|
+
const cloudModeEnv = envFilePaths.length > 0 && !localMode ? ["-e", "HARNESS_CLOUD_MODE=1"] : [];
|
|
543
636
|
const adapter = ADAPTERS[agentName];
|
|
544
|
-
const adapterOptions = { prompt, model: modelArg,
|
|
637
|
+
const adapterOptions = { prompt, model: modelArg, envFilePaths };
|
|
545
638
|
const containerCmd = adapter.buildCommand(adapterOptions);
|
|
546
639
|
const adapterDockerArgs = adapter.extraDockerArgs?.(adapterOptions) ?? [];
|
|
547
640
|
const interactive = process.stdin.isTTY;
|
|
@@ -629,12 +722,19 @@ async function run(prompt) {
|
|
|
629
722
|
for (const spec of volumeArgList) {
|
|
630
723
|
userVolumeArgs.push("-v", `${node_path_1.default.resolve(spec.split(":")[0])}:${spec.split(":").slice(1).join(":")}`);
|
|
631
724
|
}
|
|
725
|
+
// docker -p and apple/container --publish share the spec syntax, so the
|
|
726
|
+
// flags are runtime-agnostic (unlike caps/tty handling).
|
|
727
|
+
const portArgs = [];
|
|
728
|
+
for (const spec of portArgList) {
|
|
729
|
+
portArgs.push("-p", spec);
|
|
730
|
+
}
|
|
632
731
|
const args = runtime.runArgs({
|
|
633
732
|
interactive,
|
|
634
733
|
envFileArgs,
|
|
635
734
|
envArgs: [...cloudModeEnv, ...adapterDockerArgs],
|
|
636
735
|
volumeArgs,
|
|
637
736
|
userVolumeArgs,
|
|
737
|
+
portArgs,
|
|
638
738
|
workdir: "/workspace",
|
|
639
739
|
image,
|
|
640
740
|
containerCmd,
|