#!/usr/bin/env node

// ../../packages/sandbox-core/dist/index.js
import { mkdir, readFile, writeFile } from "fs/promises";
import { homedir } from "os";
import { dirname, join } from "path";
import { execa } from "execa";
import { readdir, stat } from "fs/promises";
import { join as join2 } from "path";
import { createHash } from "crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
import { readFile as readFile2 } from "fs/promises";
import { homedir as homedir2 } from "os";
import { dirname as dirname2, resolve as pathResolve } from "path";
var STATE_DIR = join(homedir(), ".agentbox");
var STATE_FILE = join(STATE_DIR, "state.json");
var EMPTY = { version: 1, boxes: [] };
async function readState(path = STATE_FILE) {
  try {
    const raw = await readFile(path, "utf8");
    const parsed = JSON.parse(raw);
    if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {
      throw new Error(`unrecognized state file shape at ${path}`);
    }
    for (const b of parsed.boxes) {
      b.provider ??= "docker";
      if ((b.provider ?? "docker") === "docker" && !b.docker) {
        b.docker = projectDockerFields(b);
      }
    }
    return parsed;
  } catch (err) {
    if (err.code === "ENOENT") {
      return { ...EMPTY };
    }
    throw err;
  }
}
async function writeState(state, path = STATE_FILE) {
  await mkdir(dirname(path), { recursive: true });
  await writeFile(path, JSON.stringify(state, null, 2) + "\n", "utf8");
}
async function recordBox(box, path = STATE_FILE) {
  const toWrite = (box.provider ?? "docker") === "docker" && !box.docker ? { ...box, docker: projectDockerFields(box) } : box;
  const state = await readState(path);
  const next = {
    version: 1,
    boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite]
  };
  await writeState(next, path);
}
function projectDockerFields(box) {
  return {
    container: box.container,
    image: box.image,
    snapshotDir: box.snapshotDir ?? null,
    socketPath: box.socketPath,
    claudeConfigVolume: box.claudeConfigVolume,
    codexConfigVolume: box.codexConfigVolume,
    opencodeConfigVolume: box.opencodeConfigVolume,
    vscodeServerVolume: box.vscodeServerVolume,
    cursorServerVolume: box.cursorServerVolume,
    vncHostPort: box.vncHostPort,
    webHostPort: box.webHostPort,
    portlessAlias: box.portlessAlias,
    portlessUrl: box.portlessUrl,
    portlessVncAlias: box.portlessVncAlias,
    portlessVncUrl: box.portlessVncUrl,
    dockerVolume: box.dockerVolume,
    dockerCacheShared: box.dockerCacheShared,
    checkpointImage: box.checkpointImage
  };
}
async function removeBoxRecord(id, path = STATE_FILE) {
  const state = await readState(path);
  const before = state.boxes.length;
  const next = {
    version: 1,
    boxes: state.boxes.filter((b) => b.id !== id)
  };
  if (next.boxes.length === before) return false;
  await writeState(next, path);
  return true;
}
function findBox(idOrName, state) {
  const q = idOrName.trim();
  if (q.length === 0) return { kind: "none" };
  const exactId = state.boxes.find((b) => b.id === q);
  if (exactId) return { kind: "ok", box: exactId };
  const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));
  if (prefixMatches.length === 1) return { kind: "ok", box: prefixMatches[0] };
  if (prefixMatches.length > 1) return { kind: "ambiguous", matches: prefixMatches };
  const byName = state.boxes.find((b) => b.name === q);
  if (byName) return { kind: "ok", box: byName };
  const byContainer = state.boxes.find((b) => b.container === q);
  if (byContainer) return { kind: "ok", box: byContainer };
  return { kind: "none" };
}
function allocateProjectIndex(state, projectRoot) {
  let max = 0;
  for (const b of state.boxes) {
    if (b.projectRoot !== projectRoot) continue;
    if (typeof b.projectIndex === "number" && b.projectIndex > max) {
      max = b.projectIndex;
    }
  }
  return max + 1;
}
function autoPickProjectBox(state, projectRoot) {
  const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);
  if (matches.length === 0) return { kind: "none" };
  if (matches.length === 1) return { kind: "ok", box: matches[0] };
  return { kind: "ambiguous", matches };
}
function resolveBoxRef(ref, state, projectRoot) {
  if (ref === void 0) {
    if (projectRoot === void 0) return { kind: "none" };
    return autoPickProjectBox(state, projectRoot);
  }
  const trimmed = ref.trim();
  if (projectRoot !== void 0 && /^[1-9][0-9]*$/.test(trimmed)) {
    const idx = Number.parseInt(trimmed, 10);
    const hit = state.boxes.find(
      (b) => b.projectRoot === projectRoot && b.projectIndex === idx
    );
    return hit ? { kind: "ok", box: hit } : { kind: "none" };
  }
  return findBox(trimmed, state);
}
async function detectGitRepos(workspace) {
  const out = [];
  if (await isGitDir(join2(workspace, ".git"))) {
    out.push({ kind: "root", hostMainRepo: workspace, relPathFromWorkspace: "" });
  }
  let entries;
  try {
    entries = await readdir(workspace, { withFileTypes: true });
  } catch {
    return out;
  }
  for (const e of entries) {
    if (!e.isDirectory() || e.name.startsWith(".")) continue;
    const sub = join2(workspace, e.name);
    if (await isGitDir(join2(sub, ".git"))) {
      out.push({ kind: "nested", hostMainRepo: sub, relPathFromWorkspace: e.name });
    }
  }
  return out;
}
async function isGitDir(path) {
  try {
    const s = await stat(path);
    return s.isDirectory();
  } catch {
    return false;
  }
}
async function pickFreshBranch(hostMainRepo, base) {
  let candidate = base;
  let suffix = 2;
  while (await branchExists(hostMainRepo, candidate)) {
    candidate = `${base}-${String(suffix++)}`;
    if (suffix > 100) throw new GitWorktreeError(`could not find a free branch name near ${base}`);
  }
  return candidate;
}
async function branchExists(hostMainRepo, name) {
  const result = await execa(
    "git",
    ["-C", hostMainRepo, "show-ref", "--verify", "--quiet", `refs/heads/${name}`],
    { reject: false }
  );
  return result.exitCode === 0;
}
var GitWorktreeError = class extends Error {
  constructor(message) {
    super(message);
    this.name = "GitWorktreeError";
  }
};
function hostOpenCommand() {
  return process.platform === "linux" ? "xdg-open" : "open";
}
function preparedStatePathFor(provider) {
  return pathResolve(homedir2(), ".agentbox", `${provider}-prepared.json`);
}
function readPreparedStateRaw(provider) {
  const path = preparedStatePathFor(provider);
  if (!existsSync(path)) return null;
  try {
    return JSON.parse(readFileSync(path, "utf8"));
  } catch {
    return null;
  }
}
function writePreparedStateRaw(provider, state) {
  const path = preparedStatePathFor(provider);
  mkdirSync(dirname2(path), { recursive: true });
  const body = JSON.stringify(state, null, 2) + "\n";
  const tmp = `${path}.tmp`;
  writeFileSync(tmp, body, { mode: 384 });
  renameSync(tmp, path);
}
async function sha256OfFile(path) {
  const buf = await readFile2(path);
  return createHash("sha256").update(buf).digest("hex");
}
async function computeContextSha256(files) {
  const sorted = [...files].sort((a, b) => a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0);
  const outer = createHash("sha256");
  for (const f of sorted) {
    const inner = await sha256OfFile(f.abs);
    outer.update(`${f.rel}\0${inner}
`);
  }
  return outer.digest("hex");
}
function readCliStamp() {
  return {
    cliVersion: process.env.AGENTBOX_CLI_VERSION ?? "unknown",
    cliCommit: process.env.AGENTBOX_CLI_COMMIT ?? "unknown"
  };
}
var DOCKER_CONTEXT_FILE_MAP = {
  "Dockerfile.box": { staged: "Dockerfile.box", dev: "Dockerfile.box" },
  "ctl/bin.cjs": {
    staged: "packages/ctl/dist/bin.cjs",
    dev: "../ctl/dist/bin.cjs"
  },
  "share/agentbox-setup/SKILL.md": {
    staged: "apps/cli/share/agentbox-setup/SKILL.md",
    dev: "../../apps/cli/share/agentbox-setup/SKILL.md"
  },
  "scripts/agentbox-vnc-start": {
    staged: "packages/sandbox-docker/scripts/agentbox-vnc-start",
    dev: "scripts/agentbox-vnc-start"
  },
  "scripts/agentbox-dockerd-start": {
    staged: "packages/sandbox-docker/scripts/agentbox-dockerd-start",
    dev: "scripts/agentbox-dockerd-start"
  },
  "scripts/agentbox-checkpoint-cleanup": {
    staged: "packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup",
    dev: "scripts/agentbox-checkpoint-cleanup"
  },
  "scripts/agentbox-open": {
    staged: "packages/sandbox-docker/scripts/agentbox-open",
    dev: "scripts/agentbox-open"
  },
  "scripts/custom-system-CLAUDE.md": {
    staged: "packages/sandbox-docker/scripts/custom-system-CLAUDE.md",
    dev: "scripts/custom-system-CLAUDE.md"
  },
  "scripts/claude-managed-settings.json": {
    staged: "packages/sandbox-docker/scripts/claude-managed-settings.json",
    dev: "scripts/claude-managed-settings.json"
  },
  "scripts/agentbox-codex-hooks.json": {
    staged: "packages/sandbox-docker/scripts/agentbox-codex-hooks.json",
    dev: "scripts/agentbox-codex-hooks.json"
  }
};
function resolveContextFilesFrom(fileMap, opts) {
  const out = [];
  for (const [rel, paths] of Object.entries(fileMap)) {
    const candidates = [
      pathResolve(opts.contextDir, paths.staged),
      pathResolve(opts.devRoot, paths.dev)
    ];
    const hit = candidates.find((p) => existsSync(p));
    if (!hit) return null;
    out.push({ rel, abs: hit });
  }
  return out;
}

// ../../packages/sandbox-docker/dist/chunk-WAKY6O4I.js
import { dirname as dirname22, resolve as resolve2 } from "path";
import { fileURLToPath as fileURLToPath2 } from "url";
import { execa as execa2 } from "execa";
import { existsSync as existsSync2 } from "fs";
import { fileURLToPath } from "url";
import { dirname as dirname3, resolve } from "path";
var DEFAULT_BOX_IMAGE = "agentbox/box:dev";
var BOX_IMAGE_REGISTRY = "ghcr.io/madarco/agentbox/box";
function registryRefForSha(sha, registry = BOX_IMAGE_REGISTRY) {
  return `${registry}:sha-${sha.slice(0, 16)}`;
}
var here = dirname3(fileURLToPath(import.meta.url));
function resolveDockerBuild() {
  const override = process.env.AGENTBOX_DOCKER_CONTEXT;
  if (override && existsSync2(resolve(override, "Dockerfile.box"))) {
    return { dockerfile: resolve(override, "Dockerfile.box"), context: override };
  }
  const staged = resolve(here, "..", "runtime", "docker");
  if (existsSync2(resolve(staged, "Dockerfile.box"))) {
    return { dockerfile: resolve(staged, "Dockerfile.box"), context: staged };
  }
  const packageRoot = resolve(here, "..");
  return {
    dockerfile: resolve(packageRoot, "Dockerfile.box"),
    context: resolve(packageRoot, "..", "..")
  };
}
var { dockerfile: DOCKERFILE_PATH_RESOLVED, context: BUILD_CONTEXT_DIR_RESOLVED } = resolveDockerBuild();
var DOCKERFILE_PATH = DOCKERFILE_PATH_RESOLVED;
var BUILD_CONTEXT_DIR = BUILD_CONTEXT_DIR_RESOLVED;
async function imageExists(ref) {
  const result = await execa2("docker", ["image", "inspect", ref], { reject: false });
  return result.exitCode === 0;
}
async function pullImage(target, opts = {}) {
  const subprocess = execa2("docker", ["pull", target], {
    stderr: "pipe",
    stdout: "pipe",
    reject: false
  });
  if (opts.onProgress) {
    const forward = (chunk) => {
      const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
      for (const line of text.split(/\r?\n/)) {
        if (line.length > 0) opts.onProgress?.(line);
      }
    };
    subprocess.stdout?.on("data", forward);
    subprocess.stderr?.on("data", forward);
  }
  const result = await subprocess;
  return result.exitCode === 0;
}
async function tagImage(source, target) {
  await execa2("docker", ["tag", source, target]);
}
async function imageInfo(ref = DEFAULT_BOX_IMAGE) {
  const result = await execa2(
    "docker",
    ["image", "inspect", "--format", "{{.Size}}|{{.Created}}", ref],
    { reject: false }
  );
  if (result.exitCode !== 0) return { ref, exists: false };
  const [sizeStr, createdAt] = result.stdout.trim().split("|");
  const sizeBytes = sizeStr ? Number.parseInt(sizeStr, 10) : NaN;
  return {
    ref,
    exists: true,
    sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : void 0,
    createdAt: createdAt && createdAt.length > 0 ? createdAt : void 0
  };
}
async function buildImage(opts = {}) {
  const ref = opts.ref ?? DEFAULT_BOX_IMAGE;
  const dockerfile = opts.dockerfile ?? DOCKERFILE_PATH;
  const contextDir = opts.contextDir ?? BUILD_CONTEXT_DIR;
  const args = ["build", "-t", ref, "-f", dockerfile, contextDir];
  if (process.env.AGENTBOX === "1") {
    args.splice(1, 0, "--network=host");
  }
  const subprocess = execa2("docker", args, {
    stderr: "pipe",
    stdout: "pipe"
  });
  if (opts.onProgress) {
    const forward = (chunk) => {
      const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
      for (const line of text.split(/\r?\n/)) {
        if (line.length > 0) opts.onProgress?.(line);
      }
    };
    subprocess.stdout?.on("data", forward);
    subprocess.stderr?.on("data", forward);
  }
  await subprocess;
  return ref;
}
async function pullOrBuild(ref, fingerprint, opts = {}) {
  const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-KE4DT3GX.js");
  const registry = opts.registry ?? BOX_IMAGE_REGISTRY;
  const allowPull = opts.allowPull !== false;
  if (allowPull && registry && fingerprint) {
    const target = registryRefForSha(fingerprint.contextSha256, registry);
    opts.onProgress?.(`[image] pulling ${target}`);
    if (await pullImage(target, { onProgress: opts.onProgress })) {
      await tagImage(target, ref);
      writePreparedDockerState2({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
      opts.onProgress?.(`[image] pulled ${target} -> ${ref}`);
      return { source: "pulled" };
    }
    opts.onProgress?.(`[image] registry miss, building ${ref} locally`);
  }
  await buildImage({
    ref,
    dockerfile: opts.dockerfile,
    contextDir: opts.contextDir,
    onProgress: opts.onProgress
  });
  if (fingerprint) {
    writePreparedDockerState2({ imageRef: ref, contextSha256: fingerprint.contextSha256 });
  }
  return { source: "built" };
}
async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
  const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-KE4DT3GX.js");
  const fingerprint = await computeDockerContextFingerprint2({
    contextDir: opts.contextDir
  });
  const prepared = readPreparedDockerState2();
  const exists = await imageExists(ref);
  let reason;
  if (!exists) {
    reason = `image ${ref} not present`;
  } else if (!fingerprint) {
    return { ref, built: false, reason: "image present (fingerprint skipped)" };
  } else if (!prepared) {
    reason = "no docker-prepared.json on disk";
  } else if (!preparedMatches2(prepared, fingerprint.contextSha256)) {
    reason = `build context changed (was ${prepared.base?.contextSha256?.slice(0, 12) ?? "<none>"}, now ${fingerprint.contextSha256.slice(0, 12)})`;
  }
  if (!reason) {
    return { ref, built: false, reason: "image up to date" };
  }
  opts.onProgress?.(`[image] ${ref}: ${reason}`);
  const { source } = await pullOrBuild(ref, fingerprint, {
    onProgress: opts.onProgress,
    dockerfile: opts.dockerfile,
    contextDir: opts.contextDir,
    allowPull: opts.allowPull,
    registry: opts.registry
  });
  return { ref, built: source === "built", reason };
}
var SCHEMA = 1;
function resolveContextFiles(opts = {}) {
  const ctx = opts.contextDir ?? BUILD_CONTEXT_DIR;
  const here2 = dirname22(fileURLToPath2(import.meta.url));
  const packageRoot = resolve2(here2, "..");
  return resolveContextFilesFrom(DOCKER_CONTEXT_FILE_MAP, {
    contextDir: ctx,
    devRoot: packageRoot
  });
}
async function computeDockerContextFingerprint(opts = {}) {
  const files = resolveContextFiles(opts);
  if (!files) return null;
  return { contextSha256: await computeContextSha256(files), files };
}
function readPreparedDockerState() {
  const raw = readPreparedStateRaw("docker");
  if (raw === null || typeof raw !== "object") return null;
  const parsed = raw;
  if (parsed.schema !== SCHEMA) return null;
  return { schema: SCHEMA, base: parsed.base };
}
function writePreparedDockerState(opts) {
  const stamp = readCliStamp();
  const state = {
    schema: SCHEMA,
    base: {
      imageRef: opts.imageRef ?? DEFAULT_BOX_IMAGE,
      contextSha256: opts.contextSha256,
      cliVersion: stamp.cliVersion,
      cliCommit: stamp.cliCommit,
      createdAt: (/* @__PURE__ */ new Date()).toISOString()
    }
  };
  writePreparedStateRaw("docker", state);
}
function preparedMatches(state, current) {
  return state?.base?.contextSha256 === current;
}

export {
  STATE_DIR,
  STATE_FILE,
  readState,
  recordBox,
  removeBoxRecord,
  findBox,
  allocateProjectIndex,
  autoPickProjectBox,
  resolveBoxRef,
  detectGitRepos,
  pickFreshBranch,
  GitWorktreeError,
  hostOpenCommand,
  preparedStatePathFor,
  readPreparedStateRaw,
  writePreparedStateRaw,
  computeContextSha256,
  readCliStamp,
  DOCKER_CONTEXT_FILE_MAP,
  resolveContextFilesFrom,
  DEFAULT_BOX_IMAGE,
  BOX_IMAGE_REGISTRY,
  registryRefForSha,
  DOCKERFILE_PATH,
  imageExists,
  pullImage,
  tagImage,
  imageInfo,
  pullOrBuild,
  ensureImage,
  resolveContextFiles,
  computeDockerContextFingerprint,
  readPreparedDockerState,
  writePreparedDockerState,
  preparedMatches
};
//# sourceMappingURL=chunk-SNTHHWKY.js.map