#!/usr/bin/env node
import {
  hostOpenCommand
} from "./chunk-SNTHHWKY.js";

// ../../packages/sandbox-vercel/dist/chunk-QUTF76VM.js
import { existsSync, readFileSync } from "fs";
import { homedir } from "os";
import { resolve } from "path";
import { spawnSync } from "child_process";
import { execa } from "execa";
import {
  chmodSync,
  existsSync as existsSync3,
  mkdirSync,
  readFileSync as readFileSync3,
  renameSync,
  writeFileSync
} from "fs";
import { homedir as homedir3 } from "os";
import { dirname, resolve as resolve2 } from "path";
import {
  confirm,
  isCancel,
  intro,
  log,
  note,
  outro,
  password,
  select,
  spinner,
  text
} from "@clack/prompts";
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
import { homedir as homedir2 } from "os";
import { join } from "path";
import { Sandbox, Snapshot } from "@vercel/sandbox";
var VERCEL_KEYS = [
  "VERCEL_OIDC_TOKEN",
  "VERCEL_TOKEN",
  "VERCEL_TEAM_ID",
  "VERCEL_PROJECT_ID",
  // Marker for CLI-login mode (`agentbox vercel login` → `sandbox login`). The
  // access token is NOT stored here — it's read live from the Vercel CLI store;
  // only this marker + team/project ids are persisted.
  "VERCEL_AUTH_SOURCE"
];
var loaded = false;
function ensureVercelEnvLoaded() {
  if (loaded) return;
  loaded = true;
  importVercelFromFile(resolve(homedir(), ".agentbox", "secrets.env"), VERCEL_KEYS);
}
function reloadVercelEnv() {
  loaded = false;
  ensureVercelEnvLoaded();
}
function importVercelFromFile(path, keys) {
  if (!existsSync(path)) return;
  let body;
  try {
    body = readFileSync(path, "utf8");
  } catch {
    return;
  }
  const parsed = parseEnvFile(body);
  for (const key of keys) {
    if (process.env[key] !== void 0) continue;
    const value = parsed[key];
    if (typeof value === "string") {
      process.env[key] = value;
    }
  }
}
function parseEnvFile(body) {
  const out = {};
  for (const rawLine of body.split(/\r?\n/)) {
    const line = rawLine.trim();
    if (line.length === 0 || line.startsWith("#")) continue;
    const stripped = line.startsWith("export ") ? line.slice("export ".length) : line;
    const eq = stripped.indexOf("=");
    if (eq <= 0) continue;
    const key = stripped.slice(0, eq).trim();
    let value = stripped.slice(eq + 1).trim();
    if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1);
    }
    out[key] = value;
  }
  return out;
}
var SBX_BINS = ["sbx", "sandbox"];
var cached = null;
async function detectSbx() {
  if (cached !== null) return cached;
  for (const bin of SBX_BINS) {
    try {
      const r = await execa(bin, ["--version"], { reject: false });
      if (r.exitCode === 0) {
        cached = { installed: true, bin, version: (r.stdout ?? "").trim() || void 0 };
        return cached;
      }
    } catch {
    }
  }
  cached = { installed: false };
  return cached;
}
function resetSbxCache() {
  cached = null;
}
function installSbxHint() {
  return "npm install -g sandbox";
}
async function installSbx() {
  try {
    const r = await execa("npm", ["install", "-g", "sandbox"], { reject: false });
    return r.exitCode === 0;
  } catch {
    return false;
  }
}
function loginSbx(bin) {
  const r = spawnSync(bin, ["login"], { stdio: "inherit" });
  return r.status ?? 1;
}
async function refreshSbxToken(bin) {
  try {
    const r = await execa(bin, ["list"], {
      reject: false,
      timeout: 3e4,
      stdin: "ignore"
    });
    return r.exitCode === 0;
  } catch {
    return false;
  }
}
function vercelCliDir() {
  const override = process.env.AGENTBOX_VERCEL_CLI_DIR;
  if (override && override.trim().length > 0) return override.trim();
  const name = "com.vercel.cli";
  if (process.platform === "darwin") {
    return join(homedir2(), "Library", "Application Support", name);
  }
  if (process.platform === "win32") {
    const appData = process.env.APPDATA;
    if (appData && appData.trim().length > 0) return join(appData, name);
    return join(homedir2(), "AppData", "Roaming", name);
  }
  const xdg = process.env.XDG_DATA_HOME;
  const base = xdg && xdg.trim().length > 0 ? xdg.trim() : join(homedir2(), ".local", "share");
  return join(base, name);
}
function cliStorePaths() {
  const dir = vercelCliDir();
  return { authPath: join(dir, "auth.json"), configPath: join(dir, "config.json") };
}
function readJson(path) {
  if (!existsSync2(path)) return null;
  try {
    return JSON.parse(readFileSync2(path, "utf8"));
  } catch {
    return null;
  }
}
function readCliAuth() {
  const raw = readJson(cliStorePaths().authPath);
  if (!raw || typeof raw.token !== "string" || raw.token.length === 0) return null;
  return {
    token: raw.token,
    expiresAt: typeof raw.expiresAt === "number" ? raw.expiresAt : void 0,
    refreshToken: typeof raw.refreshToken === "string" ? raw.refreshToken : void 0
  };
}
function readCliCurrentTeam() {
  const raw = readJson(cliStorePaths().configPath);
  return raw && typeof raw.currentTeam === "string" && raw.currentTeam.length > 0 ? raw.currentTeam : null;
}
function isNearExpiry(auth, skewSec = 120) {
  if (auth.expiresAt === void 0) return true;
  return auth.expiresAt * 1e3 < Date.now() + skewSec * 1e3;
}
function resolveCredentials() {
  ensureVercelEnvLoaded();
  const oidc = process.env.VERCEL_OIDC_TOKEN;
  if (oidc) {
    const claims = decodeOidcClaims(oidc);
    if (!claims) {
      throw new Error(
        "VERCEL_OIDC_TOKEN is set but could not be decoded (not a valid Vercel OIDC JWT). Re-run `vercel env pull`, or use the VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID trio."
      );
    }
    if (claims.exp !== void 0 && claims.exp * 1e3 < Date.now()) {
      throw new Error(
        "VERCEL_OIDC_TOKEN has expired (Vercel dev OIDC tokens last ~12h). Re-run `vercel env pull` to refresh it, then retry."
      );
    }
    return { token: oidc, teamId: claims.teamId, projectId: claims.projectId };
  }
  if (process.env.VERCEL_AUTH_SOURCE === "cli") {
    const auth = readCliAuth();
    if (!auth) {
      throw new Error(
        "Vercel CLI session not found \u2014 run `agentbox vercel login` (or `sbx login`) to sign in again."
      );
    }
    const teamId2 = process.env.VERCEL_TEAM_ID ?? readCliCurrentTeam() ?? void 0;
    const projectId2 = process.env.VERCEL_PROJECT_ID;
    if (!teamId2 || !projectId2) {
      throw new Error(
        "Vercel CLI auth is missing the team/project id \u2014 re-run `agentbox vercel login`."
      );
    }
    return { token: auth.token, teamId: teamId2, projectId: projectId2 };
  }
  const token = process.env.VERCEL_TOKEN;
  const teamId = process.env.VERCEL_TEAM_ID;
  const projectId = process.env.VERCEL_PROJECT_ID;
  if (token && teamId && projectId) return { token, teamId, projectId };
  throw new Error(
    "Vercel credentials not configured.\nEither run `vercel link && vercel env pull` to get a VERCEL_OIDC_TOKEN, or set VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID (see `agentbox vercel login`)."
  );
}
function decodeOidcClaims(token) {
  const parts = token.split(".");
  if (parts.length < 2 || !parts[1]) return null;
  try {
    const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
    if (typeof payload.owner_id !== "string" || typeof payload.project_id !== "string") return null;
    return {
      teamId: payload.owner_id,
      projectId: payload.project_id,
      exp: typeof payload.exp === "number" ? payload.exp : void 0
    };
  } catch {
    return null;
  }
}
function hasUsableCredentials() {
  ensureVercelEnvLoaded();
  if (process.env.VERCEL_OIDC_TOKEN) return true;
  if (process.env.VERCEL_AUTH_SOURCE === "cli" && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID && readCliAuth()) {
    return true;
  }
  return Boolean(
    process.env.VERCEL_TOKEN && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID
  );
}
var inflightRefresh = null;
function ensureFreshCredentials() {
  ensureVercelEnvLoaded();
  if (process.env.VERCEL_AUTH_SOURCE !== "cli") return Promise.resolve();
  if (inflightRefresh) return inflightRefresh;
  inflightRefresh = refreshCliToken().finally(() => {
    inflightRefresh = null;
  });
  return inflightRefresh;
}
async function refreshCliToken() {
  const auth = readCliAuth();
  if (!auth) return;
  if (!isNearExpiry(auth)) return;
  const det = await detectSbx();
  if (!det.installed || !det.bin) {
    throw new Error(
      "Vercel access token is near expiry and the `sandbox` CLI is no longer installed \u2014 reinstall it (`npm install -g sandbox`) or run `agentbox vercel login`."
    );
  }
  const ok = await refreshSbxToken(det.bin);
  if (!ok) {
    throw new Error(
      "Vercel token refresh failed \u2014 run `agentbox vercel login` (the refresh token may have expired)."
    );
  }
  const fresh = readCliAuth();
  if (!fresh || isNearExpiry(fresh, 0)) {
    throw new Error(
      "Vercel token is still stale after a refresh attempt \u2014 run `agentbox vercel login`."
    );
  }
}
var API = "https://api.vercel.com";
var VercelApiError = class extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
    this.name = "VercelApiError";
  }
  status;
};
async function api(token, path, init) {
  const res = await fetch(`${API}${path}`, {
    method: init?.method ?? "GET",
    headers: {
      Authorization: `Bearer ${token}`,
      ...init?.body ? { "Content-Type": "application/json" } : {}
    },
    body: init?.body ? JSON.stringify(init.body) : void 0
  });
  const text2 = await res.text();
  let json = null;
  try {
    json = text2 ? JSON.parse(text2) : null;
  } catch {
    json = null;
  }
  if (!res.ok) {
    const detail = json?.error?.message ?? text2 ?? res.statusText;
    throw new VercelApiError(res.status, `Vercel API ${res.status}: ${detail}`);
  }
  return json;
}
async function getUser(token) {
  const json = await api(token, "/v2/user");
  const user = json.user;
  if (!user?.id) throw new Error("Vercel /v2/user returned no user");
  return user;
}
async function listProjects(token, teamId, limit = 100) {
  const json = await api(
    token,
    `/v9/projects?teamId=${encodeURIComponent(teamId)}&limit=${limit}`
  );
  return (json.projects ?? []).map((p) => ({ id: p.id, name: p.name }));
}
async function createProject(token, teamId, name) {
  try {
    const json = await api(token, `/v9/projects?teamId=${encodeURIComponent(teamId)}`, {
      method: "POST",
      body: { name }
    });
    return { id: json.id, name: json.name };
  } catch (err) {
    if (err instanceof VercelApiError && err.status === 409) {
      const existing = (await listProjects(token, teamId)).find((p) => p.name === name);
      if (existing) return existing;
    }
    throw err;
  }
}
var DASHBOARD_TOKENS_URL = "https://vercel.com/account/settings/tokens";
var MANAGED_KEYS = [
  "VERCEL_OIDC_TOKEN",
  "VERCEL_TOKEN",
  "VERCEL_TEAM_ID",
  "VERCEL_PROJECT_ID",
  "VERCEL_AUTH_SOURCE"
];
async function ensureVercelCredentials(opts = {}) {
  ensureVercelEnvLoaded();
  if (!opts.force && hasUsableCredentials()) return;
  if (!process.stdin.isTTY) return;
  intro("Vercel setup");
  note(
    `AgentBox needs Vercel credentials to provision sandboxes.
Sign in with Vercel (recommended): drives the Vercel \`sandbox\` CLI through a browser login, then reads the token from the CLI's own store and keeps it fresh \u2014 no token to paste.
Access token (best for CI / headless): personal access token + team id + project id, saved to \`~/.agentbox/secrets.env\`.
OIDC (short interactive work): export VERCEL_OIDC_TOKEN in your shell or add it to \`~/.agentbox/secrets.env\` (dev token expires ~12h, no headless refresh).`,
    "Credentials required"
  );
  const mode = await select({
    message: "How do you want to authenticate?",
    options: [
      { value: "cli", label: "Sign in with Vercel (browser) \u2014 recommended for interactive use" },
      { value: "token", label: "Access token (VERCEL_TOKEN + team + project) \u2014 best for CI / headless" },
      { value: "oidc", label: "OIDC token (VERCEL_OIDC_TOKEN in env / secrets.env) \u2014 short interactive work" }
    ],
    initialValue: "cli"
  });
  if (isCancel(mode)) {
    log.warn("Vercel setup cancelled \u2014 re-run `agentbox vercel login` when ready.");
    return;
  }
  if (mode === "cli") {
    await runCliLogin();
    return;
  }
  if (mode === "oidc") {
    note(
      `Get an OIDC token with \`vercel link\` then \`vercel env pull\`, then make it visible to AgentBox by either:
  export VERCEL_OIDC_TOKEN=<token>   # in this shell
  echo "VERCEL_OIDC_TOKEN=<token>" >> ~/.agentbox/secrets.env
Re-do every ~12h; the dev token expires. AgentBox does not harvest .env.local.`,
      "OIDC setup"
    );
    reloadVercelEnv();
    if (process.env.VERCEL_OIDC_TOKEN) {
      log.success("Found VERCEL_OIDC_TOKEN \u2014 Vercel is configured.");
      await ensureSbxInstalled();
      outro("Setup complete.");
    } else {
      log.warn("No VERCEL_OIDC_TOKEN found yet \u2014 set it as above, then re-run `agentbox vercel login`.");
    }
    return;
  }
  const creds = await promptForTokenTrio();
  if (creds === null) return;
  persistCredentials(creds);
  log.success(`Vercel credentials saved to ${secretsPath()}`);
  await ensureSbxInstalled();
  outro("Setup complete.");
}
async function promptForTokenTrio() {
  const openIt = await confirm({
    message: `Open ${DASHBOARD_TOKENS_URL} to create a token?`,
    initialValue: true
  });
  if (isCancel(openIt)) return null;
  if (openIt) openDashboard();
  const token = await password({
    message: "Paste your Vercel access token",
    validate: (v) => v && v.trim().length > 0 ? void 0 : "Cannot be empty"
  });
  if (isCancel(token)) {
    log.warn("Vercel setup cancelled.");
    return null;
  }
  const teamId = await text({
    message: "Team ID (team settings \u2192 General)",
    placeholder: "team_...",
    validate: (v) => v && v.trim().length > 0 ? void 0 : "Cannot be empty"
  });
  if (isCancel(teamId)) {
    log.warn("Vercel setup cancelled.");
    return null;
  }
  const projectId = await text({
    message: "Project ID (project settings \u2192 General)",
    placeholder: "prj_...",
    validate: (v) => v && v.trim().length > 0 ? void 0 : "Cannot be empty"
  });
  if (isCancel(projectId)) {
    log.warn("Vercel setup cancelled.");
    return null;
  }
  return { token: token.trim(), teamId: teamId.trim(), projectId: projectId.trim() };
}
function persistCredentials(creds) {
  writeManaged({
    VERCEL_TOKEN: creds.token,
    VERCEL_TEAM_ID: creds.teamId,
    VERCEL_PROJECT_ID: creds.projectId
  });
}
function persistCliCredentials(ids) {
  writeManaged({
    VERCEL_AUTH_SOURCE: "cli",
    VERCEL_TEAM_ID: ids.teamId,
    VERCEL_PROJECT_ID: ids.projectId
  });
}
function writeManaged(record) {
  for (const k of MANAGED_KEYS) delete process.env[k];
  for (const [k, v] of Object.entries(record)) process.env[k] = v;
  const path = secretsPath();
  mkdirSync(dirname(path), { recursive: true });
  let existing = "";
  if (existsSync3(path)) {
    try {
      existing = readFileSync3(path, "utf8");
    } catch {
      existing = "";
    }
  }
  const kept = existing.split(/\r?\n/).filter((line) => {
    const stripped = line.startsWith("export ") ? line.slice("export ".length) : line;
    const eq = stripped.indexOf("=");
    if (eq <= 0) return true;
    const key = stripped.slice(0, eq).trim();
    return !MANAGED_KEYS.includes(key);
  }).join("\n").replace(/\s+$/u, "");
  const lines = Object.entries(record).map(([k, v]) => `${k}=${v}`);
  const body = (kept ? `${kept}
` : "") + lines.join("\n") + "\n";
  const tmp = `${path}.tmp`;
  writeFileSync(tmp, body, { mode: 384 });
  try {
    chmodSync(tmp, 384);
  } catch {
  }
  renameSync(tmp, path);
  try {
    chmodSync(path, 384);
  } catch {
  }
}
async function ensureSbxInstalled() {
  let det = await detectSbx();
  if (!det.installed) {
    const doInstall = await confirm({
      message: `The Vercel sandbox CLI (needed for interactive attach) isn't installed. Install it now? (${installSbxHint()})`,
      initialValue: true
    });
    if (isCancel(doInstall) || !doInstall) {
      log.warn(
        `Install it with \`${installSbxHint()}\` to use \`agentbox shell|claude|codex|opencode\` on Vercel boxes.`
      );
      return null;
    }
    const sp = spinner();
    sp.start("Installing the Vercel sandbox CLI\u2026");
    const ok = await installSbx();
    resetSbxCache();
    det = await detectSbx();
    if (!ok || !det.installed || !det.bin) {
      sp.stop("Install failed.");
      log.warn(`Could not install the sandbox CLI \u2014 run \`${installSbxHint()}\` manually.`);
      return null;
    }
    sp.stop(`Installed sandbox CLI${det.version ? ` ${det.version}` : ""}.`);
  }
  return det.bin ? { bin: det.bin } : null;
}
async function runCliLogin() {
  const det = await ensureSbxInstalled();
  if (!det) {
    log.warn("The Vercel sandbox CLI is required to sign in this way \u2014 install it, then re-run `agentbox vercel login`.");
    return;
  }
  note("A browser window will open to sign in to Vercel.", "Vercel sign-in");
  const status = loginSbx(det.bin);
  if (status !== 0) {
    log.warn("Vercel sign-in did not complete \u2014 re-run `agentbox vercel login` to try again.");
    return;
  }
  const harvested = harvestCliCredentials();
  if (!harvested) {
    log.warn("Sign-in finished but no credentials were found in the Vercel CLI store. Try again.");
    return;
  }
  try {
    await getUser(harvested.token);
  } catch (err) {
    log.warn(
      `The Vercel session looks invalid (${err instanceof Error ? err.message : String(err)}). Re-run \`agentbox vercel login\`.`
    );
    return;
  }
  const projectId = await resolveProjectId(harvested.token, harvested.teamId);
  if (projectId === null) {
    log.warn("No project selected \u2014 re-run `agentbox vercel login` to finish setup.");
    return;
  }
  persistCliCredentials({ teamId: harvested.teamId, projectId });
  reloadVercelEnv();
  log.success(`Signed in with Vercel \u2014 credentials managed by the sandbox CLI (saved scope to ${secretsPath()}).`);
  outro("Setup complete.");
}
function harvestCliCredentials() {
  const auth = readCliAuth();
  if (!auth) return null;
  const teamId = process.env.VERCEL_TEAM_ID ?? readCliCurrentTeam();
  if (!teamId) return null;
  return { token: auth.token, teamId };
}
async function resolveProjectId(token, teamId) {
  let projects = [];
  const sp = spinner();
  sp.start("Loading your Vercel projects\u2026");
  try {
    projects = await listProjects(token, teamId);
    sp.stop(`Found ${projects.length} project${projects.length === 1 ? "" : "s"}.`);
  } catch (err) {
    sp.stop("Could not list projects.");
    log.warn(`Failed to list Vercel projects: ${err instanceof Error ? err.message : String(err)}`);
    return null;
  }
  const CREATE = "__create__";
  const preferred = projects.find((p) => p.name === "agentbox") ?? projects.find((p) => p.name === "vercel-sandbox-default-project");
  const choice = await select({
    message: "Which Vercel project should sandboxes run under?",
    options: [
      ...projects.map((p) => ({ value: p.id, label: p.name })),
      { value: CREATE, label: "Create a new project\u2026" }
    ],
    initialValue: preferred ? preferred.id : CREATE
  });
  if (isCancel(choice)) return null;
  if (choice !== CREATE) return choice;
  const name = await text({
    message: "New project name",
    placeholder: "agentbox",
    defaultValue: "agentbox",
    validate: (v) => v && v.trim().length > 0 ? void 0 : "Cannot be empty"
  });
  if (isCancel(name)) return null;
  try {
    const created = await createProject(token, teamId, name.trim() || "agentbox");
    return created.id;
  } catch (err) {
    log.warn(`Could not create the project: ${err instanceof Error ? err.message : String(err)}`);
    return null;
  }
}
function openDashboard() {
  import("child_process").then(({ spawnSync: spawnSync2 }) => {
    const r = spawnSync2(hostOpenCommand(), [DASHBOARD_TOKENS_URL], { stdio: "ignore" });
    if (r.status !== 0) {
      log.warn(`Could not auto-open the browser \u2014 visit ${DASHBOARD_TOKENS_URL} manually.`);
    }
  }).catch(() => {
    log.warn(`Could not auto-open the browser \u2014 visit ${DASHBOARD_TOKENS_URL} manually.`);
  });
}
function secretsPath() {
  return resolve2(homedir3(), ".agentbox", "secrets.env");
}
function readVercelCredStatus() {
  const shellHad = !!process.env.VERCEL_OIDC_TOKEN || !!process.env.VERCEL_TOKEN;
  ensureVercelEnvLoaded();
  const oidc = !!process.env.VERCEL_OIDC_TOKEN;
  const teamId = process.env.VERCEL_TEAM_ID;
  const projectId = process.env.VERCEL_PROJECT_ID;
  if (oidc) {
    return { auth: "oidc", oidc: true, teamId, projectId, source: shellHad ? "env" : "secrets.env" };
  }
  if (process.env.VERCEL_AUTH_SOURCE === "cli") {
    const auth = readCliAuth();
    return {
      auth: "cli",
      oidc: false,
      token: auth?.token,
      teamId,
      projectId,
      source: "cli-store",
      cli: {
        loggedIn: !!auth,
        expiresAt: auth?.expiresAt,
        nearExpiry: auth ? isNearExpiry(auth) : void 0,
        authPath: cliStorePaths().authPath
      }
    };
  }
  const token = process.env.VERCEL_TOKEN;
  if (!token) return { auth: "none", oidc: false, source: "none" };
  return {
    auth: "token",
    oidc: false,
    token,
    teamId,
    projectId,
    source: shellHad ? "env" : "secrets.env"
  };
}
function maskKey(value) {
  if (value.length <= 8) return "*".repeat(value.length);
  return `${value.slice(0, 4)}\u2026${"*".repeat(8)}${value.slice(-4)}`;
}

export {
  ensureVercelEnvLoaded,
  reloadVercelEnv,
  detectSbx,
  installSbxHint,
  Sandbox,
  Snapshot,
  resolveCredentials,
  ensureFreshCredentials,
  ensureVercelCredentials,
  secretsPath,
  readVercelCredStatus,
  maskKey
};
//# sourceMappingURL=chunk-ECLLV5JH.js.map