@madarco/agentbox 0.15.0 → 0.16.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/CHANGELOG.md +48 -0
- package/dist/{_cloud-attach-R6TRWG5L.js → _cloud-attach-5KJWOASL.js} +4 -4
- package/dist/{chunk-RSKG7AFU.js → chunk-3WCEB6RE.js} +2 -2
- package/dist/{chunk-XKH7NTT7.js → chunk-DBBUDKKB.js} +248 -5
- package/dist/chunk-DBBUDKKB.js.map +1 -0
- package/dist/{chunk-MLMFNN4T.js → chunk-GXJNJUEV.js} +688 -197
- package/dist/chunk-GXJNJUEV.js.map +1 -0
- package/dist/{chunk-MCOU6CZS.js → chunk-NW2UZQV6.js} +10 -6
- package/dist/chunk-NW2UZQV6.js.map +1 -0
- package/dist/{chunk-E7CHS7ZR.js → chunk-PIK47622.js} +18 -6
- package/dist/chunk-PIK47622.js.map +1 -0
- package/dist/{chunk-BKU34KYY.js → chunk-QXFNLKJJ.js} +9 -3
- package/dist/{chunk-BKU34KYY.js.map → chunk-QXFNLKJJ.js.map} +1 -1
- package/dist/{chunk-43Q5GWP6.js → chunk-SB4QTF2T.js} +7 -7
- package/dist/{chunk-72CJTXN6.js → chunk-SENASAU4.js} +10 -6
- package/dist/{chunk-72CJTXN6.js.map → chunk-SENASAU4.js.map} +1 -1
- package/dist/{dist-JZ3XO6EB.js → dist-4IQFJJQI.js} +5 -5
- package/dist/{dist-OGJGZETZ.js → dist-7YB7BMNG.js} +5 -5
- package/dist/{dist-FIFEFKJ7.js → dist-SL2QSMBE.js} +5 -5
- package/dist/{dist-AGTIA7AD.js → dist-VHI5QOSQ.js} +6 -6
- package/dist/{dist-S4XR4ACV.js → dist-XC47DSCR.js} +5 -5
- package/dist/index.js +210 -68
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js → prepared-state-MQHD3M5F-2LANTRL7.js} +2 -2
- package/package.json +5 -4
- package/runtime/docker/Dockerfile.box +21 -2
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +82 -29
- package/runtime/docker/packages/ctl/dist/bin.cjs +10675 -9191
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +5 -2
- package/runtime/docker/packages/sandbox-docker/scripts/linear-shim +181 -0
- package/runtime/docker/packages/sandbox-docker/scripts/ntn-shim +95 -0
- package/runtime/e2b/agentbox-checkpoint-cleanup +5 -2
- package/runtime/e2b/agentbox-setup-skill.md +82 -29
- package/runtime/e2b/ctl.cjs +10675 -9191
- package/runtime/e2b/linear-shim +181 -0
- package/runtime/e2b/ntn-shim +95 -0
- package/runtime/e2b/scripts/build-template.sh +13 -7
- package/runtime/hetzner/agentbox-checkpoint-cleanup +5 -2
- package/runtime/hetzner/agentbox-setup-skill.md +82 -29
- package/runtime/hetzner/ctl.cjs +10675 -9191
- package/runtime/hetzner/linear-shim +181 -0
- package/runtime/hetzner/ntn-shim +95 -0
- package/runtime/hetzner/scripts/install-box.sh +19 -9
- package/runtime/relay/bin.cjs +3696 -2895
- package/runtime/vercel/agentbox-checkpoint-cleanup +5 -2
- package/runtime/vercel/agentbox-setup-skill.md +82 -29
- package/runtime/vercel/ctl.cjs +10675 -9191
- package/runtime/vercel/linear-shim +181 -0
- package/runtime/vercel/ntn-shim +95 -0
- package/runtime/vercel/scripts/provision.sh +13 -7
- package/share/agentbox-setup/SKILL.md +82 -29
- package/share/host-skills/agentbox-info/SKILL.md +1 -1
- package/dist/chunk-E7CHS7ZR.js.map +0 -1
- package/dist/chunk-MCOU6CZS.js.map +0 -1
- package/dist/chunk-MLMFNN4T.js.map +0 -1
- package/dist/chunk-XKH7NTT7.js.map +0 -1
- /package/dist/{_cloud-attach-R6TRWG5L.js.map → _cloud-attach-5KJWOASL.js.map} +0 -0
- /package/dist/{chunk-RSKG7AFU.js.map → chunk-3WCEB6RE.js.map} +0 -0
- /package/dist/{chunk-43Q5GWP6.js.map → chunk-SB4QTF2T.js.map} +0 -0
- /package/dist/{dist-JZ3XO6EB.js.map → dist-4IQFJJQI.js.map} +0 -0
- /package/dist/{dist-OGJGZETZ.js.map → dist-7YB7BMNG.js.map} +0 -0
- /package/dist/{dist-FIFEFKJ7.js.map → dist-SL2QSMBE.js.map} +0 -0
- /package/dist/{dist-AGTIA7AD.js.map → dist-VHI5QOSQ.js.map} +0 -0
- /package/dist/{dist-S4XR4ACV.js.map → dist-XC47DSCR.js.map} +0 -0
- /package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js.map → prepared-state-MQHD3M5F-2LANTRL7.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,54 @@ Entries are generated from the commit history with `/release-notes` and then
|
|
|
9
9
|
hand-reviewed — they describe what changed for someone using the `agentbox`
|
|
10
10
|
CLI, not the raw commits.
|
|
11
11
|
|
|
12
|
+
## [0.16.0] - 2026-06-07
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Notion integration.** A box can now call Notion through the host's
|
|
17
|
+
authenticated `ntn` CLI without the Notion token ever entering the box. The
|
|
18
|
+
in-box `ntn`/`notion` shim proxies to the host relay: reads pass straight
|
|
19
|
+
through, writes (`pages create`/`pages update`) prompt for host approval.
|
|
20
|
+
`ntn api` is read-only — GET to any endpoint plus the read-only POSTs
|
|
21
|
+
`v1/search`, `v1/databases/<id>/query`, and `v1/data_sources/<id>/query`
|
|
22
|
+
(full JSON bodies via `-d '<json>'`); every other method/endpoint is refused.
|
|
23
|
+
Off by default; enable per project with
|
|
24
|
+
`agentbox config set --project integrations.notion.enabled true`. Shows up in
|
|
25
|
+
`agentbox doctor`.
|
|
26
|
+
- **Linear integration.** Same model for `@schpet/linear-cli`: read issues,
|
|
27
|
+
teams, and filtered queries plus a GraphQL **query** passthrough
|
|
28
|
+
(`linear api`); `mutation`/`subscription` are refused. `issue create`/
|
|
29
|
+
`update`/`comment add` prompt for host approval; `auth token` is hard-rejected
|
|
30
|
+
so the key stays on the host. Enable with `integrations.linear.enabled`.
|
|
31
|
+
- **`run_once:` tasks** in `agentbox.yaml` (renamed from `idempotent:`): a task
|
|
32
|
+
that runs only on a cold box and is skipped on warm boots, tracked by a
|
|
33
|
+
durable marker.
|
|
34
|
+
- **`agentbox.yaml` replacement engine** with an `{{AGENTBOX_AUTO_SECRET}}`
|
|
35
|
+
generator (stable per-project secret) and a new `agentbox render` command to
|
|
36
|
+
preview the resolved file. Replacements also apply to `carry:` targets.
|
|
37
|
+
- **Docker `image:` services.** Sidecar containers declared under `image:` now
|
|
38
|
+
take their `ports`/`env` nested under `image:` as well, keeping all
|
|
39
|
+
image-level config in one place.
|
|
40
|
+
- **Codex plugin marketplace.** AgentBox installs as a Codex plugin straight
|
|
41
|
+
from the repo (`codex plugin marketplace add madarco/agentbox`).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- `carry:` and `agentbox cp` copy files via `docker exec tar` instead of
|
|
46
|
+
`docker cp`, fixing "read/write on closed pipe" failures into the
|
|
47
|
+
bind-mounted workspace and relative-path targets (e.g. `./backups/...`).
|
|
48
|
+
- `agentbox doctor` integration probes are time-bounded and stdin-isolated, so
|
|
49
|
+
doctor no longer hangs when a connector's auth check blocks; a timed-out
|
|
50
|
+
probe now reports a timeout rather than "not logged in".
|
|
51
|
+
|
|
52
|
+
### Security
|
|
53
|
+
|
|
54
|
+
- The Notion `ntn api` gate is fail-closed: it refuses any unrecognized flag
|
|
55
|
+
rather than ignoring it, closing a bypass where ntn's value-consuming global
|
|
56
|
+
flags (`--workers-config-file`, `--env`) could shift the real request
|
|
57
|
+
endpoint past the read classification. Host-file (`--file`/`--input`) bodies
|
|
58
|
+
and `.`/`..` path segments are refused.
|
|
59
|
+
|
|
12
60
|
## [0.15.0] - 2026-06-05
|
|
13
61
|
|
|
14
62
|
### Breaking
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
buildCloudAttachInnerCommand,
|
|
4
4
|
cloudAgentAttach,
|
|
5
5
|
cloudAgentStartDetached
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-SB4QTF2T.js";
|
|
7
|
+
import "./chunk-GXJNJUEV.js";
|
|
8
|
+
import "./chunk-DBBUDKKB.js";
|
|
9
9
|
import "./chunk-G3H2L3O2.js";
|
|
10
10
|
export {
|
|
11
11
|
buildCloudAttachInnerCommand,
|
|
12
12
|
cloudAgentAttach,
|
|
13
13
|
cloudAgentStartDetached
|
|
14
14
|
};
|
|
15
|
-
//# sourceMappingURL=_cloud-attach-
|
|
15
|
+
//# sourceMappingURL=_cloud-attach-5KJWOASL.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
readPreparedStateRaw,
|
|
8
8
|
resolveContextFilesFrom,
|
|
9
9
|
writePreparedStateRaw
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DBBUDKKB.js";
|
|
11
11
|
|
|
12
12
|
// ../../packages/sandbox-daytona/dist/chunk-35HJOOGT.js
|
|
13
13
|
import { existsSync } from "fs";
|
|
@@ -830,4 +830,4 @@ export {
|
|
|
830
830
|
writePreparedDaytonaState,
|
|
831
831
|
preparedMatches
|
|
832
832
|
};
|
|
833
|
-
//# sourceMappingURL=chunk-
|
|
833
|
+
//# sourceMappingURL=chunk-3WCEB6RE.js.map
|
|
@@ -1,5 +1,195 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// ../../packages/core/dist/index.js
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
var claudeCodeLauncher = {
|
|
6
|
+
kind: "claude-code",
|
|
7
|
+
// claude treats its first positional argument as the seed user turn in
|
|
8
|
+
// interactive mode (`claude "<message>"`), so we slot the initial message
|
|
9
|
+
// ahead of any user-passed flags.
|
|
10
|
+
buildArgs(initialMessage, userArgs) {
|
|
11
|
+
if (!initialMessage) return [...userArgs];
|
|
12
|
+
return [initialMessage, ...userArgs];
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var codexLauncher = {
|
|
16
|
+
kind: "codex",
|
|
17
|
+
buildArgs(initialMessage, userArgs) {
|
|
18
|
+
if (!initialMessage) return [...userArgs];
|
|
19
|
+
return [initialMessage, ...userArgs];
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var opencodeLauncher = {
|
|
23
|
+
kind: "opencode",
|
|
24
|
+
buildArgs(initialMessage, userArgs) {
|
|
25
|
+
if (!initialMessage) return [...userArgs];
|
|
26
|
+
return [initialMessage, ...userArgs];
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function resolveAgentLauncher(kind) {
|
|
30
|
+
if (kind === "claude-code") return claudeCodeLauncher;
|
|
31
|
+
if (kind === "codex") return codexLauncher;
|
|
32
|
+
if (kind === "opencode") return opencodeLauncher;
|
|
33
|
+
throw new Error(`unknown agent kind: ${String(kind)}`);
|
|
34
|
+
}
|
|
35
|
+
var PLACEHOLDER_KEYS = [
|
|
36
|
+
"AGENTBOX_BOX_NAME",
|
|
37
|
+
"AGENTBOX_BOX_ID",
|
|
38
|
+
"AGENTBOX_BOX_KIND",
|
|
39
|
+
"AGENTBOX_HOST_WORKSPACE",
|
|
40
|
+
"AGENTBOX_PROJECT_ROOT",
|
|
41
|
+
// Convenience: the portless host this box publishes (`<box-name>.localhost`).
|
|
42
|
+
// Derived from AGENTBOX_BOX_NAME when not set explicitly.
|
|
43
|
+
"AGENTBOX_BOX_HOST"
|
|
44
|
+
];
|
|
45
|
+
var PLACEHOLDER_SET = new Set(PLACEHOLDER_KEYS);
|
|
46
|
+
var ReplaceError = class extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "ReplaceError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var PLACEHOLDER_RE = /\{\{\s*([A-Z0-9_]+)\s*\}\}/g;
|
|
53
|
+
function substitutePlaceholders(text, context, onWarn) {
|
|
54
|
+
return text.replace(PLACEHOLDER_RE, (match, name) => {
|
|
55
|
+
if (!PLACEHOLDER_SET.has(name)) return match;
|
|
56
|
+
const value = context[name];
|
|
57
|
+
if (value === void 0) {
|
|
58
|
+
onWarn?.(`placeholder {{${name}}} has no value in this context \u2014 left untouched`);
|
|
59
|
+
return match;
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function applyReplacements(content, opts) {
|
|
65
|
+
let out = content;
|
|
66
|
+
if (opts.env) {
|
|
67
|
+
out = substitutePlaceholders(out, opts.context, opts.onWarn);
|
|
68
|
+
}
|
|
69
|
+
for (const rule of opts.rules ?? []) {
|
|
70
|
+
const to = substitutePlaceholders(rule.to, opts.context, opts.onWarn);
|
|
71
|
+
if (rule.regex) {
|
|
72
|
+
let re;
|
|
73
|
+
try {
|
|
74
|
+
re = new RegExp(rule.from, rule.flags ?? "g");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new ReplaceError(
|
|
77
|
+
`invalid regex "${rule.from}": ${err instanceof Error ? err.message : String(err)}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
out = out.replace(re, to);
|
|
81
|
+
} else {
|
|
82
|
+
out = out.split(rule.from).join(to);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
function deriveBoxHost(ctx) {
|
|
88
|
+
if (ctx.AGENTBOX_BOX_HOST === void 0 && ctx.AGENTBOX_BOX_NAME !== void 0) {
|
|
89
|
+
ctx.AGENTBOX_BOX_HOST = `${ctx.AGENTBOX_BOX_NAME}.localhost`;
|
|
90
|
+
}
|
|
91
|
+
return ctx;
|
|
92
|
+
}
|
|
93
|
+
function isPlainObject(v) {
|
|
94
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
95
|
+
}
|
|
96
|
+
var RULE_KEYS = /* @__PURE__ */ new Set(["from", "to", "regex", "flags"]);
|
|
97
|
+
function parseReplaceRule(raw, where) {
|
|
98
|
+
if (!isPlainObject(raw)) {
|
|
99
|
+
throw new ReplaceError(`${where} must be a mapping with at least { from, to }`);
|
|
100
|
+
}
|
|
101
|
+
for (const key of Object.keys(raw)) {
|
|
102
|
+
if (!RULE_KEYS.has(key)) throw new ReplaceError(`${where} has unknown key "${key}"`);
|
|
103
|
+
}
|
|
104
|
+
if (typeof raw.from !== "string" || raw.from.length === 0) {
|
|
105
|
+
throw new ReplaceError(`${where}.from must be a non-empty string`);
|
|
106
|
+
}
|
|
107
|
+
if (typeof raw.to !== "string") {
|
|
108
|
+
throw new ReplaceError(`${where}.to must be a string`);
|
|
109
|
+
}
|
|
110
|
+
const rule = { from: raw.from, to: raw.to };
|
|
111
|
+
if (raw.regex !== void 0 && raw.regex !== null) {
|
|
112
|
+
if (typeof raw.regex !== "boolean") throw new ReplaceError(`${where}.regex must be a boolean`);
|
|
113
|
+
rule.regex = raw.regex;
|
|
114
|
+
}
|
|
115
|
+
if (raw.flags !== void 0 && raw.flags !== null) {
|
|
116
|
+
if (typeof raw.flags !== "string") throw new ReplaceError(`${where}.flags must be a string`);
|
|
117
|
+
rule.flags = raw.flags;
|
|
118
|
+
}
|
|
119
|
+
if (rule.regex) {
|
|
120
|
+
try {
|
|
121
|
+
new RegExp(rule.from, rule.flags ?? "g");
|
|
122
|
+
} catch (err) {
|
|
123
|
+
throw new ReplaceError(
|
|
124
|
+
`${where}.from is not a valid regex: ${err instanceof Error ? err.message : String(err)}`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return rule;
|
|
129
|
+
}
|
|
130
|
+
function parseReplaceRules(raw, where) {
|
|
131
|
+
if (raw === void 0 || raw === null) return [];
|
|
132
|
+
if (!Array.isArray(raw)) throw new ReplaceError(`${where} must be a list of rules`);
|
|
133
|
+
return raw.map((r, i) => parseReplaceRule(r, `${where}[${String(i)}]`));
|
|
134
|
+
}
|
|
135
|
+
function parseReplacements(raw) {
|
|
136
|
+
if (raw === void 0 || raw === null) return {};
|
|
137
|
+
if (!isPlainObject(raw)) {
|
|
138
|
+
throw new ReplaceError("replacements must be a mapping of name \u2192 rule list");
|
|
139
|
+
}
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const [name, rules] of Object.entries(raw)) {
|
|
142
|
+
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
143
|
+
throw new ReplaceError(`replacements.${name}: name must match [A-Za-z0-9_-]+`);
|
|
144
|
+
}
|
|
145
|
+
out[name] = parseReplaceRules(rules, `replacements.${name}`);
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function resolveRuleRefs(refs, replacements, where) {
|
|
150
|
+
const out = [];
|
|
151
|
+
for (const name of refs) {
|
|
152
|
+
const set = replacements[name];
|
|
153
|
+
if (set === void 0) {
|
|
154
|
+
const known = Object.keys(replacements);
|
|
155
|
+
throw new ReplaceError(
|
|
156
|
+
`${where}: unknown replacements rule-set "${name}"` + (known.length > 0 ? ` (known: ${known.join(", ")})` : " (none declared)")
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
out.push(...set);
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
var UserFacingError = class extends Error {
|
|
164
|
+
constructor(message) {
|
|
165
|
+
super(message);
|
|
166
|
+
this.name = "UserFacingError";
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var BoxNotFoundError = class extends Error {
|
|
170
|
+
constructor(query) {
|
|
171
|
+
super(`no agentbox matches "${query}"`);
|
|
172
|
+
this.query = query;
|
|
173
|
+
this.name = "BoxNotFoundError";
|
|
174
|
+
}
|
|
175
|
+
query;
|
|
176
|
+
};
|
|
177
|
+
var AmbiguousBoxError = class extends Error {
|
|
178
|
+
constructor(query, matches) {
|
|
179
|
+
const ids = matches.map((m) => m.id).join(", ");
|
|
180
|
+
super(`"${query}" matches multiple boxes: ${ids}`);
|
|
181
|
+
this.query = query;
|
|
182
|
+
this.matches = matches;
|
|
183
|
+
this.name = "AmbiguousBoxError";
|
|
184
|
+
}
|
|
185
|
+
query;
|
|
186
|
+
matches;
|
|
187
|
+
};
|
|
188
|
+
var BOX_ID_PREFIX = "b";
|
|
189
|
+
function generateBoxId() {
|
|
190
|
+
return `${BOX_ID_PREFIX}${randomBytes(4).toString("hex")}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
3
193
|
// ../../packages/sandbox-core/dist/index.js
|
|
4
194
|
import { mkdir, open, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
5
195
|
import { homedir } from "os";
|
|
@@ -8,9 +198,12 @@ import { setTimeout as delay } from "timers/promises";
|
|
|
8
198
|
import { execa } from "execa";
|
|
9
199
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
10
200
|
import { join as join2 } from "path";
|
|
201
|
+
import { mkdtemp, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
202
|
+
import { tmpdir } from "os";
|
|
203
|
+
import { basename, join as join3 } from "path";
|
|
11
204
|
import { createHash } from "crypto";
|
|
12
205
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
13
|
-
import { readFile as
|
|
206
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
14
207
|
import { homedir as homedir2 } from "os";
|
|
15
208
|
import { dirname as dirname2, resolve as pathResolve } from "path";
|
|
16
209
|
var STATE_DIR = join(homedir(), ".agentbox");
|
|
@@ -238,6 +431,46 @@ var GitWorktreeError = class extends Error {
|
|
|
238
431
|
function hostOpenCommand() {
|
|
239
432
|
return process.platform === "linux" ? "xdg-open" : "open";
|
|
240
433
|
}
|
|
434
|
+
function carryPlaceholderContext(ctx) {
|
|
435
|
+
const out = {};
|
|
436
|
+
if (ctx.name) out.AGENTBOX_BOX_NAME = ctx.name;
|
|
437
|
+
if (ctx.id) out.AGENTBOX_BOX_ID = ctx.id;
|
|
438
|
+
if (ctx.kind) out.AGENTBOX_BOX_KIND = ctx.kind;
|
|
439
|
+
if (ctx.hostWorkspace) out.AGENTBOX_HOST_WORKSPACE = ctx.hostWorkspace;
|
|
440
|
+
if (ctx.projectRoot) out.AGENTBOX_PROJECT_ROOT = ctx.projectRoot;
|
|
441
|
+
return deriveBoxHost(out);
|
|
442
|
+
}
|
|
443
|
+
function wantsRender(e) {
|
|
444
|
+
return e.kind === "file" && (!!e.replaceEnvs || (e.replace?.length ?? 0) > 0);
|
|
445
|
+
}
|
|
446
|
+
async function renderCarryEntries(entries, ctx, onLog) {
|
|
447
|
+
if (!entries.some(wantsRender)) return entries;
|
|
448
|
+
const context = carryPlaceholderContext(ctx);
|
|
449
|
+
const stage = await mkdtemp(join3(tmpdir(), "agentbox-carry-render-"));
|
|
450
|
+
const out = [];
|
|
451
|
+
for (const [i, entry] of entries.entries()) {
|
|
452
|
+
if (!wantsRender(entry)) {
|
|
453
|
+
out.push(entry);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const content = await readFile2(entry.absSrc, "utf8");
|
|
457
|
+
const rendered = applyReplacements(content, {
|
|
458
|
+
env: entry.replaceEnvs,
|
|
459
|
+
rules: entry.replace,
|
|
460
|
+
context,
|
|
461
|
+
onWarn: (msg) => onLog?.(`carry: ${entry.rawSrc}: ${msg}`)
|
|
462
|
+
});
|
|
463
|
+
const tmp = join3(stage, `${String(i)}-${basename(entry.absSrc)}`);
|
|
464
|
+
await writeFile2(tmp, rendered, "utf8");
|
|
465
|
+
out.push({ ...entry, absSrc: tmp, bytes: Buffer.byteLength(rendered) });
|
|
466
|
+
const what = [
|
|
467
|
+
...entry.replaceEnvs ? ["env"] : [],
|
|
468
|
+
...entry.replace?.length ? [`${String(entry.replace.length)} rule(s)`] : []
|
|
469
|
+
].join("+");
|
|
470
|
+
onLog?.(`carry: rendered ${entry.rawSrc} (${what})`);
|
|
471
|
+
}
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
241
474
|
function preparedStatePathFor(provider) {
|
|
242
475
|
return pathResolve(homedir2(), ".agentbox", `${provider}-prepared.json`);
|
|
243
476
|
}
|
|
@@ -259,7 +492,7 @@ function writePreparedStateRaw(provider, state) {
|
|
|
259
492
|
renameSync(tmp, path);
|
|
260
493
|
}
|
|
261
494
|
async function sha256OfFile(path) {
|
|
262
|
-
const buf = await
|
|
495
|
+
const buf = await readFile3(path);
|
|
263
496
|
return createHash("sha256").update(buf).digest("hex");
|
|
264
497
|
}
|
|
265
498
|
async function computeContextSha256(files) {
|
|
@@ -430,7 +663,7 @@ async function buildImage(opts = {}) {
|
|
|
430
663
|
return ref;
|
|
431
664
|
}
|
|
432
665
|
async function pullOrBuild(ref, fingerprint, opts = {}) {
|
|
433
|
-
const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-
|
|
666
|
+
const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-2LANTRL7.js");
|
|
434
667
|
const registry = opts.registry ?? BOX_IMAGE_REGISTRY;
|
|
435
668
|
const allowPull = opts.allowPull !== false;
|
|
436
669
|
if (allowPull && registry && fingerprint) {
|
|
@@ -456,7 +689,7 @@ async function pullOrBuild(ref, fingerprint, opts = {}) {
|
|
|
456
689
|
return { source: "built" };
|
|
457
690
|
}
|
|
458
691
|
async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
|
|
459
|
-
const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-
|
|
692
|
+
const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-2LANTRL7.js");
|
|
460
693
|
const fingerprint = await computeDockerContextFingerprint2({
|
|
461
694
|
contextDir: opts.contextDir
|
|
462
695
|
});
|
|
@@ -526,6 +759,15 @@ function preparedMatches(state, current) {
|
|
|
526
759
|
}
|
|
527
760
|
|
|
528
761
|
export {
|
|
762
|
+
resolveAgentLauncher,
|
|
763
|
+
ReplaceError,
|
|
764
|
+
parseReplaceRules,
|
|
765
|
+
parseReplacements,
|
|
766
|
+
resolveRuleRefs,
|
|
767
|
+
UserFacingError,
|
|
768
|
+
BoxNotFoundError,
|
|
769
|
+
AmbiguousBoxError,
|
|
770
|
+
generateBoxId,
|
|
529
771
|
STATE_DIR,
|
|
530
772
|
STATE_FILE,
|
|
531
773
|
readState,
|
|
@@ -540,6 +782,7 @@ export {
|
|
|
540
782
|
pickFreshBranch,
|
|
541
783
|
GitWorktreeError,
|
|
542
784
|
hostOpenCommand,
|
|
785
|
+
renderCarryEntries,
|
|
543
786
|
preparedStatePathFor,
|
|
544
787
|
readPreparedStateRaw,
|
|
545
788
|
writePreparedStateRaw,
|
|
@@ -563,4 +806,4 @@ export {
|
|
|
563
806
|
writePreparedDockerState,
|
|
564
807
|
preparedMatches
|
|
565
808
|
};
|
|
566
|
-
//# sourceMappingURL=chunk-
|
|
809
|
+
//# sourceMappingURL=chunk-DBBUDKKB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/core/src/agent.ts","../../../packages/core/src/box-record.ts","../../../packages/core/src/replace.ts","../../../packages/core/src/errors.ts","../../../packages/core/src/identity.ts","../../../packages/sandbox-core/src/state.ts","../../../packages/sandbox-core/src/git-detect.ts","../../../packages/sandbox-core/src/host-open.ts","../../../packages/sandbox-core/src/carry-render.ts","../../../packages/sandbox-core/src/prepared-state.ts","../../../packages/sandbox-docker/src/prepared-state.ts","../../../packages/sandbox-docker/src/image.ts"],"sourcesContent":["import type { AgentKind } from './types.js';\n\nexport interface AgentLauncher {\n readonly kind: AgentKind;\n buildArgs(initialMessage: string, userArgs: string[]): string[];\n}\n\nconst claudeCodeLauncher: AgentLauncher = {\n kind: 'claude-code',\n // claude treats its first positional argument as the seed user turn in\n // interactive mode (`claude \"<message>\"`), so we slot the initial message\n // ahead of any user-passed flags.\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\n// codex accepts a leading positional as the seed prompt — `codex \"<message>\"`\n// drops the user into the TUI with that turn pre-submitted. Same shape as\n// claude, so the launcher is structurally identical.\nconst codexLauncher: AgentLauncher = {\n kind: 'codex',\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\n// opencode also accepts a leading positional as the seed prompt — `opencode\n// \"<message>\"` enters the TUI with that turn pre-submitted. Mirrors claude/codex.\nconst opencodeLauncher: AgentLauncher = {\n kind: 'opencode',\n buildArgs(initialMessage, userArgs) {\n if (!initialMessage) return [...userArgs];\n return [initialMessage, ...userArgs];\n },\n};\n\nexport function resolveAgentLauncher(kind: AgentKind): AgentLauncher {\n if (kind === 'claude-code') return claudeCodeLauncher;\n if (kind === 'codex') return codexLauncher;\n if (kind === 'opencode') return opencodeLauncher;\n throw new Error(`unknown agent kind: ${String(kind)}`);\n}\n","/**\n * The per-box state record persisted to `~/.agentbox/state.json`. Shared by\n * every provider: a box may be a local Docker container or a remote cloud\n * sandbox. The `provider` discriminator says which; provider-specific fields\n * live flat (Docker, for historical reasons) or under `cloud` (cloud backends).\n */\n\nimport type { BoxRuntimeState } from './provider.js';\n\n/** Sandbox backend a box runs on. Open-ended so future providers need no core change. */\nexport type ProviderName = 'docker' | 'daytona' | 'hetzner' | (string & {});\n\n/**\n * Docker-backend-specific fields nested under `BoxRecord.docker` once 7.1\n * lands fully. Today every Docker box also keeps the flat copies of these\n * fields on `BoxRecord` directly for back-compat (state.json migration is\n * the rest of 7.1) — write sites populate both shapes; read sites still\n * use the flat fields. New code should target this interface so the\n * eventual flat-field removal is a search-and-replace, not a redesign.\n */\nexport interface DockerBoxFields {\n /** Docker container name (`agentbox-<id|name>`). */\n container: string;\n /** Base image / checkpoint image tag the container was started from. */\n image: string;\n /** Per-box scratch dir with the `cp -c` APFS clone (when --host-snapshot is on). */\n snapshotDir?: string | null;\n /** Host-side path to the agentbox-ctl unix socket bind-mounted into the container. */\n socketPath?: string;\n /** Docker volume mounted at /home/vscode/.claude inside the box. */\n claudeConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.codex inside the box. */\n codexConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.local/share/opencode. */\n opencodeConfigVolume?: string;\n /** Per-box volume holding `.vscode-server`. */\n vscodeServerVolume?: string;\n /** Per-box volume holding `.cursor-server`. */\n cursorServerVolume?: string;\n /** Host port mapped to the noVNC web server. */\n vncHostPort?: number;\n /** Host port mapped to container :80. */\n webHostPort?: number;\n /** Portless route name registered for this box's web port. */\n portlessAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box. */\n portlessUrl?: string;\n /** Portless route name registered for this box's noVNC port (`vnc-<box-name>`). */\n portlessVncAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box's VNC. */\n portlessVncUrl?: string;\n /** Volume mounted at /var/lib/docker for the in-box dockerd. */\n dockerVolume?: string;\n /** True when this box's `dockerVolume` is the shared cache. */\n dockerCacheShared?: boolean;\n /** Checkpoint image tag this box was started from. */\n checkpointImage?: string;\n}\n\n/**\n * Cloud-backend-specific fields for a box. Populated only when\n * `BoxRecord.provider` is a cloud provider; `undefined` for Docker boxes.\n */\nexport interface CloudBoxFields {\n /** Cloud backend name, e.g. 'daytona'. Mirrors `BoxRecord.provider`. */\n backend: string;\n /** Provider-native sandbox id (the handle the backend SDK resolves). */\n sandboxId: string;\n /** Resolved base image / snapshot ref the sandbox was provisioned from. */\n image?: string;\n /**\n * In-box port the web service is exposed on (the supervisor's WebProxy\n * listen port). Cloud boxes bind a non-privileged port; Docker boxes use 80.\n */\n webPort?: number;\n /**\n * Token-authed preview URLs keyed by in-box port. Resolved at create and\n * refreshed on every start (preview tokens can rotate across stop/start).\n */\n previewUrls?: Record<number, string>;\n /**\n * Preview URL of the in-sandbox relay's `/bridge/*` surface — the channel the\n * host CloudBoxPoller drains. Refreshed on start alongside `previewUrls`.\n */\n relayPreviewUrl?: string;\n /**\n * Daytona-style preview-proxy token for `relayPreviewUrl` (sent as\n * `x-daytona-preview-token` header). Persisted so a relay restart can\n * rehydrate the poller without re-resolving from the SDK.\n */\n relayPreviewToken?: string;\n /**\n * Bearer secret authenticating the host poller to the in-sandbox relay's\n * `/bridge/*` routes. Distinct from `BoxRecord.relayToken` (the per-box token\n * the in-box agent sees) so a compromised agent cannot impersonate the host.\n */\n bridgeToken?: string;\n /**\n * User-facing checkpoint name this box was provisioned from (e.g. `setup`),\n * when `agentbox create --checkpoint <name>` resolved to a cloud snapshot.\n * Surfaces in `agentbox status --inspect` so the user can tell which\n * checkpoint a box is currently running.\n */\n snapshotRef?: string;\n /**\n * Last lifecycle state agentbox itself drove this box to (running on\n * create/start/resume, paused on pause/stop/vercel-checkpoint). `listBoxes`\n * shows this for cloud boxes so `agentbox list` stays instant — no SDK probe.\n * It reflects the last *host-initiated* op, not the authoritative live state\n * (a platform-side stop won't update it); `agentbox list --live` probes for\n * the real state. Absent on pre-feature records → treated as `running`.\n */\n lastState?: BoxRuntimeState;\n}\n\nexport interface GitWorktreeRecord {\n kind: 'root' | 'nested';\n /** Host path to the main repo whose `.git/` is the source of the worktree. */\n hostMainRepo: string;\n /**\n * Agent-visible container path of the worktree (`/workspace` for root,\n * `/workspace/<sub>` for nested).\n */\n containerPath: string;\n /**\n * Per-box unique path where git registered the worktree. Docker boxes\n * register against the bind-mounted host `.git/`; cloud boxes clone into the\n * sandbox so this is a sandbox-local path. `destroyBox` uses it to deregister\n * a Docker worktree on the host.\n */\n gitWorktreePath: string;\n /** Branch the worktree was created on, e.g. `agentbox/<box-name>`. */\n branch: string;\n /** Workspace-relative path the repo was found at (empty string for root). */\n relPathFromWorkspace: string;\n}\n\nexport interface BoxRecord {\n id: string;\n name: string;\n /**\n * Sandbox backend the box runs on. Absent on records written before the\n * multi-provider split — `readState` migrates those to `'docker'` on read.\n */\n provider?: ProviderName;\n /**\n * Unique runtime identifier. For docker records: the container name\n * (`agentbox-<id|name>`) — what `docker exec` resolves. For cloud\n * records: the backend sandbox id, prefixed with `cloud:` so a grep\n * for \"agentbox-cloud-*\" finds nothing (post 7.2) — the cloud\n * sandbox is not a docker container and shouldn't look like one.\n * Docker-internal code should only ever reach this via\n * `requireDockerProvider(box, ...)` first.\n */\n container: string;\n /**\n * The image the box was started from. Docker: base image or checkpoint\n * image tag. Cloud: the resolved sandbox image / snapshot ref (mirrors\n * `box.cloud.image`).\n */\n image: string;\n workspacePath: string;\n /**\n * Optional per-box scratch dir holding a `cp -c` APFS clone of the host\n * workspace, made at create time when `--host-snapshot` is on. Docker only.\n */\n snapshotDir?: string | null;\n /**\n * Host-side path to the agentbox-ctl unix socket bind-mounted into the\n * container at /run/agentbox/ctl.sock. Docker only.\n */\n socketPath?: string;\n /** Docker volume mounted at /home/vscode/.claude inside the box. Docker only. */\n claudeConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.codex inside the box. Docker only. */\n codexConfigVolume?: string;\n /** Docker volume mounted at /home/vscode/.local/share/opencode. Docker only. */\n opencodeConfigVolume?: string;\n /** Per-box volume holding `.vscode-server`. Docker only. */\n vscodeServerVolume?: string;\n /** Per-box volume holding `.cursor-server`. Docker only. */\n cursorServerVolume?: string;\n /**\n * Bearer token the in-box supervisor uses to authenticate with the relay.\n * Generated at create time and forwarded as AGENTBOX_RELAY_TOKEN.\n */\n relayToken?: string;\n /** Per-box git worktrees. Empty/absent when the host workspace is not a git checkout. */\n gitWorktrees?: GitWorktreeRecord[];\n /** True when the box was created with --with-playwright. */\n withPlaywright?: boolean;\n /** True when the box was created with --with-env. */\n withEnv?: boolean;\n /**\n * Resolved `box.autoApproveHostActions` at create time. Forwarded to the\n * host relay so host-action confirms (git push, cp, gh writes, checkpoint)\n * auto-resolve to `y` for this box — with an audit event per bypass.\n * Persisted so a `relay` rehydrate re-registers with the same policy.\n */\n autoApproveHostActions?: boolean;\n /**\n * Carry summary recorded at create time: which host paths were copied into\n * the box from `agentbox.yaml`'s `carry:` block. Audit trail for inspect\n * (the actual file content is not retained — only the src/dest pairs and\n * sizes). Absent when no carry: block was applied.\n */\n carry?: {\n count: number;\n /**\n * `hash` is a content hash of the host source at copy time, used by the\n * on-start resync to re-copy only entries whose host source changed.\n * Absent on records written before resync existed (treated as \"changed\").\n */\n entries: Array<{ src: string; dest: string; bytes: number; hash?: string }>;\n };\n /** VNC stack (Xvnc + websockify + noVNC) is enabled for this box. */\n vncEnabled?: boolean;\n /** Container-side noVNC web port. */\n vncContainerPort?: number;\n /** Host port mapped to the noVNC web server (Docker), or preview port (cloud). */\n vncHostPort?: number;\n /** Per-box password baked into Xvnc's PasswordFile and the auto-connect URL. */\n vncPassword?: string;\n /** Container port reserved for the web service `expose:` forward. */\n webContainerPort?: number;\n /** Host port mapped to container :80 (Docker). */\n webHostPort?: number;\n /** Portless route name registered for this box's web port. Docker only. */\n portlessAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box. Docker only. */\n portlessUrl?: string;\n /** Portless route name registered for this box's noVNC port (`vnc-<box-name>`). */\n portlessVncAlias?: string;\n /** Full user-facing URL the Portless proxy serves for this box's VNC. */\n portlessVncUrl?: string;\n /** Volume mounted at /var/lib/docker for the in-box dockerd. Docker only. */\n dockerVolume?: string;\n /** True when this box's `dockerVolume` is the shared cache. Docker only. */\n dockerCacheShared?: boolean;\n /** Absolute host path of the project this box belongs to. */\n projectRoot?: string;\n /** Monotonic 1-based index within `projectRoot`. Never recycled. */\n projectIndex?: number;\n /** The checkpoint image tag this box was started from. Docker only. */\n checkpointImage?: string;\n /** Lineage of the checkpoint this box was started from. */\n checkpointSource?: {\n ref: string;\n type: 'layered' | 'flattened';\n /** Checkpoint refs composing the chain, base-most last. */\n chain: string[];\n };\n /** Resource ceilings actually applied at create. */\n resourceLimits?: {\n memoryBytes?: number;\n cpus?: number;\n pidsLimit?: number;\n disk?: string;\n };\n /**\n * Docker-backend-specific fields. Populated alongside the flat fields\n * during the 7.1 transition; once readers migrate, the flat fields go\n * away. New code SHOULD prefer `box.docker?.<field>` and fall back to\n * the flat field via the `dockerField()` helper in `@agentbox/core`.\n */\n docker?: DockerBoxFields;\n /** Cloud-backend-specific fields. Present only for cloud providers. */\n cloud?: CloudBoxFields;\n createdAt: string; // ISO-8601\n}\n\n/**\n * Read a Docker-specific field with fallback to the legacy flat slot.\n * Prefer this over `box.field` directly while the 7.1 migration is in\n * flight — once the flat fields are removed, this collapses to\n * `box.docker?.[key]` and the migration is mechanical.\n */\nexport function dockerField<K extends keyof DockerBoxFields>(\n box: BoxRecord,\n key: K,\n): DockerBoxFields[K] | undefined {\n if (box.docker && box.docker[key] !== undefined) return box.docker[key];\n // Fall back to the flat copy. Container / image are required at the\n // BoxRecord top level so they always exist as flat fields today.\n return (box as unknown as Record<string, unknown>)[key as string] as\n | DockerBoxFields[K]\n | undefined;\n}\n\nexport interface StateFile {\n version: 1;\n boxes: BoxRecord[];\n}\n\nexport type FindBoxResult =\n | { kind: 'ok'; box: BoxRecord }\n | { kind: 'none' }\n | { kind: 'ambiguous'; matches: BoxRecord[] };\n","/**\n * Provider-neutral text-replacement engine. Pure (no fs / no yaml) so it can be\n * shared by the host carry path (`renderCarryEntries`) and the in-box\n * `agentbox-ctl render` CLI without a dependency cycle. The yaml/fs loaders that\n * read a `replacements:` block live in `@agentbox/ctl` (which has those deps).\n */\n\n/**\n * The fixed set of `{{NAME}}` placeholders that `replaceEnvs` / `--env`\n * substitution recognizes. Deliberately a whitelist (not \"any env var\") so a\n * rendered file is predictable: a stray `{{FOO}}` is left untouched rather than\n * silently clobbered, and secrets/tokens are never substitutable.\n */\nexport const PLACEHOLDER_KEYS = [\n 'AGENTBOX_BOX_NAME',\n 'AGENTBOX_BOX_ID',\n 'AGENTBOX_BOX_KIND',\n 'AGENTBOX_HOST_WORKSPACE',\n 'AGENTBOX_PROJECT_ROOT',\n // Convenience: the portless host this box publishes (`<box-name>.localhost`).\n // Derived from AGENTBOX_BOX_NAME when not set explicitly.\n 'AGENTBOX_BOX_HOST',\n] as const;\n\nexport type PlaceholderKey = (typeof PLACEHOLDER_KEYS)[number];\n\nconst PLACEHOLDER_SET = new Set<string>(PLACEHOLDER_KEYS);\n\n/** A single regex/literal substitution. `to` may itself contain placeholders. */\nexport interface ReplaceRule {\n from: string;\n to: string;\n /** Treat `from` as a JS regex (with `flags`); otherwise a literal string. */\n regex?: boolean;\n /** Regex flags (default `g`). Ignored unless `regex` is true. */\n flags?: string;\n}\n\nexport class ReplaceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReplaceError';\n }\n}\n\nconst PLACEHOLDER_RE = /\\{\\{\\s*([A-Z0-9_]+)\\s*\\}\\}/g;\n\n/**\n * Replace `{{NAME}}` placeholders whose NAME is in {@link PLACEHOLDER_KEYS}\n * with the matching value from `context`. Unknown names (not whitelisted) are\n * left as-is. Whitelisted names with no value in `context` are also left as-is\n * (and reported via `onWarn`).\n */\nexport function substitutePlaceholders(\n text: string,\n context: Record<string, string>,\n onWarn?: (msg: string) => void,\n): string {\n return text.replace(PLACEHOLDER_RE, (match, name: string) => {\n if (!PLACEHOLDER_SET.has(name)) return match;\n const value = context[name];\n if (value === undefined) {\n onWarn?.(`placeholder {{${name}}} has no value in this context — left untouched`);\n return match;\n }\n return value;\n });\n}\n\nexport interface ApplyReplacementsOptions {\n /** When true, substitute `{{NAME}}` whitelist placeholders across the file. */\n env?: boolean;\n /** Ordered custom rules applied after (or instead of) placeholder env subst. */\n rules?: ReplaceRule[];\n /** Placeholder values (used by both `env` subst and rule `to` strings). */\n context: Record<string, string>;\n onWarn?: (msg: string) => void;\n}\n\n/**\n * Apply env-placeholder substitution and/or custom rules to file content.\n * Rules run in declaration order; each rule's `to` string is itself run through\n * placeholder substitution so `to: '.{{AGENTBOX_BOX_NAME}}.localhost'` works\n * regardless of `env`.\n */\nexport function applyReplacements(content: string, opts: ApplyReplacementsOptions): string {\n let out = content;\n if (opts.env) {\n out = substitutePlaceholders(out, opts.context, opts.onWarn);\n }\n for (const rule of opts.rules ?? []) {\n const to = substitutePlaceholders(rule.to, opts.context, opts.onWarn);\n if (rule.regex) {\n let re: RegExp;\n try {\n re = new RegExp(rule.from, rule.flags ?? 'g');\n } catch (err) {\n throw new ReplaceError(\n `invalid regex \"${rule.from}\": ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n out = out.replace(re, to);\n } else {\n // Literal: split/join so `$`/special chars in either side stay literal.\n out = out.split(rule.from).join(to);\n }\n }\n return out;\n}\n\n/**\n * Fill in `AGENTBOX_BOX_HOST` (the published portless host, `<box-name>.localhost`)\n * from `AGENTBOX_BOX_NAME` when it isn't already set. Single source of truth for\n * the host-derivation rule, shared by every placeholder-context builder. Mutates\n * and returns `ctx`.\n */\nexport function deriveBoxHost(ctx: Record<string, string>): Record<string, string> {\n if (ctx.AGENTBOX_BOX_HOST === undefined && ctx.AGENTBOX_BOX_NAME !== undefined) {\n ctx.AGENTBOX_BOX_HOST = `${ctx.AGENTBOX_BOX_NAME}.localhost`;\n }\n return ctx;\n}\n\n/** Build the whitelist placeholder context from a process environment. */\nexport function placeholderContextFromEnv(\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n const ctx: Record<string, string> = {};\n for (const key of PLACEHOLDER_KEYS) {\n const v = env[key];\n if (typeof v === 'string' && v.length > 0) ctx[key] = v;\n }\n return deriveBoxHost(ctx);\n}\n\n// --- rule parsing (shared by config top-level `replacements:` and the CLI) ---\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nconst RULE_KEYS = new Set(['from', 'to', 'regex', 'flags']);\n\n/** Parse one rule mapping (from `replacements:` blocks or carry `replace:`). */\nexport function parseReplaceRule(raw: unknown, where: string): ReplaceRule {\n if (!isPlainObject(raw)) {\n throw new ReplaceError(`${where} must be a mapping with at least { from, to }`);\n }\n for (const key of Object.keys(raw)) {\n if (!RULE_KEYS.has(key)) throw new ReplaceError(`${where} has unknown key \"${key}\"`);\n }\n if (typeof raw.from !== 'string' || raw.from.length === 0) {\n throw new ReplaceError(`${where}.from must be a non-empty string`);\n }\n if (typeof raw.to !== 'string') {\n throw new ReplaceError(`${where}.to must be a string`);\n }\n const rule: ReplaceRule = { from: raw.from, to: raw.to };\n if (raw.regex !== undefined && raw.regex !== null) {\n if (typeof raw.regex !== 'boolean') throw new ReplaceError(`${where}.regex must be a boolean`);\n rule.regex = raw.regex;\n }\n if (raw.flags !== undefined && raw.flags !== null) {\n if (typeof raw.flags !== 'string') throw new ReplaceError(`${where}.flags must be a string`);\n rule.flags = raw.flags;\n }\n if (rule.regex) {\n try {\n new RegExp(rule.from, rule.flags ?? 'g');\n } catch (err) {\n throw new ReplaceError(\n `${where}.from is not a valid regex: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n return rule;\n}\n\n/** Parse a list of rules (carry `replace:` or a `replacements:` named set). */\nexport function parseReplaceRules(raw: unknown, where: string): ReplaceRule[] {\n if (raw === undefined || raw === null) return [];\n if (!Array.isArray(raw)) throw new ReplaceError(`${where} must be a list of rules`);\n return raw.map((r, i) => parseReplaceRule(r, `${where}[${String(i)}]`));\n}\n\n/** Parse the top-level `replacements:` block: name → rule list. */\nexport function parseReplacements(raw: unknown): Record<string, ReplaceRule[]> {\n if (raw === undefined || raw === null) return {};\n if (!isPlainObject(raw)) {\n throw new ReplaceError('replacements must be a mapping of name → rule list');\n }\n const out: Record<string, ReplaceRule[]> = {};\n for (const [name, rules] of Object.entries(raw)) {\n if (!/^[A-Za-z0-9_-]+$/.test(name)) {\n throw new ReplaceError(`replacements.${name}: name must match [A-Za-z0-9_-]+`);\n }\n out[name] = parseReplaceRules(rules, `replacements.${name}`);\n }\n return out;\n}\n\n/**\n * Resolve a list of named rule-set references against a `replacements:` map,\n * concatenating their rules in reference order. Throws on an unknown name.\n */\nexport function resolveRuleRefs(\n refs: string[],\n replacements: Record<string, ReplaceRule[]>,\n where: string,\n): ReplaceRule[] {\n const out: ReplaceRule[] = [];\n for (const name of refs) {\n const set = replacements[name];\n if (set === undefined) {\n const known = Object.keys(replacements);\n throw new ReplaceError(\n `${where}: unknown replacements rule-set \"${name}\"` +\n (known.length > 0 ? ` (known: ${known.join(', ')})` : ' (none declared)'),\n );\n }\n out.push(...set);\n }\n return out;\n}\n\n/** Parse a CLI `--rule 'from=>to'` argument into a rule. `regex` opt-in. */\nexport function parseRuleArg(arg: string, regex: boolean): ReplaceRule {\n const idx = arg.indexOf('=>');\n if (idx === -1) {\n throw new ReplaceError(`--rule \"${arg}\" must be of the form 'from=>to'`);\n }\n const from = arg.slice(0, idx);\n const to = arg.slice(idx + 2);\n if (from.length === 0) throw new ReplaceError(`--rule \"${arg}\" has an empty 'from'`);\n return parseReplaceRule({ from, to, ...(regex ? { regex: true } : {}) }, `--rule \"${arg}\"`);\n}\n","/**\n * Provider-neutral box-resolution errors. Thrown by the box-ref resolver and\n * caught by the CLI's lifecycle error handler. They carry the query (and, for\n * the ambiguous case, the candidate records) so the CLI can render a useful\n * hint.\n */\n\nimport type { BoxRecord } from './box-record.js';\n\n/**\n * Marker class for expected, actionable failures that the CLI should render\n * to the user as a clean message (no stack trace) — e.g. \"you skipped a\n * required `agentbox prepare` step\". The top-level CLI catch in\n * `apps/cli/src/index.ts` detects this via `instanceof` and falls back to the\n * `name` field so bundling / dual-publish boundaries can't lose the marker.\n */\nexport class UserFacingError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'UserFacingError';\n }\n}\n\nexport class BoxNotFoundError extends Error {\n constructor(public readonly query: string) {\n super(`no agentbox matches \"${query}\"`);\n this.name = 'BoxNotFoundError';\n }\n}\n\nexport class AmbiguousBoxError extends Error {\n constructor(\n public readonly query: string,\n public readonly matches: BoxRecord[],\n ) {\n const ids = matches.map((m) => m.id).join(', ');\n super(`\"${query}\" matches multiple boxes: ${ids}`);\n this.name = 'AmbiguousBoxError';\n }\n}\n","import { randomBytes } from 'node:crypto';\n\n/**\n * Stable identity helpers shared across providers. Currently just the per-box\n * id mint; checkpoint ids etc. can join later under their own prefix.\n */\n\n/**\n * Type tag prefixed to every minted box id so the kind is readable at a glance\n * and id namespaces can grow without colliding (reserve `c` for checkpoints,\n * etc.). The leading non-digit is load-bearing: it guarantees a box id is never\n * an all-decimal string, which would otherwise collide with the per-project\n * numeric index in `resolveBoxRef` (`agentbox open 4`) and make all-digit ids\n * unresolvable.\n */\nexport const BOX_ID_PREFIX = 'b';\n\nexport function generateBoxId(): string {\n return `${BOX_ID_PREFIX}${randomBytes(4).toString('hex')}`;\n}\n","import { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport type { BoxRecord, DockerBoxFields, FindBoxResult, StateFile } from '@agentbox/core';\n\nexport const STATE_DIR = join(homedir(), '.agentbox');\nexport const STATE_FILE = join(STATE_DIR, 'state.json');\n\nconst EMPTY: StateFile = { version: 1, boxes: [] };\n\n// Cross-process lock tunables. The lock guards the read-modify-write of\n// `state.json` so concurrent `agentbox create`/`destroy` processes can't lose\n// each other's records or interleave a half-written file. Held only for the\n// duration of one read+write (sub-millisecond), so contention clears fast even\n// with a burst of parallel creates.\nconst LOCK_STALE_MS = 15_000; // a lock older than this is presumed abandoned\nconst LOCK_ACQUIRE_TIMEOUT_MS = 20_000;\nconst LOCK_RETRY_MS = 25;\n\n/**\n * Run `fn` while holding an exclusive cross-process lock on `${path}.lock`.\n *\n * Acquisition: create the lockfile with `wx` (O_EXCL) — atomic on local FSes.\n * On contention, retry with a short backoff until {@link LOCK_ACQUIRE_TIMEOUT_MS};\n * a lockfile older than {@link LOCK_STALE_MS} is treated as abandoned (crashed\n * holder) and forcibly broken. If the lock still can't be taken before the\n * timeout we proceed anyway — the write is atomic (temp+rename) so the worst\n * case degrades to a possible lost update, never a corrupt file. The lock is\n * always released in `finally`.\n */\nasync function withStateLock<T>(path: string, fn: () => Promise<T>): Promise<T> {\n const lockPath = `${path}.lock`;\n await mkdir(dirname(path), { recursive: true });\n const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;\n let held = false;\n while (!held) {\n try {\n const fh = await open(lockPath, 'wx');\n await fh.writeFile(`${String(process.pid)}\\n`);\n await fh.close();\n held = true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n // Break a stale lock left by a crashed holder.\n try {\n const st = await stat(lockPath);\n if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {\n await rm(lockPath, { force: true });\n continue;\n }\n } catch {\n // lock vanished between open and stat — retry immediately\n continue;\n }\n if (Date.now() >= deadline) break; // give up waiting; proceed best-effort\n await delay(LOCK_RETRY_MS);\n }\n }\n try {\n return await fn();\n } finally {\n if (held) await rm(lockPath, { force: true }).catch(() => {});\n }\n}\n\n/**\n * Locked read-modify-write of the state file. `mutator` receives the current\n * state and returns the next one; the read and the (atomic) write happen under\n * the same lock so concurrent mutators serialize instead of clobbering.\n */\nexport async function mutateState(\n mutator: (state: StateFile) => StateFile,\n path: string = STATE_FILE,\n): Promise<void> {\n await withStateLock(path, async () => {\n const state = await readState(path);\n await writeState(mutator(state), path);\n });\n}\n\nexport async function readState(path: string = STATE_FILE): Promise<StateFile> {\n try {\n const raw = await readFile(path, 'utf8');\n const parsed = JSON.parse(raw) as StateFile;\n if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {\n throw new Error(`unrecognized state file shape at ${path}`);\n }\n // Migrate-on-read: records written before the multi-provider split carry no\n // `provider` field — they are all Docker boxes. Default it so every\n // consumer (provider registry, `findBox`) sees a discriminated record.\n // Also backfill `box.docker` from the flat fields for Docker records so\n // forward-looking readers (7.1) see the nested shape without waiting\n // for the box to be re-recorded.\n for (const b of parsed.boxes) {\n b.provider ??= 'docker';\n if ((b.provider ?? 'docker') === 'docker' && !b.docker) {\n b.docker = projectDockerFields(b);\n }\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ...EMPTY };\n }\n throw err;\n }\n}\n\nexport async function writeState(state: StateFile, path: string = STATE_FILE): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n // Atomic: write a sibling temp file then rename over the target. rename(2) is\n // atomic on local filesystems, so a concurrent writer (or a reader) never\n // observes a half-written / interleaved JSON file. The pid+time suffix keeps\n // parallel writers from colliding on the temp path itself.\n const tmp = `${path}.tmp.${String(process.pid)}.${String(Date.now())}`;\n await writeFile(tmp, JSON.stringify(state, null, 2) + '\\n', 'utf8');\n await rename(tmp, path);\n}\n\nexport async function recordBox(box: BoxRecord, path: string = STATE_FILE): Promise<void> {\n // Forward-looking shape: every Docker write also mirrors the flat\n // docker-specific fields into `box.docker` so readers can move to the\n // nested form opportunistically (7.1). Cloud records skip the mirror —\n // the discriminator is `box.provider !== 'docker'`.\n const toWrite: BoxRecord =\n (box.provider ?? 'docker') === 'docker' && !box.docker\n ? { ...box, docker: projectDockerFields(box) }\n : box;\n await mutateState(\n (state) => ({\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n }),\n path,\n );\n}\n\n/**\n * Atomically allocate the next per-project index AND persist `record` claiming\n * it, under the state lock. Returns the reserved index (also stamped onto the\n * persisted record).\n *\n * Callers bake this index into on-disk paths (`<id>-<n>-<mnemonic>` box dir,\n * socket, snapshot), and host helpers re-derive those paths from the *recorded*\n * index — so the index must be settled before the paths are built. Allocating\n * with an unlocked `readState` and bumping a clash at record time would desync\n * the recorded index from the box's actual directory (status/ctl reads would\n * then miss it). Reserving up front, under the lock, guarantees the index a\n * create uses for its dirs is exactly the one in state.json, with no two\n * concurrent creates in the same project claiming the same number.\n */\nexport async function reserveProjectIndex(\n record: BoxRecord,\n projectRoot: string,\n path: string = STATE_FILE,\n): Promise<number> {\n let index = 1;\n await mutateState((state) => {\n index = allocateProjectIndex(state, projectRoot);\n const reserved: BoxRecord = { ...record, projectRoot, projectIndex: index };\n return {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== record.id), reserved],\n };\n }, path);\n return index;\n}\n\n/**\n * Build a `DockerBoxFields` payload from the flat Docker-specific fields\n * still living on `BoxRecord` for back-compat. Pure function, no\n * filesystem; safe for both `readState` migration and `recordBox` mirror.\n *\n * Once every reader uses `box.docker?.<field>` (the rest of 7.1), the\n * flat fields can be dropped and this projection becomes the canonical\n * shape. Until then, every write produces both shapes from the same\n * source so they can't drift.\n */\nfunction projectDockerFields(box: BoxRecord): DockerBoxFields {\n return {\n container: box.container,\n image: box.image,\n snapshotDir: box.snapshotDir ?? null,\n socketPath: box.socketPath,\n claudeConfigVolume: box.claudeConfigVolume,\n codexConfigVolume: box.codexConfigVolume,\n opencodeConfigVolume: box.opencodeConfigVolume,\n vscodeServerVolume: box.vscodeServerVolume,\n cursorServerVolume: box.cursorServerVolume,\n vncHostPort: box.vncHostPort,\n webHostPort: box.webHostPort,\n portlessAlias: box.portlessAlias,\n portlessUrl: box.portlessUrl,\n portlessVncAlias: box.portlessVncAlias,\n portlessVncUrl: box.portlessVncUrl,\n dockerVolume: box.dockerVolume,\n dockerCacheShared: box.dockerCacheShared,\n checkpointImage: box.checkpointImage,\n };\n}\n\nexport async function removeBoxRecord(id: string, path: string = STATE_FILE): Promise<boolean> {\n let removed = false;\n await mutateState((state) => {\n const next = state.boxes.filter((b) => b.id !== id);\n removed = next.length !== state.boxes.length;\n return { version: 1, boxes: next };\n }, path);\n return removed;\n}\n\n/**\n * Resolve a user-supplied identifier against the state file. Matching\n * precedence mirrors `docker`'s container reference resolution:\n *\n * 1. exact id\n * 2. unique id prefix\n * 3. exact name\n * 4. exact container name\n *\n * Returns `'ambiguous'` if step 2 finds more than one match (steps 1, 3, 4\n * are exact-match so they cannot be ambiguous on their own).\n */\nexport function findBox(idOrName: string, state: StateFile): FindBoxResult {\n const q = idOrName.trim();\n if (q.length === 0) return { kind: 'none' };\n\n const exactId = state.boxes.find((b) => b.id === q);\n if (exactId) return { kind: 'ok', box: exactId };\n\n const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));\n if (prefixMatches.length === 1) return { kind: 'ok', box: prefixMatches[0]! };\n if (prefixMatches.length > 1) return { kind: 'ambiguous', matches: prefixMatches };\n\n const byName = state.boxes.find((b) => b.name === q);\n if (byName) return { kind: 'ok', box: byName };\n\n // For docker records `container` is the docker container name; for cloud\n // records it's `cloud:<sandboxId>` (post 7.2 — no more synthetic\n // agentbox-cloud-* prefix). Either form is a valid byContainer lookup\n // key for `findBox`.\n const byContainer = state.boxes.find((b) => b.container === q);\n if (byContainer) return { kind: 'ok', box: byContainer };\n\n return { kind: 'none' };\n}\n\n/**\n * Next monotonic 1-based index for the given project. Reads only `state.boxes`\n * — caller is responsible for persisting the assignment. Boxes without\n * `projectRoot` are ignored (legacy records); boxes in *other* projects are\n * also ignored. Indices are never recycled, so a destroyed #2 leaves a gap.\n */\nexport function allocateProjectIndex(state: StateFile, projectRoot: string): number {\n let max = 0;\n for (const b of state.boxes) {\n if (b.projectRoot !== projectRoot) continue;\n if (typeof b.projectIndex === 'number' && b.projectIndex > max) {\n max = b.projectIndex;\n }\n }\n return max + 1;\n}\n\n/**\n * Auto-pick when a command's `[box]` argument is omitted. Returns the unique\n * box for `projectRoot`, an `ambiguous` carrying all candidates so the CLI can\n * print a chooser, or `none`.\n */\nexport function autoPickProjectBox(state: StateFile, projectRoot: string): FindBoxResult {\n const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);\n if (matches.length === 0) return { kind: 'none' };\n if (matches.length === 1) return { kind: 'ok', box: matches[0]! };\n return { kind: 'ambiguous', matches };\n}\n\n/**\n * Top-level resolver every CLI command goes through. Combines numeric-index\n * lookup with the legacy `findBox` matcher:\n *\n * - `ref === undefined` and `projectRoot` known → autoPickProjectBox.\n * - `ref` is a pure positive integer and `projectRoot` known → resolve as\n * project index. **Never** falls through to `findBox` on miss, so\n * `agentbox open 3` is reserved for the index and won't accidentally\n * match a hex id like `3abc…`.\n * - Otherwise → `findBox` (id → prefix → name → container).\n */\nexport function resolveBoxRef(\n ref: string | undefined,\n state: StateFile,\n projectRoot: string | undefined,\n): FindBoxResult {\n if (ref === undefined) {\n if (projectRoot === undefined) return { kind: 'none' };\n return autoPickProjectBox(state, projectRoot);\n }\n const trimmed = ref.trim();\n if (projectRoot !== undefined && /^[1-9][0-9]*$/.test(trimmed)) {\n const idx = Number.parseInt(trimmed, 10);\n const hit = state.boxes.find(\n (b) => b.projectRoot === projectRoot && b.projectIndex === idx,\n );\n return hit ? { kind: 'ok', box: hit } : { kind: 'none' };\n }\n return findBox(trimmed, state);\n}\n","import { execa } from 'execa';\nimport { readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface DetectedGitRepo {\n kind: 'root' | 'nested';\n /** Absolute host path of the repo working tree (== `<workspace>` for root). */\n hostMainRepo: string;\n /** Path relative to the workspace where the repo lives. Empty string for root. */\n relPathFromWorkspace: string;\n}\n\n/**\n * Look for `.git` directories at the workspace root and at every 1st-level\n * subdirectory. Worktree-form `.git` files (regular file containing\n * `gitdir: …`) are intentionally skipped — turning an existing worktree into\n * another worktree gets weird, and the user case for it is rare.\n *\n * Pure host-side detection: it only tells callers where the repos are. Docker\n * boxes create the worktree inside the container against the bind-mounted\n * `.git/`; cloud boxes clone from a bundle. Either way this is the host probe.\n */\nexport async function detectGitRepos(workspace: string): Promise<DetectedGitRepo[]> {\n const out: DetectedGitRepo[] = [];\n if (await isGitDir(join(workspace, '.git'))) {\n out.push({ kind: 'root', hostMainRepo: workspace, relPathFromWorkspace: '' });\n }\n let entries: Array<{ name: string; isDirectory: () => boolean }>;\n try {\n entries = await readdir(workspace, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const e of entries) {\n if (!e.isDirectory() || e.name.startsWith('.')) continue;\n const sub = join(workspace, e.name);\n if (await isGitDir(join(sub, '.git'))) {\n out.push({ kind: 'nested', hostMainRepo: sub, relPathFromWorkspace: e.name });\n }\n }\n return out;\n}\n\nasync function isGitDir(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Pick `<base>`, `<base>-2`, `<base>-3`, … until git reports no such branch\n * exists. Avoids collision when the user reruns `agentbox create -n same-name`\n * after destroying — the destroyed box's branch still lives in the host repo.\n */\nexport async function pickFreshBranch(hostMainRepo: string, base: string): Promise<string> {\n let candidate = base;\n let suffix = 2;\n while (await branchExists(hostMainRepo, candidate)) {\n candidate = `${base}-${String(suffix++)}`;\n if (suffix > 100) throw new GitWorktreeError(`could not find a free branch name near ${base}`);\n }\n return candidate;\n}\n\nasync function branchExists(hostMainRepo: string, name: string): Promise<boolean> {\n const result = await execa(\n 'git',\n ['-C', hostMainRepo, 'show-ref', '--verify', '--quiet', `refs/heads/${name}`],\n { reject: false },\n );\n return result.exitCode === 0;\n}\n\nexport class GitWorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitWorktreeError';\n }\n}\n","/**\n * The host command that opens a URL or file path in the OS default handler.\n *\n * macOS ships `open`; Linux uses `xdg-open` (from `xdg-utils`, present on any\n * desktop install). We deliberately return only the binary name and let each\n * call site keep its own spawn semantics (sync/async, stdio, detached) — the\n * single platform decision lives here so adding a host platform is a one-line\n * change. Callers already treat a non-zero exit / ENOENT as \"couldn't\n * auto-open\" and print the target, so an absent `xdg-open` degrades cleanly.\n */\nexport function hostOpenCommand(): string {\n return process.platform === 'linux' ? 'xdg-open' : 'open';\n}\n","import { mkdtemp, readFile, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, join } from 'node:path';\nimport { applyReplacements, deriveBoxHost, type ResolvedCarryEntry } from '@agentbox/core';\n\n/**\n * Box facts used to fill `{{AGENTBOX_*}}` placeholders in carried files. The\n * placeholder *names* match the in-box `agentbox-ctl render` whitelist; here the\n * values come from the create context (the box is named but not yet booted).\n */\nexport interface CarryBoxContext {\n name?: string;\n id?: string;\n kind?: string;\n /** Host workspace path (mirrors in-box AGENTBOX_HOST_WORKSPACE). */\n hostWorkspace?: string;\n projectRoot?: string;\n}\n\n/** Build the whitelist placeholder context (and derive AGENTBOX_BOX_HOST). */\nexport function carryPlaceholderContext(ctx: CarryBoxContext): Record<string, string> {\n const out: Record<string, string> = {};\n if (ctx.name) out.AGENTBOX_BOX_NAME = ctx.name;\n if (ctx.id) out.AGENTBOX_BOX_ID = ctx.id;\n if (ctx.kind) out.AGENTBOX_BOX_KIND = ctx.kind;\n if (ctx.hostWorkspace) out.AGENTBOX_HOST_WORKSPACE = ctx.hostWorkspace;\n if (ctx.projectRoot) out.AGENTBOX_PROJECT_ROOT = ctx.projectRoot;\n return deriveBoxHost(out);\n}\n\n/** Whether an entry opts into host-side rendering (file entries only). */\nfunction wantsRender(e: ResolvedCarryEntry): boolean {\n return e.kind === 'file' && (!!e.replaceEnvs || (e.replace?.length ?? 0) > 0);\n}\n\n/**\n * Render carry entries that opt into `replaceEnvs`/`replace`: read each file\n * host-side, apply the substitutions, write the result to a temp file, and\n * repoint `absSrc` at it so the existing per-provider tar/copy step transfers\n * the rendered content. Entries without replace options pass through unchanged.\n * Returns a new array (inputs are not mutated).\n */\nexport async function renderCarryEntries(\n entries: ResolvedCarryEntry[],\n ctx: CarryBoxContext,\n onLog?: (line: string) => void,\n): Promise<ResolvedCarryEntry[]> {\n if (!entries.some(wantsRender)) return entries;\n\n const context = carryPlaceholderContext(ctx);\n const stage = await mkdtemp(join(tmpdir(), 'agentbox-carry-render-'));\n const out: ResolvedCarryEntry[] = [];\n for (const [i, entry] of entries.entries()) {\n if (!wantsRender(entry)) {\n out.push(entry);\n continue;\n }\n const content = await readFile(entry.absSrc, 'utf8');\n const rendered = applyReplacements(content, {\n env: entry.replaceEnvs,\n rules: entry.replace,\n context,\n onWarn: (msg) => onLog?.(`carry: ${entry.rawSrc}: ${msg}`),\n });\n const tmp = join(stage, `${String(i)}-${basename(entry.absSrc)}`);\n await writeFile(tmp, rendered, 'utf8');\n out.push({ ...entry, absSrc: tmp, bytes: Buffer.byteLength(rendered) });\n const what = [\n ...(entry.replaceEnvs ? ['env'] : []),\n ...(entry.replace?.length ? [`${String(entry.replace.length)} rule(s)`] : []),\n ].join('+');\n onLog?.(`carry: rendered ${entry.rawSrc} (${what})`);\n }\n return out;\n}\n","/**\n * Cross-provider versioning primitives for `~/.agentbox/<provider>-prepared.json`.\n *\n * Each provider records what it has baked (docker image / hetzner snapshot /\n * daytona snapshot) under a per-provider JSON file with a shared `base.*`\n * substructure so the CLI can detect when the on-disk artifact is stale\n * relative to the current CLI's build context.\n *\n * The invalidation key is `base.contextSha256`: a deterministic SHA-256\n * over every file in the build context (Dockerfile + scripts + baked\n * config), keyed by the file's relative path. Two CLIs with the same\n * staged runtime tree produce the same hash; an edit to any baked asset\n * — even a one-byte tweak to `custom-system-CLAUDE.md` — flips it.\n *\n * Checkpoints embed the captured `contextSha256` so restoring an older\n * checkpoint can warn the user that the baked layers predate the current\n * base image.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, resolve as pathResolve } from 'node:path';\n\nexport type PreparedProviderKind = 'docker' | 'daytona' | 'hetzner' | 'vercel' | 'e2b';\n\n/**\n * The cross-provider record. `TImage` is the provider's opaque image\n * identifier: a string tag for docker/daytona, a numeric image id for\n * hetzner. The `TExtra` slot lets a provider attach provider-specific\n * fields (e.g. hetzner's `description` and `projects[]`) without forking\n * the whole shape.\n */\nexport interface PreparedBaseSnapshot<TImage = string, TExtra = unknown> {\n /** Schema version. Bumped when the on-disk shape changes incompatibly. */\n schema: number;\n base?: {\n /** Provider-opaque image identifier (docker tag | hetzner imageId | daytona snapshot name). */\n imageRef: TImage;\n /** Deterministic SHA-256 of the build context — the invalidation key. */\n contextSha256: string;\n /** Informational: CLI version that produced this artifact. */\n cliVersion: string;\n /** Informational: git short SHA injected at CLI build time (or 'dev'). */\n cliCommit?: string;\n /** ISO timestamp of bake completion. */\n createdAt: string;\n };\n /** Provider-specific extras (e.g. hetzner's per-project snapshot tier). */\n extras?: TExtra;\n}\n\nexport function preparedStatePathFor(provider: PreparedProviderKind): string {\n return pathResolve(homedir(), '.agentbox', `${provider}-prepared.json`);\n}\n\n/**\n * Read the prepared-state file for `provider`. Returns `null` when the file\n * is missing, malformed, or carries a schema this code doesn't recognise —\n * callers treat all three as \"rebuild needed\". Sync so it can run from\n * non-async setup paths (mirrors the hetzner helper it generalises).\n */\nexport function readPreparedStateRaw(provider: PreparedProviderKind): unknown {\n const path = preparedStatePathFor(provider);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Atomic write: write to `<path>.tmp` then rename. `mode: 0o600` because\n * the file is informational but lives alongside `secrets.env` — same dir,\n * same permissions hygiene.\n */\nexport function writePreparedStateRaw(provider: PreparedProviderKind, state: unknown): void {\n const path = preparedStatePathFor(provider);\n mkdirSync(dirname(path), { recursive: true });\n const body = JSON.stringify(state, null, 2) + '\\n';\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n renameSync(tmp, path);\n}\n\nexport async function sha256OfFile(path: string): Promise<string> {\n const buf = await readFile(path);\n return createHash('sha256').update(buf).digest('hex');\n}\n\nexport interface ContextFile {\n /**\n * Logical relative path. Used as the canonical key for hash determinism\n * — two stagings with identical contents but different absolute paths\n * must hash the same.\n */\n rel: string;\n /** Absolute path the file is read from. */\n abs: string;\n}\n\n/**\n * Deterministic hash over a set of context files. Entries are sorted by\n * `rel` then hashed as `<rel>\\0<sha256(file)>\\n` lines into a final SHA-256.\n *\n * - Sort order = determinism (the caller can pass files in any order).\n * - NUL separator = no collision between a `rel` ending in hex and the\n * following digest.\n * - Trailing newline per record = stable framing.\n *\n * Missing files raise — silently skipping would let a partial dev rebuild\n * stamp a hash that doesn't represent what's actually in the image.\n */\nexport async function computeContextSha256(files: ContextFile[]): Promise<string> {\n const sorted = [...files].sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));\n const outer = createHash('sha256');\n for (const f of sorted) {\n const inner = await sha256OfFile(f.abs);\n outer.update(`${f.rel}\\0${inner}\\n`);\n }\n return outer.digest('hex');\n}\n\n/** Short form for log lines — first 12 hex chars of a sha256. */\nexport function shortFingerprint(sha: string): string {\n return sha.slice(0, 12);\n}\n\n/**\n * CLI version stamps set by `apps/cli/src/index.ts` at startup via env vars\n * (the values themselves come from tsup's build-time `define`). Providers\n * record them onto prepared-state files and checkpoint manifests so a stale\n * artifact carries a human-readable hint about which CLI built it.\n *\n * Fallbacks cover the unit-test and unbundled-dev paths (the CLI never\n * loaded, env unset). `unknown` is a sentinel — never a real version.\n */\nexport interface CliStamp {\n cliVersion: string;\n cliCommit: string;\n}\n\nexport function readCliStamp(): CliStamp {\n return {\n cliVersion: process.env.AGENTBOX_CLI_VERSION ?? 'unknown',\n cliCommit: process.env.AGENTBOX_CLI_COMMIT ?? 'unknown',\n };\n}\n\n/**\n * Canonical map of files that go into the Docker base image build context\n * — every file `Dockerfile.box` COPYs, plus the Dockerfile itself. Two\n * layouts resolve the same logical entries:\n *\n * - staged: `<contextDir>/<staged>` (production CLI runtime + dev with `apps/cli/runtime/docker`)\n * - dev: `<sandboxDockerRoot>/<dev>` (workspace dev, no staged tree)\n *\n * Shared across providers because:\n * - sandbox-docker uses it to fingerprint its locally-built image.\n * - sandbox-daytona uses it to fingerprint the snapshot it bakes from the\n * same Dockerfile.box + the daytona-specific CLAUDE.md overlay.\n *\n * If you add a COPY line to `Dockerfile.box`, add the file here AND in\n * `apps/cli/scripts/stage-runtime.mjs` — failure to do so means the image\n * won't get re-built when that file changes.\n */\nexport const DOCKER_CONTEXT_FILE_MAP: Record<string, { staged: string; dev: string }> = {\n 'Dockerfile.box': { staged: 'Dockerfile.box', dev: 'Dockerfile.box' },\n 'ctl/bin.cjs': {\n staged: 'packages/ctl/dist/bin.cjs',\n dev: '../ctl/dist/bin.cjs',\n },\n 'share/agentbox-setup/SKILL.md': {\n staged: 'apps/cli/share/agentbox-setup/SKILL.md',\n dev: '../../apps/cli/share/agentbox-setup/SKILL.md',\n },\n 'scripts/agentbox-vnc-start': {\n staged: 'packages/sandbox-docker/scripts/agentbox-vnc-start',\n dev: 'scripts/agentbox-vnc-start',\n },\n 'scripts/agentbox-dockerd-start': {\n staged: 'packages/sandbox-docker/scripts/agentbox-dockerd-start',\n dev: 'scripts/agentbox-dockerd-start',\n },\n 'scripts/agentbox-checkpoint-cleanup': {\n staged: 'packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup',\n dev: 'scripts/agentbox-checkpoint-cleanup',\n },\n 'scripts/agentbox-open': {\n staged: 'packages/sandbox-docker/scripts/agentbox-open',\n dev: 'scripts/agentbox-open',\n },\n 'scripts/custom-system-CLAUDE.md': {\n staged: 'packages/sandbox-docker/scripts/custom-system-CLAUDE.md',\n dev: 'scripts/custom-system-CLAUDE.md',\n },\n 'scripts/claude-managed-settings.json': {\n staged: 'packages/sandbox-docker/scripts/claude-managed-settings.json',\n dev: 'scripts/claude-managed-settings.json',\n },\n 'scripts/agentbox-codex-hooks.json': {\n staged: 'packages/sandbox-docker/scripts/agentbox-codex-hooks.json',\n dev: 'scripts/agentbox-codex-hooks.json',\n },\n};\n\n/**\n * Resolve every entry in `fileMap` to an absolute path. Tries `<contextDir>/<staged>`\n * first; falls back to `<devRoot>/<dev>`. Returns `null` if any required file\n * is missing — callers treat that as \"can't fingerprint\" and skip the\n * cache-hit shortcut. Pure (no I/O beyond `existsSync`), so safe for use\n * from the provider's prepare path.\n */\nexport function resolveContextFilesFrom(\n fileMap: Record<string, { staged: string; dev: string }>,\n opts: { contextDir: string; devRoot: string },\n): ContextFile[] | null {\n const out: ContextFile[] = [];\n for (const [rel, paths] of Object.entries(fileMap)) {\n const candidates = [\n pathResolve(opts.contextDir, paths.staged),\n pathResolve(opts.devRoot, paths.dev),\n ];\n const hit = candidates.find((p) => existsSync(p));\n if (!hit) return null;\n out.push({ rel, abs: hit });\n }\n return out;\n}\n","/**\n * Docker provider's `~/.agentbox/docker-prepared.json` reader/writer + the\n * build-context fingerprint that drives base-image invalidation.\n *\n * The fingerprint is a SHA-256 over every file `docker build` would COPY\n * into the image — Dockerfile + scripts + baked config files. Two CLIs\n * with identical staged runtime trees produce the same hash; a one-byte\n * edit to any baked asset flips it, which is the signal `ensureImage()`\n * uses to rebuild instead of reusing the cached image.\n */\n\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n computeContextSha256,\n DOCKER_CONTEXT_FILE_MAP,\n readCliStamp,\n readPreparedStateRaw,\n resolveContextFilesFrom,\n writePreparedStateRaw,\n type ContextFile,\n type PreparedBaseSnapshot,\n} from '@agentbox/sandbox-core';\nimport { BUILD_CONTEXT_DIR, DEFAULT_BOX_IMAGE, DOCKERFILE_PATH } from './image.js';\n\nconst SCHEMA = 1 as const;\n\nexport type PreparedDockerState = PreparedBaseSnapshot<string, never>;\n\n/**\n * Resolve every fingerprint input to an absolute path. The canonical file\n * list lives in `@agentbox/sandbox-core` (DOCKER_CONTEXT_FILE_MAP) so the\n * daytona provider can hash the same inputs without depending on this\n * package. Two layouts are tried in order, mirroring `resolveDockerBuild()`\n * in `image.ts`:\n * 1. Build context dir (staged runtime / env override).\n * 2. Sandbox-docker package root (dev fallback).\n *\n * Returns `null` when *any* required file is missing — callers treat that\n * as \"can't fingerprint\" and skip the cache-hit shortcut (always rebuild).\n */\nexport function resolveContextFiles(opts: { contextDir?: string } = {}): ContextFile[] | null {\n const ctx = opts.contextDir ?? BUILD_CONTEXT_DIR;\n const here = dirname(fileURLToPath(import.meta.url));\n // sandbox-docker's package root = parent of src/ or parent of dist/.\n const packageRoot = resolve(here, '..');\n return resolveContextFilesFrom(DOCKER_CONTEXT_FILE_MAP, {\n contextDir: ctx,\n devRoot: packageRoot,\n });\n}\n\nexport interface ResolvedFingerprint {\n contextSha256: string;\n /** Files that fed the hash (in canonical sorted order). */\n files: ContextFile[];\n}\n\nexport async function computeDockerContextFingerprint(opts: {\n contextDir?: string;\n} = {}): Promise<ResolvedFingerprint | null> {\n const files = resolveContextFiles(opts);\n if (!files) return null;\n return { contextSha256: await computeContextSha256(files), files };\n}\n\nexport function readPreparedDockerState(): PreparedDockerState | null {\n const raw = readPreparedStateRaw('docker');\n if (raw === null || typeof raw !== 'object') return null;\n const parsed = raw as Partial<PreparedDockerState>;\n if (parsed.schema !== SCHEMA) return null;\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedDockerState(opts: {\n imageRef?: string;\n contextSha256: string;\n}): void {\n const stamp = readCliStamp();\n const state: PreparedDockerState = {\n schema: SCHEMA,\n base: {\n imageRef: opts.imageRef ?? DEFAULT_BOX_IMAGE,\n contextSha256: opts.contextSha256,\n cliVersion: stamp.cliVersion,\n cliCommit: stamp.cliCommit,\n createdAt: new Date().toISOString(),\n },\n };\n writePreparedStateRaw('docker', state);\n}\n\n/** Convenience for `ensureImage` and `prepare` — true when the stamped fingerprint matches. */\nexport function preparedMatches(state: PreparedDockerState | null, current: string): boolean {\n return state?.base?.contextSha256 === current;\n}\n\n/** Re-export so callers don't reach into image.ts just for the Dockerfile path. */\nexport { DOCKERFILE_PATH };\n","import { execa } from 'execa';\nimport { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\n\nexport const DEFAULT_BOX_IMAGE = 'agentbox/box:dev';\n\n/**\n * Public registry repo the box image is published to (see\n * `.github/workflows/box-image.yml`). The CLI pulls a fingerprint-tagged\n * image from here on first use instead of building locally — a multi-minute\n * build collapses to a `docker pull`. An empty registry (config override)\n * disables pulling and always builds.\n */\nexport const BOX_IMAGE_REGISTRY = 'ghcr.io/madarco/agentbox/box';\n\n/**\n * The pull target for a given build-context fingerprint. The tag *is* the\n * content identity: a local staged context that matches a published build\n * has the same sha, so a pull hit can be retagged to `agentbox/box:dev` and\n * stamped into docker-prepared.json without risk of a stale image (a locally\n * edited context has a different sha, its tag 404s, and we build instead).\n */\nexport function registryRefForSha(sha: string, registry: string = BOX_IMAGE_REGISTRY): string {\n return `${registry}:sha-${sha.slice(0, 16)}`;\n}\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n// The Dockerfile's COPY lines reference monorepo-relative paths\n// (packages/ctl/dist/bin.cjs, apps/cli/share/..., packages/sandbox-docker/scripts/*),\n// so the build context must be a dir containing that tree.\n//\n// Resolution order:\n// 0. AGENTBOX_DOCKER_CONTEXT env override (dir holding Dockerfile.box).\n// 1. Staged context shipped with the bundled `agent-box` package: this\n// module is bundled into the CLI at <root>/dist, the stage step mirrors\n// the COPY tree at <root>/runtime/docker (sibling of dist/, uniform in\n// dev and when installed).\n// 2. Legacy monorepo: Dockerfile.box at the sandbox-docker package root,\n// build context = monorepo root.\nfunction resolveDockerBuild(): { dockerfile: string; context: string } {\n const override = process.env.AGENTBOX_DOCKER_CONTEXT;\n if (override && existsSync(resolve(override, 'Dockerfile.box'))) {\n return { dockerfile: resolve(override, 'Dockerfile.box'), context: override };\n }\n const staged = resolve(here, '..', 'runtime', 'docker');\n if (existsSync(resolve(staged, 'Dockerfile.box'))) {\n return { dockerfile: resolve(staged, 'Dockerfile.box'), context: staged };\n }\n // Legacy: src/ (or the unbundled package dist/) is one level under the\n // package root; the monorepo root is two more up.\n const packageRoot = resolve(here, '..');\n return {\n dockerfile: resolve(packageRoot, 'Dockerfile.box'),\n context: resolve(packageRoot, '..', '..'),\n };\n}\n\nconst { dockerfile: DOCKERFILE_PATH_RESOLVED, context: BUILD_CONTEXT_DIR_RESOLVED } =\n resolveDockerBuild();\nexport const DOCKERFILE_PATH = DOCKERFILE_PATH_RESOLVED;\nexport const BUILD_CONTEXT_DIR = BUILD_CONTEXT_DIR_RESOLVED;\n\nexport async function imageExists(ref: string): Promise<boolean> {\n const result = await execa('docker', ['image', 'inspect', ref], { reject: false });\n return result.exitCode === 0;\n}\n\n/**\n * Attempt `docker pull <target>`. Returns true on success, false on any\n * failure (missing tag, offline, auth) — callers fall back to a local build.\n * Never throws. Single attempt: a missing tag is the expected \"build locally\"\n * signal, not a transient error worth retrying.\n */\nexport async function pullImage(\n target: string,\n opts: { onProgress?: (line: string) => void } = {},\n): Promise<boolean> {\n const subprocess = execa('docker', ['pull', target], {\n stderr: 'pipe',\n stdout: 'pipe',\n reject: false,\n });\n if (opts.onProgress) {\n const forward = (chunk: Buffer | string): void => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onProgress?.(line);\n }\n };\n subprocess.stdout?.on('data', forward);\n subprocess.stderr?.on('data', forward);\n }\n const result = await subprocess;\n return result.exitCode === 0;\n}\n\nexport async function tagImage(source: string, target: string): Promise<void> {\n await execa('docker', ['tag', source, target]);\n}\n\nexport interface ImageInfo {\n /** Image ref (e.g. `agentbox/box:dev`). */\n ref: string;\n /** True when the engine has the image locally. */\n exists: boolean;\n /** Image size in bytes, when known. */\n sizeBytes?: number;\n /** ISO-8601 creation time, when known. */\n createdAt?: string;\n}\n\n/**\n * Read-only inspect of a Docker image. Used by `agentbox prepare` (no-args\n * status mode) to surface base-image state. Never throws — returns\n * `{ exists: false }` on any error so the status command works even when\n * the docker daemon is unreachable.\n */\nexport async function imageInfo(ref: string = DEFAULT_BOX_IMAGE): Promise<ImageInfo> {\n const result = await execa(\n 'docker',\n ['image', 'inspect', '--format', '{{.Size}}|{{.Created}}', ref],\n { reject: false },\n );\n if (result.exitCode !== 0) return { ref, exists: false };\n const [sizeStr, createdAt] = result.stdout.trim().split('|');\n const sizeBytes = sizeStr ? Number.parseInt(sizeStr, 10) : NaN;\n return {\n ref,\n exists: true,\n sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : undefined,\n createdAt: createdAt && createdAt.length > 0 ? createdAt : undefined,\n };\n}\n\nexport interface BuildImageOptions {\n ref?: string;\n dockerfile?: string;\n contextDir?: string;\n onProgress?: (line: string) => void;\n}\n\nexport async function buildImage(opts: BuildImageOptions = {}): Promise<string> {\n const ref = opts.ref ?? DEFAULT_BOX_IMAGE;\n const dockerfile = opts.dockerfile ?? DOCKERFILE_PATH;\n const contextDir = opts.contextDir ?? BUILD_CONTEXT_DIR;\n\n // Dogfood path: when building from inside an agentbox (docker-in-docker),\n // the default bridge network can't bind-mount /proc/<pid>/ns/net for the\n // build container, breaking any RUN that needs network (e.g. apt, curl).\n // Falling back to host networking sidesteps the missing capability.\n const args = ['build', '-t', ref, '-f', dockerfile, contextDir];\n if (process.env.AGENTBOX === '1') {\n args.splice(1, 0, '--network=host');\n }\n\n const subprocess = execa('docker', args, {\n stderr: 'pipe',\n stdout: 'pipe',\n });\n\n if (opts.onProgress) {\n const forward = (chunk: Buffer | string): void => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onProgress?.(line);\n }\n };\n subprocess.stdout?.on('data', forward);\n subprocess.stderr?.on('data', forward);\n }\n\n await subprocess;\n return ref;\n}\n\nexport interface PullOrBuildOptions {\n onProgress?: (line: string) => void;\n /** Dockerfile path. Defaults to `Dockerfile.box` next to this package. */\n dockerfile?: string;\n /** Build context directory. Defaults to the staged runtime / monorepo root. */\n contextDir?: string;\n /** Try the registry before building. Defaults to true. */\n allowPull?: boolean;\n /** Registry repo to pull from. Defaults to `BOX_IMAGE_REGISTRY`; empty disables pulling. */\n registry?: string;\n}\n\n/**\n * Make `ref` present locally, preferring a registry pull over a local build.\n *\n * When `fingerprint` is non-null and pulling is allowed, pull the\n * fingerprint-tagged image and retag it to `ref`; on a miss (or when pulling\n * is disabled / unfingerprintable) build from the staged context. Either way,\n * a known fingerprint is stamped into docker-prepared.json so the next\n * `ensureImage()` treats this as a cache hit.\n */\nexport async function pullOrBuild(\n ref: string,\n fingerprint: { contextSha256: string } | null,\n opts: PullOrBuildOptions = {},\n): Promise<{ source: 'pulled' | 'built' }> {\n const { writePreparedDockerState } = await import('./prepared-state.js');\n const registry = opts.registry ?? BOX_IMAGE_REGISTRY;\n const allowPull = opts.allowPull !== false;\n\n if (allowPull && registry && fingerprint) {\n const target = registryRefForSha(fingerprint.contextSha256, registry);\n opts.onProgress?.(`[image] pulling ${target}`);\n if (await pullImage(target, { onProgress: opts.onProgress })) {\n await tagImage(target, ref);\n writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });\n opts.onProgress?.(`[image] pulled ${target} -> ${ref}`);\n return { source: 'pulled' };\n }\n opts.onProgress?.(`[image] registry miss, building ${ref} locally`);\n }\n\n await buildImage({\n ref,\n dockerfile: opts.dockerfile,\n contextDir: opts.contextDir,\n onProgress: opts.onProgress,\n });\n if (fingerprint) {\n writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });\n }\n return { source: 'built' };\n}\n\nexport interface EnsureImageOptions {\n onProgress?: (line: string) => void;\n /** Dockerfile path. Defaults to `Dockerfile.box` next to this package. */\n dockerfile?: string;\n /** Build context directory. Defaults to the monorepo root. */\n contextDir?: string;\n /** Try the registry before building. Defaults to true. */\n allowPull?: boolean;\n /** Registry repo to pull from. Defaults to `BOX_IMAGE_REGISTRY`; empty disables pulling. */\n registry?: string;\n}\n\nexport async function ensureImage(\n ref: string = DEFAULT_BOX_IMAGE,\n opts: EnsureImageOptions = {},\n): Promise<{ ref: string; built: boolean; reason?: string }> {\n // Lazy import: prepared-state imports back into image.ts for the default\n // DOCKERFILE_PATH/BUILD_CONTEXT_DIR constants, so loading it at top-level\n // would create a circular ESM init order.\n const { computeDockerContextFingerprint, readPreparedDockerState, preparedMatches } =\n await import('./prepared-state.js');\n\n const fingerprint = await computeDockerContextFingerprint({\n contextDir: opts.contextDir,\n });\n const prepared = readPreparedDockerState();\n const exists = await imageExists(ref);\n\n let reason: string | undefined;\n if (!exists) {\n reason = `image ${ref} not present`;\n } else if (!fingerprint) {\n // Couldn't enumerate the context (partial dev rebuild?). Don't rebuild\n // unconditionally — that would surprise users mid-iteration. Trust the\n // image-exists check and leave the prepared file untouched.\n return { ref, built: false, reason: 'image present (fingerprint skipped)' };\n } else if (!prepared) {\n reason = 'no docker-prepared.json on disk';\n } else if (!preparedMatches(prepared, fingerprint.contextSha256)) {\n reason =\n `build context changed (was ${prepared.base?.contextSha256?.slice(0, 12) ?? '<none>'}, ` +\n `now ${fingerprint.contextSha256.slice(0, 12)})`;\n }\n\n if (!reason) {\n return { ref, built: false, reason: 'image up to date' };\n }\n\n opts.onProgress?.(`[image] ${ref}: ${reason}`);\n const { source } = await pullOrBuild(ref, fingerprint, {\n onProgress: opts.onProgress,\n dockerfile: opts.dockerfile,\n contextDir: opts.contextDir,\n allowPull: opts.allowPull,\n registry: opts.registry,\n });\n return { ref, built: source === 'built', reason };\n}\n\n"],"mappings":";;;AIAA,SAAS,mBAAmB;AJO5B,IAAM,qBAAoC;EACxC,MAAM;;;;EAIN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAKA,IAAM,gBAA+B;EACnC,MAAM;EACN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAIA,IAAM,mBAAkC;EACtC,MAAM;EACN,UAAU,gBAAgB,UAAU;AAClC,QAAI,CAAC,eAAgB,QAAO,CAAC,GAAG,QAAQ;AACxC,WAAO,CAAC,gBAAgB,GAAG,QAAQ;EACrC;AACF;AAEO,SAAS,qBAAqB,MAAgC;AACnE,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,WAAY,QAAO;AAChC,QAAM,IAAI,MAAM,uBAAuB,OAAO,IAAI,CAAC,EAAE;AACvD;AE/BO,IAAM,mBAAmB;EAC9B;EACA;EACA;EACA;EACA;;;EAGA;AACF;AAIA,IAAM,kBAAkB,IAAI,IAAY,gBAAgB;AAYjD,IAAM,eAAN,cAA2B,MAAM;EACtC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAEA,IAAM,iBAAiB;AAQhB,SAAS,uBACd,MACA,SACA,QACQ;AACR,SAAO,KAAK,QAAQ,gBAAgB,CAAC,OAAO,SAAiB;AAC3D,QAAI,CAAC,gBAAgB,IAAI,IAAI,EAAG,QAAO;AACvC,UAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAI,UAAU,QAAW;AACvB,eAAS,iBAAiB,IAAI,uDAAkD;AAChF,aAAO;IACT;AACA,WAAO;EACT,CAAC;AACH;AAkBO,SAAS,kBAAkB,SAAiB,MAAwC;AACzF,MAAI,MAAM;AACV,MAAI,KAAK,KAAK;AACZ,UAAM,uBAAuB,KAAK,KAAK,SAAS,KAAK,MAAM;EAC7D;AACA,aAAW,QAAQ,KAAK,SAAS,CAAC,GAAG;AACnC,UAAM,KAAK,uBAAuB,KAAK,IAAI,KAAK,SAAS,KAAK,MAAM;AACpE,QAAI,KAAK,OAAO;AACd,UAAI;AACJ,UAAI;AACF,aAAK,IAAI,OAAO,KAAK,MAAM,KAAK,SAAS,GAAG;MAC9C,SAAS,KAAK;AACZ,cAAM,IAAI;UACR,kBAAkB,KAAK,IAAI,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;QACnF;MACF;AACA,YAAM,IAAI,QAAQ,IAAI,EAAE;IAC1B,OAAO;AAEL,YAAM,IAAI,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE;IACpC;EACF;AACA,SAAO;AACT;AAQO,SAAS,cAAc,KAAqD;AACjF,MAAI,IAAI,sBAAsB,UAAa,IAAI,sBAAsB,QAAW;AAC9E,QAAI,oBAAoB,GAAG,IAAI,iBAAiB;EAClD;AACA,SAAO;AACT;AAgBA,SAAS,cAAc,GAA0C;AAC/D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,QAAQ,MAAM,SAAS,OAAO,CAAC;AAGnD,SAAS,iBAAiB,KAAc,OAA4B;AACzE,MAAI,CAAC,cAAc,GAAG,GAAG;AACvB,UAAM,IAAI,aAAa,GAAG,KAAK,+CAA+C;EAChF;AACA,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAClC,QAAI,CAAC,UAAU,IAAI,GAAG,EAAG,OAAM,IAAI,aAAa,GAAG,KAAK,qBAAqB,GAAG,GAAG;EACrF;AACA,MAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,WAAW,GAAG;AACzD,UAAM,IAAI,aAAa,GAAG,KAAK,kCAAkC;EACnE;AACA,MAAI,OAAO,IAAI,OAAO,UAAU;AAC9B,UAAM,IAAI,aAAa,GAAG,KAAK,sBAAsB;EACvD;AACA,QAAM,OAAoB,EAAE,MAAM,IAAI,MAAM,IAAI,IAAI,GAAG;AACvD,MAAI,IAAI,UAAU,UAAa,IAAI,UAAU,MAAM;AACjD,QAAI,OAAO,IAAI,UAAU,UAAW,OAAM,IAAI,aAAa,GAAG,KAAK,0BAA0B;AAC7F,SAAK,QAAQ,IAAI;EACnB;AACA,MAAI,IAAI,UAAU,UAAa,IAAI,UAAU,MAAM;AACjD,QAAI,OAAO,IAAI,UAAU,SAAU,OAAM,IAAI,aAAa,GAAG,KAAK,yBAAyB;AAC3F,SAAK,QAAQ,IAAI;EACnB;AACA,MAAI,KAAK,OAAO;AACd,QAAI;AACF,UAAI,OAAO,KAAK,MAAM,KAAK,SAAS,GAAG;IACzC,SAAS,KAAK;AACZ,YAAM,IAAI;QACR,GAAG,KAAK,+BAA+B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;MACzF;IACF;EACF;AACA,SAAO;AACT;AAGO,SAAS,kBAAkB,KAAc,OAA8B;AAC5E,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO,CAAC;AAC/C,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,OAAM,IAAI,aAAa,GAAG,KAAK,0BAA0B;AAClF,SAAO,IAAI,IAAI,CAAC,GAAG,MAAM,iBAAiB,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC;AACxE;AAGO,SAAS,kBAAkB,KAA6C;AAC7E,MAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO,CAAC;AAC/C,MAAI,CAAC,cAAc,GAAG,GAAG;AACvB,UAAM,IAAI,aAAa,yDAAoD;EAC7E;AACA,QAAM,MAAqC,CAAC;AAC5C,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC/C,QAAI,CAAC,mBAAmB,KAAK,IAAI,GAAG;AAClC,YAAM,IAAI,aAAa,gBAAgB,IAAI,kCAAkC;IAC/E;AACA,QAAI,IAAI,IAAI,kBAAkB,OAAO,gBAAgB,IAAI,EAAE;EAC7D;AACA,SAAO;AACT;AAMO,SAAS,gBACd,MACA,cACA,OACe;AACf,QAAM,MAAqB,CAAC;AAC5B,aAAW,QAAQ,MAAM;AACvB,UAAM,MAAM,aAAa,IAAI;AAC7B,QAAI,QAAQ,QAAW;AACrB,YAAM,QAAQ,OAAO,KAAK,YAAY;AACtC,YAAM,IAAI;QACR,GAAG,KAAK,oCAAoC,IAAI,OAC7C,MAAM,SAAS,IAAI,YAAY,MAAM,KAAK,IAAI,CAAC,MAAM;MAC1D;IACF;AACA,QAAI,KAAK,GAAG,GAAG;EACjB;AACA,SAAO;AACT;AC/MO,IAAM,kBAAN,cAA8B,MAAM;EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;AAEO,IAAM,mBAAN,cAA+B,MAAM;EAC1C,YAA4B,OAAe;AACzC,UAAM,wBAAwB,KAAK,GAAG;AADZ,SAAA,QAAA;AAE1B,SAAK,OAAO;EACd;EAH4B;AAI9B;AAEO,IAAM,oBAAN,cAAgC,MAAM;EAC3C,YACkB,OACA,SAChB;AACA,UAAM,MAAM,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,IAAI;AAC9C,UAAM,IAAI,KAAK,6BAA6B,GAAG,EAAE;AAJjC,SAAA,QAAA;AACA,SAAA,UAAA;AAIhB,SAAK,OAAO;EACd;EANkB;EACA;AAMpB;ACxBO,IAAM,gBAAgB;AAEtB,SAAS,gBAAwB;AACtC,SAAO,GAAG,aAAa,GAAG,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AAC1D;;;ACnBA,SAAS,OAAO,MAAM,UAAU,QAAQ,IAAI,MAAM,iBAAiB;AACnE,SAAS,eAAe;AACxB,SAAS,SAAS,YAAY;AAC9B,SAAS,cAAc,aAAa;ACHpC,SAAS,aAAa;AACtB,SAAS,SAAS,QAAAA,aAAY;AAC9B,SAAS,QAAAC,aAAY;AEFrB,SAAS,SAAS,YAAAC,WAAU,aAAAC,kBAAiB;AAC7C,SAAS,cAAc;AACvB,SAAS,UAAU,QAAAF,aAAY;ACiB/B,SAAS,kBAAkB;AAC3B,SAAS,YAAY,WAAW,cAAc,YAAY,qBAAqB;AAC/E,SAAS,YAAAG,iBAAgB;AACzB,SAAS,WAAAC,gBAAe;AACxB,SAAS,WAAAC,UAAS,WAAW,mBAAmB;AJjBzC,IAAM,YAAY,KAAK,QAAQ,GAAG,WAAW;AAC7C,IAAM,aAAa,KAAK,WAAW,YAAY;AAEtD,IAAM,QAAmB,EAAE,SAAS,GAAG,OAAO,CAAC,EAAE;AAOjD,IAAM,gBAAgB;AACtB,IAAM,0BAA0B;AAChC,IAAM,gBAAgB;AAatB,eAAe,cAAiB,MAAc,IAAkC;AAC9E,QAAM,WAAW,GAAG,IAAI;AACxB,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,MAAI,OAAO;AACX,SAAO,CAAC,MAAM;AACZ,QAAI;AACF,YAAM,KAAK,MAAM,KAAK,UAAU,IAAI;AACpC,YAAM,GAAG,UAAU,GAAG,OAAO,QAAQ,GAAG,CAAC;CAAI;AAC7C,YAAM,GAAG,MAAM;AACf,aAAO;IACT,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,SAAU,OAAM;AAE5D,UAAI;AACF,cAAM,KAAK,MAAM,KAAK,QAAQ;AAC9B,YAAI,KAAK,IAAI,IAAI,GAAG,UAAU,eAAe;AAC3C,gBAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC;AAClC;QACF;MACF,QAAQ;AAEN;MACF;AACA,UAAI,KAAK,IAAI,KAAK,SAAU;AAC5B,YAAM,MAAM,aAAa;IAC3B;EACF;AACA,MAAI;AACF,WAAO,MAAM,GAAG;EAClB,UAAA;AACE,QAAI,KAAM,OAAM,GAAG,UAAU,EAAE,OAAO,KAAK,CAAC,EAAE,MAAM,MAAM;IAAC,CAAC;EAC9D;AACF;AAOA,eAAsB,YACpB,SACA,OAAe,YACA;AACf,QAAM,cAAc,MAAM,YAAY;AACpC,UAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,UAAM,WAAW,QAAQ,KAAK,GAAG,IAAI;EACvC,CAAC;AACH;AAEA,eAAsB,UAAU,OAAe,YAAgC;AAC7E,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,MAAM,MAAM;AACvC,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,YAAY,KAAK,CAAC,MAAM,QAAQ,OAAO,KAAK,GAAG;AACxD,YAAM,IAAI,MAAM,oCAAoC,IAAI,EAAE;IAC5D;AAOA,eAAW,KAAK,OAAO,OAAO;AAC5B,QAAE,aAAa;AACf,WAAK,EAAE,YAAY,cAAc,YAAY,CAAC,EAAE,QAAQ;AACtD,UAAE,SAAS,oBAAoB,CAAC;MAClC;IACF;AACA,WAAO;EACT,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,UAAU;AACpD,aAAO,EAAE,GAAG,MAAM;IACpB;AACA,UAAM;EACR;AACF;AAEA,eAAsB,WAAW,OAAkB,OAAe,YAA2B;AAC3F,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAK9C,QAAM,MAAM,GAAG,IAAI,QAAQ,OAAO,QAAQ,GAAG,CAAC,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC;AACpE,QAAM,UAAU,KAAK,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI,MAAM,MAAM;AAClE,QAAM,OAAO,KAAK,IAAI;AACxB;AAEA,eAAsB,UAAU,KAAgB,OAAe,YAA2B;AAKxF,QAAM,WACH,IAAI,YAAY,cAAc,YAAY,CAAC,IAAI,SAC5C,EAAE,GAAG,KAAK,QAAQ,oBAAoB,GAAG,EAAE,IAC3C;AACN,QAAM;IACJ,CAAC,WAAW;MACV,SAAS;MACT,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,QAAQ,EAAE,GAAG,OAAO;IACpE;IACA;EACF;AACF;AAgBA,eAAsB,oBACpB,QACA,aACA,OAAe,YACE;AACjB,MAAI,QAAQ;AACZ,QAAM,YAAY,CAAC,UAAU;AAC3B,YAAQ,qBAAqB,OAAO,WAAW;AAC/C,UAAM,WAAsB,EAAE,GAAG,QAAQ,aAAa,cAAc,MAAM;AAC1E,WAAO;MACL,SAAS;MACT,OAAO,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE,GAAG,QAAQ;IACpE;EACF,GAAG,IAAI;AACP,SAAO;AACT;AAYA,SAAS,oBAAoB,KAAiC;AAC5D,SAAO;IACL,WAAW,IAAI;IACf,OAAO,IAAI;IACX,aAAa,IAAI,eAAe;IAChC,YAAY,IAAI;IAChB,oBAAoB,IAAI;IACxB,mBAAmB,IAAI;IACvB,sBAAsB,IAAI;IAC1B,oBAAoB,IAAI;IACxB,oBAAoB,IAAI;IACxB,aAAa,IAAI;IACjB,aAAa,IAAI;IACjB,eAAe,IAAI;IACnB,aAAa,IAAI;IACjB,kBAAkB,IAAI;IACtB,gBAAgB,IAAI;IACpB,cAAc,IAAI;IAClB,mBAAmB,IAAI;IACvB,iBAAiB,IAAI;EACvB;AACF;AAEA,eAAsB,gBAAgB,IAAY,OAAe,YAA8B;AAC7F,MAAI,UAAU;AACd,QAAM,YAAY,CAAC,UAAU;AAC3B,UAAM,OAAO,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAClD,cAAU,KAAK,WAAW,MAAM,MAAM;AACtC,WAAO,EAAE,SAAS,GAAG,OAAO,KAAK;EACnC,GAAG,IAAI;AACP,SAAO;AACT;AAcO,SAAS,QAAQ,UAAkB,OAAiC;AACzE,QAAM,IAAI,SAAS,KAAK;AACxB,MAAI,EAAE,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAE1C,QAAM,UAAU,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC;AAClD,MAAI,QAAS,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ;AAE/C,QAAM,gBAAgB,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC;AAClE,MAAI,cAAc,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,cAAc,CAAC,EAAG;AAC5E,MAAI,cAAc,SAAS,EAAG,QAAO,EAAE,MAAM,aAAa,SAAS,cAAc;AAEjF,QAAM,SAAS,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC;AACnD,MAAI,OAAQ,QAAO,EAAE,MAAM,MAAM,KAAK,OAAO;AAM7C,QAAM,cAAc,MAAM,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC;AAC7D,MAAI,YAAa,QAAO,EAAE,MAAM,MAAM,KAAK,YAAY;AAEvD,SAAO,EAAE,MAAM,OAAO;AACxB;AAQO,SAAS,qBAAqB,OAAkB,aAA6B;AAClF,MAAI,MAAM;AACV,aAAW,KAAK,MAAM,OAAO;AAC3B,QAAI,EAAE,gBAAgB,YAAa;AACnC,QAAI,OAAO,EAAE,iBAAiB,YAAY,EAAE,eAAe,KAAK;AAC9D,YAAM,EAAE;IACV;EACF;AACA,SAAO,MAAM;AACf;AAOO,SAAS,mBAAmB,OAAkB,aAAoC;AACvF,QAAM,UAAU,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,gBAAgB,WAAW;AACvE,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAChD,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,MAAM,KAAK,QAAQ,CAAC,EAAG;AAChE,SAAO,EAAE,MAAM,aAAa,QAAQ;AACtC;AAaO,SAAS,cACd,KACA,OACA,aACe;AACf,MAAI,QAAQ,QAAW;AACrB,QAAI,gBAAgB,OAAW,QAAO,EAAE,MAAM,OAAO;AACrD,WAAO,mBAAmB,OAAO,WAAW;EAC9C;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,gBAAgB,UAAa,gBAAgB,KAAK,OAAO,GAAG;AAC9D,UAAM,MAAM,OAAO,SAAS,SAAS,EAAE;AACvC,UAAM,MAAM,MAAM,MAAM;MACtB,CAAC,MAAM,EAAE,gBAAgB,eAAe,EAAE,iBAAiB;IAC7D;AACA,WAAO,MAAM,EAAE,MAAM,MAAM,KAAK,IAAI,IAAI,EAAE,MAAM,OAAO;EACzD;AACA,SAAO,QAAQ,SAAS,KAAK;AAC/B;AC5RA,eAAsB,eAAe,WAA+C;AAClF,QAAM,MAAyB,CAAC;AAChC,MAAI,MAAM,SAASC,MAAK,WAAW,MAAM,CAAC,GAAG;AAC3C,QAAI,KAAK,EAAE,MAAM,QAAQ,cAAc,WAAW,sBAAsB,GAAG,CAAC;EAC9E;AACA,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;EAC5D,QAAQ;AACN,WAAO;EACT;AACA,aAAW,KAAK,SAAS;AACvB,QAAI,CAAC,EAAE,YAAY,KAAK,EAAE,KAAK,WAAW,GAAG,EAAG;AAChD,UAAM,MAAMA,MAAK,WAAW,EAAE,IAAI;AAClC,QAAI,MAAM,SAASA,MAAK,KAAK,MAAM,CAAC,GAAG;AACrC,UAAI,KAAK,EAAE,MAAM,UAAU,cAAc,KAAK,sBAAsB,EAAE,KAAK,CAAC;IAC9E;EACF;AACA,SAAO;AACT;AAEA,eAAe,SAAS,MAAgC;AACtD,MAAI;AACF,UAAM,IAAI,MAAMC,MAAK,IAAI;AACzB,WAAO,EAAE,YAAY;EACvB,QAAQ;AACN,WAAO;EACT;AACF;AAOA,eAAsB,gBAAgB,cAAsB,MAA+B;AACzF,MAAI,YAAY;AAChB,MAAI,SAAS;AACb,SAAO,MAAM,aAAa,cAAc,SAAS,GAAG;AAClD,gBAAY,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC;AACvC,QAAI,SAAS,IAAK,OAAM,IAAI,iBAAiB,0CAA0C,IAAI,EAAE;EAC/F;AACA,SAAO;AACT;AAEA,eAAe,aAAa,cAAsB,MAAgC;AAChF,QAAM,SAAS,MAAM;IACnB;IACA,CAAC,MAAM,cAAc,YAAY,YAAY,WAAW,cAAc,IAAI,EAAE;IAC5E,EAAE,QAAQ,MAAM;EAClB;AACA,SAAO,OAAO,aAAa;AAC7B;AAEO,IAAM,mBAAN,cAA+B,MAAM;EAC1C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;EACd;AACF;ACvEO,SAAS,kBAA0B;AACxC,SAAO,QAAQ,aAAa,UAAU,aAAa;AACrD;ACQO,SAAS,wBAAwB,KAA8C;AACpF,QAAM,MAA8B,CAAC;AACrC,MAAI,IAAI,KAAM,KAAI,oBAAoB,IAAI;AAC1C,MAAI,IAAI,GAAI,KAAI,kBAAkB,IAAI;AACtC,MAAI,IAAI,KAAM,KAAI,oBAAoB,IAAI;AAC1C,MAAI,IAAI,cAAe,KAAI,0BAA0B,IAAI;AACzD,MAAI,IAAI,YAAa,KAAI,wBAAwB,IAAI;AACrD,SAAO,cAAc,GAAG;AAC1B;AAGA,SAAS,YAAY,GAAgC;AACnD,SAAO,EAAE,SAAS,WAAW,CAAC,CAAC,EAAE,gBAAgB,EAAE,SAAS,UAAU,KAAK;AAC7E;AASA,eAAsB,mBACpB,SACA,KACA,OAC+B;AAC/B,MAAI,CAAC,QAAQ,KAAK,WAAW,EAAG,QAAO;AAEvC,QAAM,UAAU,wBAAwB,GAAG;AAC3C,QAAM,QAAQ,MAAM,QAAQD,MAAK,OAAO,GAAG,wBAAwB,CAAC;AACpE,QAAM,MAA4B,CAAC;AACnC,aAAW,CAAC,GAAG,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAC1C,QAAI,CAAC,YAAY,KAAK,GAAG;AACvB,UAAI,KAAK,KAAK;AACd;IACF;AACA,UAAM,UAAU,MAAMH,UAAS,MAAM,QAAQ,MAAM;AACnD,UAAM,WAAW,kBAAkB,SAAS;MAC1C,KAAK,MAAM;MACX,OAAO,MAAM;MACb;MACA,QAAQ,CAAC,QAAQ,QAAQ,UAAU,MAAM,MAAM,KAAK,GAAG,EAAE;IAC3D,CAAC;AACD,UAAM,MAAMG,MAAK,OAAO,GAAG,OAAO,CAAC,CAAC,IAAI,SAAS,MAAM,MAAM,CAAC,EAAE;AAChE,UAAME,WAAU,KAAK,UAAU,MAAM;AACrC,QAAI,KAAK,EAAE,GAAG,OAAO,QAAQ,KAAK,OAAO,OAAO,WAAW,QAAQ,EAAE,CAAC;AACtE,UAAM,OAAO;MACX,GAAI,MAAM,cAAc,CAAC,KAAK,IAAI,CAAC;MACnC,GAAI,MAAM,SAAS,SAAS,CAAC,GAAG,OAAO,MAAM,QAAQ,MAAM,CAAC,UAAU,IAAI,CAAC;IAC7E,EAAE,KAAK,GAAG;AACV,YAAQ,mBAAmB,MAAM,MAAM,KAAK,IAAI,GAAG;EACrD;AACA,SAAO;AACT;ACrBO,SAAS,qBAAqB,UAAwC;AAC3E,SAAO,YAAYJ,SAAQ,GAAG,aAAa,GAAG,QAAQ,gBAAgB;AACxE;AAQO,SAAS,qBAAqB,UAAyC;AAC5E,QAAM,OAAO,qBAAqB,QAAQ;AAC1C,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;EAC9C,QAAQ;AACN,WAAO;EACT;AACF;AAOO,SAAS,sBAAsB,UAAgC,OAAsB;AAC1F,QAAM,OAAO,qBAAqB,QAAQ;AAC1C,YAAUC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC,IAAI;AAC9C,QAAM,MAAM,GAAG,IAAI;AACnB,gBAAc,KAAK,MAAM,EAAE,MAAM,IAAM,CAAC;AACxC,aAAW,KAAK,IAAI;AACtB;AAEA,eAAsB,aAAa,MAA+B;AAChE,QAAM,MAAM,MAAMF,UAAS,IAAI;AAC/B,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,KAAK;AACtD;AAyBA,eAAsB,qBAAqB,OAAuC;AAChF,QAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,CAAE;AACrF,QAAM,QAAQ,WAAW,QAAQ;AACjC,aAAW,KAAK,QAAQ;AACtB,UAAM,QAAQ,MAAM,aAAa,EAAE,GAAG;AACtC,UAAM,OAAO,GAAG,EAAE,GAAG,KAAK,KAAK;CAAI;EACrC;AACA,SAAO,MAAM,OAAO,KAAK;AAC3B;AAqBO,SAAS,eAAyB;AACvC,SAAO;IACL,YAAY,QAAQ,IAAI,wBAAwB;IAChD,WAAW,QAAQ,IAAI,uBAAuB;EAChD;AACF;AAmBO,IAAM,0BAA2E;EACtF,kBAAkB,EAAE,QAAQ,kBAAkB,KAAK,iBAAiB;EACpE,eAAe;IACb,QAAQ;IACR,KAAK;EACP;EACA,iCAAiC;IAC/B,QAAQ;IACR,KAAK;EACP;EACA,8BAA8B;IAC5B,QAAQ;IACR,KAAK;EACP;EACA,kCAAkC;IAChC,QAAQ;IACR,KAAK;EACP;EACA,uCAAuC;IACrC,QAAQ;IACR,KAAK;EACP;EACA,yBAAyB;IACvB,QAAQ;IACR,KAAK;EACP;EACA,mCAAmC;IACjC,QAAQ;IACR,KAAK;EACP;EACA,wCAAwC;IACtC,QAAQ;IACR,KAAK;EACP;EACA,qCAAqC;IACnC,QAAQ;IACR,KAAK;EACP;AACF;AASO,SAAS,wBACd,SACA,MACsB;AACtB,QAAM,MAAqB,CAAC;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAM,aAAa;MACjB,YAAY,KAAK,YAAY,MAAM,MAAM;MACzC,YAAY,KAAK,SAAS,MAAM,GAAG;IACrC;AACA,UAAM,MAAM,WAAW,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC;AAChD,QAAI,CAAC,IAAK,QAAO;AACjB,QAAI,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;EAC5B;AACA,SAAO;AACT;;;AC3NA,SAAS,WAAAM,WAAS,WAAAC,gBAAe;AACjC,SAAS,iBAAAC,sBAAqB;ACZ9B,SAAS,SAAAC,cAAa;AACtB,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,qBAAqB;AAC9B,SAAS,WAAAC,UAAS,eAAe;AAE1B,IAAM,oBAAoB;AAS1B,IAAM,qBAAqB;AAS3B,SAAS,kBAAkB,KAAa,WAAmB,oBAA4B;AAC5F,SAAO,GAAG,QAAQ,QAAQ,IAAI,MAAM,GAAG,EAAE,CAAC;AAC5C;AAEA,IAAM,OAAOA,SAAQ,cAAc,YAAY,GAAG,CAAC;AAcnD,SAAS,qBAA8D;AACrE,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAYD,YAAW,QAAQ,UAAU,gBAAgB,CAAC,GAAG;AAC/D,WAAO,EAAE,YAAY,QAAQ,UAAU,gBAAgB,GAAG,SAAS,SAAS;EAC9E;AACA,QAAM,SAAS,QAAQ,MAAM,MAAM,WAAW,QAAQ;AACtD,MAAIA,YAAW,QAAQ,QAAQ,gBAAgB,CAAC,GAAG;AACjD,WAAO,EAAE,YAAY,QAAQ,QAAQ,gBAAgB,GAAG,SAAS,OAAO;EAC1E;AAGA,QAAM,cAAc,QAAQ,MAAM,IAAI;AACtC,SAAO;IACL,YAAY,QAAQ,aAAa,gBAAgB;IACjD,SAAS,QAAQ,aAAa,MAAM,IAAI;EAC1C;AACF;AAEA,IAAM,EAAE,YAAY,0BAA0B,SAAS,2BAA2B,IAChF,mBAAmB;AACd,IAAM,kBAAkB;AACxB,IAAM,oBAAoB;AAEjC,eAAsB,YAAY,KAA+B;AAC/D,QAAM,SAAS,MAAMD,OAAM,UAAU,CAAC,SAAS,WAAW,GAAG,GAAG,EAAE,QAAQ,MAAM,CAAC;AACjF,SAAO,OAAO,aAAa;AAC7B;AAQA,eAAsB,UACpB,QACA,OAAgD,CAAC,GAC/B;AAClB,QAAM,aAAaA,OAAM,UAAU,CAAC,QAAQ,MAAM,GAAG;IACnD,QAAQ;IACR,QAAQ;IACR,QAAQ;EACV,CAAC;AACD,MAAI,KAAK,YAAY;AACnB,UAAM,UAAU,CAAC,UAAiC;AAChD,YAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS,MAAM;AACtE,iBAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,YAAI,KAAK,SAAS,EAAG,MAAK,aAAa,IAAI;MAC7C;IACF;AACA,eAAW,QAAQ,GAAG,QAAQ,OAAO;AACrC,eAAW,QAAQ,GAAG,QAAQ,OAAO;EACvC;AACA,QAAM,SAAS,MAAM;AACrB,SAAO,OAAO,aAAa;AAC7B;AAEA,eAAsB,SAAS,QAAgB,QAA+B;AAC5E,QAAMA,OAAM,UAAU,CAAC,OAAO,QAAQ,MAAM,CAAC;AAC/C;AAmBA,eAAsB,UAAU,MAAc,mBAAuC;AACnF,QAAM,SAAS,MAAMA;IACnB;IACA,CAAC,SAAS,WAAW,YAAY,0BAA0B,GAAG;IAC9D,EAAE,QAAQ,MAAM;EAClB;AACA,MAAI,OAAO,aAAa,EAAG,QAAO,EAAE,KAAK,QAAQ,MAAM;AACvD,QAAM,CAAC,SAAS,SAAS,IAAI,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG;AAC3D,QAAM,YAAY,UAAU,OAAO,SAAS,SAAS,EAAE,IAAI;AAC3D,SAAO;IACL;IACA,QAAQ;IACR,WAAW,OAAO,SAAS,SAAS,IAAI,YAAY;IACpD,WAAW,aAAa,UAAU,SAAS,IAAI,YAAY;EAC7D;AACF;AASA,eAAsB,WAAW,OAA0B,CAAC,GAAoB;AAC9E,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,aAAa,KAAK,cAAc;AAMtC,QAAM,OAAO,CAAC,SAAS,MAAM,KAAK,MAAM,YAAY,UAAU;AAC9D,MAAI,QAAQ,IAAI,aAAa,KAAK;AAChC,SAAK,OAAO,GAAG,GAAG,gBAAgB;EACpC;AAEA,QAAM,aAAaA,OAAM,UAAU,MAAM;IACvC,QAAQ;IACR,QAAQ;EACV,CAAC;AAED,MAAI,KAAK,YAAY;AACnB,UAAM,UAAU,CAAC,UAAiC;AAChD,YAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,MAAM,SAAS,MAAM;AACtE,iBAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,YAAI,KAAK,SAAS,EAAG,MAAK,aAAa,IAAI;MAC7C;IACF;AACA,eAAW,QAAQ,GAAG,QAAQ,OAAO;AACrC,eAAW,QAAQ,GAAG,QAAQ,OAAO;EACvC;AAEA,QAAM;AACN,SAAO;AACT;AAuBA,eAAsB,YACpB,KACA,aACA,OAA2B,CAAC,GACa;AACzC,QAAM,EAAE,0BAAAG,0BAAyB,IAAI,MAAM,OAAO,uCAAqB;AACvE,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,YAAY,KAAK,cAAc;AAErC,MAAI,aAAa,YAAY,aAAa;AACxC,UAAM,SAAS,kBAAkB,YAAY,eAAe,QAAQ;AACpE,SAAK,aAAa,mBAAmB,MAAM,EAAE;AAC7C,QAAI,MAAM,UAAU,QAAQ,EAAE,YAAY,KAAK,WAAW,CAAC,GAAG;AAC5D,YAAM,SAAS,QAAQ,GAAG;AAC1BA,gCAAyB,EAAE,UAAU,KAAK,eAAe,YAAY,cAAc,CAAC;AACpF,WAAK,aAAa,kBAAkB,MAAM,OAAO,GAAG,EAAE;AACtD,aAAO,EAAE,QAAQ,SAAS;IAC5B;AACA,SAAK,aAAa,mCAAmC,GAAG,UAAU;EACpE;AAEA,QAAM,WAAW;IACf;IACA,YAAY,KAAK;IACjB,YAAY,KAAK;IACjB,YAAY,KAAK;EACnB,CAAC;AACD,MAAI,aAAa;AACfA,8BAAyB,EAAE,UAAU,KAAK,eAAe,YAAY,cAAc,CAAC;EACtF;AACA,SAAO,EAAE,QAAQ,QAAQ;AAC3B;AAcA,eAAsB,YACpB,MAAc,mBACd,OAA2B,CAAC,GAC+B;AAI3D,QAAM,EAAE,iCAAAC,kCAAiC,yBAAAC,0BAAyB,iBAAAC,iBAAgB,IAChF,MAAM,OAAO,uCAAqB;AAEpC,QAAM,cAAc,MAAMF,iCAAgC;IACxD,YAAY,KAAK;EACnB,CAAC;AACD,QAAM,WAAWC,yBAAwB;AACzC,QAAM,SAAS,MAAM,YAAY,GAAG;AAEpC,MAAI;AACJ,MAAI,CAAC,QAAQ;AACX,aAAS,SAAS,GAAG;EACvB,WAAW,CAAC,aAAa;AAIvB,WAAO,EAAE,KAAK,OAAO,OAAO,QAAQ,sCAAsC;EAC5E,WAAW,CAAC,UAAU;AACpB,aAAS;EACX,WAAW,CAACC,iBAAgB,UAAU,YAAY,aAAa,GAAG;AAChE,aACE,8BAA8B,SAAS,MAAM,eAAe,MAAM,GAAG,EAAE,KAAK,QAAQ,SAC7E,YAAY,cAAc,MAAM,GAAG,EAAE,CAAC;EACjD;AAEA,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,KAAK,OAAO,OAAO,QAAQ,mBAAmB;EACzD;AAEA,OAAK,aAAa,WAAW,GAAG,KAAK,MAAM,EAAE;AAC7C,QAAM,EAAE,OAAO,IAAI,MAAM,YAAY,KAAK,aAAa;IACrD,YAAY,KAAK;IACjB,YAAY,KAAK;IACjB,YAAY,KAAK;IACjB,WAAW,KAAK;IAChB,UAAU,KAAK;EACjB,CAAC;AACD,SAAO,EAAE,KAAK,OAAO,WAAW,SAAS,OAAO;AAClD;ADvQA,IAAM,SAAS;AAgBR,SAAS,oBAAoB,OAAgC,CAAC,GAAyB;AAC5F,QAAM,MAAM,KAAK,cAAc;AAC/B,QAAMC,QAAOL,UAAQM,eAAc,YAAY,GAAG,CAAC;AAEnD,QAAM,cAAcC,SAAQF,OAAM,IAAI;AACtC,SAAO,wBAAwB,yBAAyB;IACtD,YAAY;IACZ,SAAS;EACX,CAAC;AACH;AAQA,eAAsB,gCAAgC,OAElD,CAAC,GAAwC;AAC3C,QAAM,QAAQ,oBAAoB,IAAI;AACtC,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,EAAE,eAAe,MAAM,qBAAqB,KAAK,GAAG,MAAM;AACnE;AAEO,SAAS,0BAAsD;AACpE,QAAM,MAAM,qBAAqB,QAAQ;AACzC,MAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU,QAAO;AACpD,QAAM,SAAS;AACf,MAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,SAAO,EAAE,QAAQ,QAAQ,MAAM,OAAO,KAAK;AAC7C;AAEO,SAAS,yBAAyB,MAGhC;AACP,QAAM,QAAQ,aAAa;AAC3B,QAAM,QAA6B;IACjC,QAAQ;IACR,MAAM;MACJ,UAAU,KAAK,YAAY;MAC3B,eAAe,KAAK;MACpB,YAAY,MAAM;MAClB,WAAW,MAAM;MACjB,YAAW,oBAAI,KAAK,GAAE,YAAY;IACpC;EACF;AACA,wBAAsB,UAAU,KAAK;AACvC;AAGO,SAAS,gBAAgB,OAAmC,SAA0B;AAC3F,SAAO,OAAO,MAAM,kBAAkB;AACxC;","names":["stat","join","readFile","writeFile","readFile","homedir","dirname","join","stat","writeFile","dirname","resolve","fileURLToPath","execa","existsSync","dirname","writePreparedDockerState","computeDockerContextFingerprint","readPreparedDockerState","preparedMatches","here","fileURLToPath","resolve"]}
|