@madarco/agentbox 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/{_cloud-attach-O6NYTLES.js → _cloud-attach-2DGI6FUA.js} +4 -4
  3. package/dist/{chunk-7UIAO7PC.js → chunk-CDKVD6UO.js} +27 -11
  4. package/dist/chunk-CDKVD6UO.js.map +1 -0
  5. package/dist/{chunk-R4O5WPHW.js → chunk-I7NOGCL4.js} +6 -6
  6. package/dist/{chunk-2GPORKYF.js → chunk-M2UWJKFA.js} +3 -3
  7. package/dist/{chunk-KL36BRN4.js → chunk-PWUVHPN6.js} +66 -17
  8. package/dist/{chunk-KL36BRN4.js.map → chunk-PWUVHPN6.js.map} +1 -1
  9. package/dist/{dist-TMHSUVTP.js → dist-BD5QJRDC.js} +4 -4
  10. package/dist/{dist-5FQGYRW5.js → dist-BNI5PQYK.js} +4 -4
  11. package/dist/{dist-BQNX7RQE.js → dist-SBCQVFCE.js} +13 -3
  12. package/dist/{dist-PZW3GWWU.js → dist-SJHY3HYN.js} +4 -4
  13. package/dist/index.js +35 -23
  14. package/dist/index.js.map +1 -1
  15. package/dist/{prepared-state-CL4CWXQA-H5THETIM.js → prepared-state-MQHD3M5F-O5M4NIN4.js} +2 -2
  16. package/package.json +5 -5
  17. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
  18. package/runtime/docker/packages/ctl/dist/bin.cjs +6 -0
  19. package/runtime/hetzner/agentbox-setup-skill.md +1 -1
  20. package/runtime/hetzner/ctl.cjs +6 -0
  21. package/runtime/relay/bin.cjs +9 -0
  22. package/runtime/vercel/agentbox-setup-skill.md +1 -1
  23. package/runtime/vercel/ctl.cjs +6 -0
  24. package/share/agentbox-setup/SKILL.md +1 -1
  25. package/dist/chunk-7UIAO7PC.js.map +0 -1
  26. /package/dist/{_cloud-attach-O6NYTLES.js.map → _cloud-attach-2DGI6FUA.js.map} +0 -0
  27. /package/dist/{chunk-R4O5WPHW.js.map → chunk-I7NOGCL4.js.map} +0 -0
  28. /package/dist/{chunk-2GPORKYF.js.map → chunk-M2UWJKFA.js.map} +0 -0
  29. /package/dist/{dist-TMHSUVTP.js.map → dist-BD5QJRDC.js.map} +0 -0
  30. /package/dist/{dist-5FQGYRW5.js.map → dist-BNI5PQYK.js.map} +0 -0
  31. /package/dist/{dist-BQNX7RQE.js.map → dist-SBCQVFCE.js.map} +0 -0
  32. /package/dist/{dist-PZW3GWWU.js.map → dist-SJHY3HYN.js.map} +0 -0
  33. /package/dist/{prepared-state-CL4CWXQA-H5THETIM.js.map → prepared-state-MQHD3M5F-O5M4NIN4.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  DEFAULT_RELAY_PORT,
4
4
  readBoxStatus
5
- } from "./chunk-7UIAO7PC.js";
5
+ } from "./chunk-CDKVD6UO.js";
6
6
 
7
7
  // src/commands/_cloud-attach.ts
8
8
  import { spawn as spawn3 } from "child_process";
@@ -19,21 +19,21 @@ function isKnownProvider(name) {
19
19
  async function getProvider(name) {
20
20
  switch (name) {
21
21
  case "docker": {
22
- const mod = await import("./dist-BQNX7RQE.js");
22
+ const mod = await import("./dist-SBCQVFCE.js");
23
23
  return mod.dockerProvider;
24
24
  }
25
25
  case "daytona": {
26
- const mod = await import("./dist-TMHSUVTP.js");
26
+ const mod = await import("./dist-BD5QJRDC.js");
27
27
  await mod.ensureDaytonaCredentials();
28
28
  return mod.daytonaProvider;
29
29
  }
30
30
  case "hetzner": {
31
- const mod = await import("./dist-5FQGYRW5.js");
31
+ const mod = await import("./dist-BNI5PQYK.js");
32
32
  await mod.ensureHetznerCredentials();
33
33
  return mod.hetznerProvider;
34
34
  }
35
35
  case "vercel": {
36
- const mod = await import("./dist-PZW3GWWU.js");
36
+ const mod = await import("./dist-SJHY3HYN.js");
37
37
  await mod.ensureVercelCredentials();
38
38
  return mod.vercelProvider;
39
39
  }
@@ -1825,4 +1825,4 @@ export {
1825
1825
  buildCloudAttachInnerCommand,
1826
1826
  cloudAgentAttach
1827
1827
  };
1828
- //# sourceMappingURL=chunk-R4O5WPHW.js.map
1828
+ //# sourceMappingURL=chunk-I7NOGCL4.js.map
@@ -27,14 +27,14 @@ import {
27
27
  stageOpencodeCredentialsForUpload,
28
28
  stageOpencodeStateForUpload,
29
29
  stageOpencodeStaticForUpload
30
- } from "./chunk-7UIAO7PC.js";
30
+ } from "./chunk-CDKVD6UO.js";
31
31
  import {
32
32
  allocateProjectIndex,
33
33
  detectGitRepos,
34
34
  readState,
35
35
  recordBox,
36
36
  removeBoxRecord
37
- } from "./chunk-KL36BRN4.js";
37
+ } from "./chunk-PWUVHPN6.js";
38
38
 
39
39
  // ../../packages/sandbox-cloud/dist/index.js
40
40
  import { randomBytes } from "crypto";
@@ -1804,4 +1804,4 @@ export {
1804
1804
  createCloudProvider,
1805
1805
  renderInnerCommand
1806
1806
  };
1807
- //# sourceMappingURL=chunk-2GPORKYF.js.map
1807
+ //# sourceMappingURL=chunk-M2UWJKFA.js.map
@@ -271,7 +271,7 @@ function resolveContextFilesFrom(fileMap, opts) {
271
271
  return out;
272
272
  }
273
273
 
274
- // ../../packages/sandbox-docker/dist/chunk-OZU3LVKX.js
274
+ // ../../packages/sandbox-docker/dist/chunk-WAKY6O4I.js
275
275
  import { dirname as dirname22, resolve as resolve2 } from "path";
276
276
  import { fileURLToPath as fileURLToPath2 } from "url";
277
277
  import { execa as execa2 } from "execa";
@@ -279,6 +279,10 @@ import { existsSync as existsSync2 } from "fs";
279
279
  import { fileURLToPath } from "url";
280
280
  import { dirname as dirname3, resolve } from "path";
281
281
  var DEFAULT_BOX_IMAGE = "agentbox/box:dev";
282
+ var BOX_IMAGE_REGISTRY = "ghcr.io/madarco/agentbox/box";
283
+ function registryRefForSha(sha, registry = BOX_IMAGE_REGISTRY) {
284
+ return `${registry}:sha-${sha.slice(0, 16)}`;
285
+ }
282
286
  var here = dirname3(fileURLToPath(import.meta.url));
283
287
  function resolveDockerBuild() {
284
288
  const override = process.env.AGENTBOX_DOCKER_CONTEXT;
@@ -302,6 +306,28 @@ async function imageExists(ref) {
302
306
  const result = await execa2("docker", ["image", "inspect", ref], { reject: false });
303
307
  return result.exitCode === 0;
304
308
  }
309
+ async function pullImage(target, opts = {}) {
310
+ const subprocess = execa2("docker", ["pull", target], {
311
+ stderr: "pipe",
312
+ stdout: "pipe",
313
+ reject: false
314
+ });
315
+ if (opts.onProgress) {
316
+ const forward = (chunk) => {
317
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
318
+ for (const line of text.split(/\r?\n/)) {
319
+ if (line.length > 0) opts.onProgress?.(line);
320
+ }
321
+ };
322
+ subprocess.stdout?.on("data", forward);
323
+ subprocess.stderr?.on("data", forward);
324
+ }
325
+ const result = await subprocess;
326
+ return result.exitCode === 0;
327
+ }
328
+ async function tagImage(source, target) {
329
+ await execa2("docker", ["tag", source, target]);
330
+ }
305
331
  async function imageInfo(ref = DEFAULT_BOX_IMAGE) {
306
332
  const result = await execa2(
307
333
  "docker",
@@ -343,13 +369,34 @@ async function buildImage(opts = {}) {
343
369
  await subprocess;
344
370
  return ref;
345
371
  }
372
+ async function pullOrBuild(ref, fingerprint, opts = {}) {
373
+ const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-O5M4NIN4.js");
374
+ const registry = opts.registry ?? BOX_IMAGE_REGISTRY;
375
+ const allowPull = opts.allowPull !== false;
376
+ if (allowPull && registry && fingerprint) {
377
+ const target = registryRefForSha(fingerprint.contextSha256, registry);
378
+ opts.onProgress?.(`[image] pulling ${target}`);
379
+ if (await pullImage(target, { onProgress: opts.onProgress })) {
380
+ await tagImage(target, ref);
381
+ writePreparedDockerState2({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
382
+ opts.onProgress?.(`[image] pulled ${target} -> ${ref}`);
383
+ return { source: "pulled" };
384
+ }
385
+ opts.onProgress?.(`[image] registry miss, building ${ref} locally`);
386
+ }
387
+ await buildImage({
388
+ ref,
389
+ dockerfile: opts.dockerfile,
390
+ contextDir: opts.contextDir,
391
+ onProgress: opts.onProgress
392
+ });
393
+ if (fingerprint) {
394
+ writePreparedDockerState2({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
395
+ }
396
+ return { source: "built" };
397
+ }
346
398
  async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
347
- const {
348
- computeDockerContextFingerprint: computeDockerContextFingerprint2,
349
- readPreparedDockerState: readPreparedDockerState2,
350
- writePreparedDockerState: writePreparedDockerState2,
351
- preparedMatches: preparedMatches2
352
- } = await import("./prepared-state-CL4CWXQA-H5THETIM.js");
399
+ const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-O5M4NIN4.js");
353
400
  const fingerprint = await computeDockerContextFingerprint2({
354
401
  contextDir: opts.contextDir
355
402
  });
@@ -368,17 +415,15 @@ async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
368
415
  if (!reason) {
369
416
  return { ref, built: false, reason: "image up to date" };
370
417
  }
371
- opts.onProgress?.(`[image] rebuilding ${ref}: ${reason}`);
372
- await buildImage({
373
- ref,
418
+ opts.onProgress?.(`[image] ${ref}: ${reason}`);
419
+ const { source } = await pullOrBuild(ref, fingerprint, {
420
+ onProgress: opts.onProgress,
374
421
  dockerfile: opts.dockerfile,
375
422
  contextDir: opts.contextDir,
376
- onProgress: opts.onProgress
423
+ allowPull: opts.allowPull,
424
+ registry: opts.registry
377
425
  });
378
- if (fingerprint) {
379
- writePreparedDockerState2({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
380
- }
381
- return { ref, built: true, reason };
426
+ return { ref, built: source === "built", reason };
382
427
  }
383
428
  var SCHEMA = 1;
384
429
  function resolveContextFiles(opts = {}) {
@@ -441,10 +486,14 @@ export {
441
486
  DOCKER_CONTEXT_FILE_MAP,
442
487
  resolveContextFilesFrom,
443
488
  DEFAULT_BOX_IMAGE,
489
+ BOX_IMAGE_REGISTRY,
490
+ registryRefForSha,
444
491
  DOCKERFILE_PATH,
445
492
  imageExists,
493
+ pullImage,
494
+ tagImage,
446
495
  imageInfo,
447
- buildImage,
496
+ pullOrBuild,
448
497
  ensureImage,
449
498
  resolveContextFiles,
450
499
  computeDockerContextFingerprint,
@@ -452,4 +501,4 @@ export {
452
501
  writePreparedDockerState,
453
502
  preparedMatches
454
503
  };
455
- //# sourceMappingURL=chunk-KL36BRN4.js.map
504
+ //# sourceMappingURL=chunk-PWUVHPN6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../packages/sandbox-core/src/state.ts","../../../packages/sandbox-core/src/git-detect.ts","../../../packages/sandbox-core/src/prepared-state.ts","../../../packages/sandbox-docker/src/prepared-state.ts","../../../packages/sandbox-docker/src/image.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\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\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 await writeFile(path, JSON.stringify(state, null, 2) + '\\n', 'utf8');\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 const state = await readState(path);\n const next: StateFile = {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n };\n await writeState(next, path);\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 const state = await readState(path);\n const before = state.boxes.length;\n const next: StateFile = {\n version: 1,\n boxes: state.boxes.filter((b) => b.id !== id),\n };\n if (next.boxes.length === before) return false;\n await writeState(next, path);\n return true;\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 * 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';\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\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\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 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}\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 {\n computeDockerContextFingerprint,\n readPreparedDockerState,\n writePreparedDockerState,\n 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] rebuilding ${ref}: ${reason}`);\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 { ref, built: true, reason };\n}\n\n"],"mappings":"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","names":["join","readFile","homedir","dirname","dirname","resolve","fileURLToPath","execa","existsSync","dirname","computeDockerContextFingerprint","readPreparedDockerState","writePreparedDockerState","preparedMatches","here","fileURLToPath","resolve"]}
1
+ {"version":3,"sources":["../../../packages/sandbox-core/src/state.ts","../../../packages/sandbox-core/src/git-detect.ts","../../../packages/sandbox-core/src/prepared-state.ts","../../../packages/sandbox-docker/src/prepared-state.ts","../../../packages/sandbox-docker/src/image.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\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\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 await writeFile(path, JSON.stringify(state, null, 2) + '\\n', 'utf8');\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 const state = await readState(path);\n const next: StateFile = {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n };\n await writeState(next, path);\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 const state = await readState(path);\n const before = state.boxes.length;\n const next: StateFile = {\n version: 1,\n boxes: state.boxes.filter((b) => b.id !== id),\n };\n if (next.boxes.length === before) return false;\n await writeState(next, path);\n return true;\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 * 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';\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":"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","names":["join","readFile","homedir","dirname","dirname","resolve","fileURLToPath","execa","existsSync","dirname","writePreparedDockerState","computeDockerContextFingerprint","readPreparedDockerState","preparedMatches","here","fileURLToPath","resolve"]}
@@ -10,12 +10,12 @@ import {
10
10
  } from "./chunk-LEV3KICD.js";
11
11
  import {
12
12
  createCloudProvider
13
- } from "./chunk-2GPORKYF.js";
13
+ } from "./chunk-M2UWJKFA.js";
14
14
  import {
15
15
  stageClaudeStaticForUpload,
16
16
  stageCodexStaticForUpload,
17
17
  stageOpencodeStaticForUpload
18
- } from "./chunk-7UIAO7PC.js";
18
+ } from "./chunk-CDKVD6UO.js";
19
19
  import {
20
20
  DOCKER_CONTEXT_FILE_MAP,
21
21
  computeContextSha256,
@@ -23,7 +23,7 @@ import {
23
23
  readPreparedStateRaw,
24
24
  resolveContextFilesFrom,
25
25
  writePreparedStateRaw
26
- } from "./chunk-KL36BRN4.js";
26
+ } from "./chunk-PWUVHPN6.js";
27
27
  import "./chunk-G3H2L3O2.js";
28
28
 
29
29
  // ../../packages/sandbox-daytona/dist/index.js
@@ -290,4 +290,4 @@ export {
290
290
  getDaytonaStatus,
291
291
  resolveDockerfileContext
292
292
  };
293
- //# sourceMappingURL=dist-TMHSUVTP.js.map
293
+ //# sourceMappingURL=dist-BD5QJRDC.js.map
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-I24B6AXR.js";
21
21
  import {
22
22
  createCloudProvider
23
- } from "./chunk-2GPORKYF.js";
23
+ } from "./chunk-M2UWJKFA.js";
24
24
  import {
25
25
  stageClaudeCredentialsForUpload,
26
26
  stageClaudeStaticForUpload,
@@ -28,14 +28,14 @@ import {
28
28
  stageCodexStaticForUpload,
29
29
  stageOpencodeCredentialsForUpload,
30
30
  stageOpencodeStaticForUpload
31
- } from "./chunk-7UIAO7PC.js";
31
+ } from "./chunk-CDKVD6UO.js";
32
32
  import {
33
33
  computeContextSha256,
34
34
  preparedStatePathFor,
35
35
  readCliStamp,
36
36
  readPreparedStateRaw,
37
37
  writePreparedStateRaw
38
- } from "./chunk-KL36BRN4.js";
38
+ } from "./chunk-PWUVHPN6.js";
39
39
  import "./chunk-G3H2L3O2.js";
40
40
 
41
41
  // ../../packages/sandbox-hetzner/dist/index.js
@@ -1414,4 +1414,4 @@ export {
1414
1414
  withHetznerRetry,
1415
1415
  writePreparedState
1416
1416
  };
1417
- //# sourceMappingURL=dist-5FQGYRW5.js.map
1417
+ //# sourceMappingURL=dist-BNI5PQYK.js.map
@@ -206,8 +206,9 @@ import {
206
206
  vscodeServerVolumeName,
207
207
  waitForTmuxPaneContent,
208
208
  warmUpClaudeCredentials
209
- } from "./chunk-7UIAO7PC.js";
209
+ } from "./chunk-CDKVD6UO.js";
210
210
  import {
211
+ BOX_IMAGE_REGISTRY,
211
212
  DEFAULT_BOX_IMAGE,
212
213
  GitWorktreeError,
213
214
  STATE_DIR,
@@ -222,18 +223,23 @@ import {
222
223
  imageInfo,
223
224
  pickFreshBranch,
224
225
  preparedMatches,
226
+ pullImage,
227
+ pullOrBuild,
225
228
  readPreparedDockerState,
226
229
  readState,
227
230
  recordBox,
231
+ registryRefForSha,
228
232
  removeBoxRecord,
229
233
  resolveBoxRef,
230
234
  resolveContextFiles,
235
+ tagImage,
231
236
  writePreparedDockerState
232
- } from "./chunk-KL36BRN4.js";
237
+ } from "./chunk-PWUVHPN6.js";
233
238
  import "./chunk-G3H2L3O2.js";
234
239
  export {
235
240
  AmbiguousBoxError,
236
241
  BOXES_ROOT,
242
+ BOX_IMAGE_REGISTRY,
237
243
  BoxNotFoundError,
238
244
  CHECKPOINTS_ROOT,
239
245
  CHECKPOINT_IMAGE_PREFIX,
@@ -392,7 +398,9 @@ export {
392
398
  pruneBoxes,
393
399
  pullClaudeExtras,
394
400
  pullCodexConfig,
401
+ pullImage,
395
402
  pullOpencodeConfig,
403
+ pullOrBuild,
396
404
  pullToHost,
397
405
  readBoxStatus,
398
406
  readPreparedDockerState,
@@ -401,6 +409,7 @@ export {
401
409
  recordBox,
402
410
  refreshExport,
403
411
  registerBoxWithRelay,
412
+ registryRefForSha,
404
413
  rehydrateRelayRegistry,
405
414
  removeBoxRecord,
406
415
  removeCheckpoint,
@@ -449,6 +458,7 @@ export {
449
458
  stopBox,
450
459
  stopRelay,
451
460
  syncClaudeCredentials,
461
+ tagImage,
452
462
  unpauseBox,
453
463
  uploadToBox,
454
464
  volumeExists,
@@ -460,4 +470,4 @@ export {
460
470
  warmUpClaudeCredentials,
461
471
  writePreparedDockerState
462
472
  };
463
- //# sourceMappingURL=dist-BQNX7RQE.js.map
473
+ //# sourceMappingURL=dist-SBCQVFCE.js.map
@@ -19,7 +19,7 @@ import {
19
19
  renderInnerCommand,
20
20
  resolveCloudCheckpoint,
21
21
  writeCloudCheckpointManifest
22
- } from "./chunk-2GPORKYF.js";
22
+ } from "./chunk-M2UWJKFA.js";
23
23
  import {
24
24
  stageClaudeCredentialsForUpload,
25
25
  stageClaudeStaticForUpload,
@@ -27,7 +27,7 @@ import {
27
27
  stageCodexStaticForUpload,
28
28
  stageOpencodeCredentialsForUpload,
29
29
  stageOpencodeStaticForUpload
30
- } from "./chunk-7UIAO7PC.js";
30
+ } from "./chunk-CDKVD6UO.js";
31
31
  import {
32
32
  computeContextSha256,
33
33
  preparedStatePathFor,
@@ -35,7 +35,7 @@ import {
35
35
  readPreparedStateRaw,
36
36
  recordBox,
37
37
  writePreparedStateRaw
38
- } from "./chunk-KL36BRN4.js";
38
+ } from "./chunk-PWUVHPN6.js";
39
39
  import "./chunk-G3H2L3O2.js";
40
40
 
41
41
  // ../../packages/sandbox-vercel/dist/index.js
@@ -871,4 +871,4 @@ export {
871
871
  vercelProvider,
872
872
  writePreparedState
873
873
  };
874
- //# sourceMappingURL=dist-PZW3GWWU.js.map
874
+ //# sourceMappingURL=dist-SJHY3HYN.js.map