@madarco/agentbox 0.16.0 → 0.17.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 (41) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/{_cloud-attach-5KJWOASL.js → _cloud-attach-RUD4W33P.js} +5 -4
  3. package/dist/{chunk-QXFNLKJJ.js → chunk-3JXSW6PN.js} +36 -24
  4. package/dist/chunk-3JXSW6PN.js.map +1 -0
  5. package/dist/{chunk-3WCEB6RE.js → chunk-46NXCJ6Z.js} +45 -34
  6. package/dist/{chunk-3WCEB6RE.js.map → chunk-46NXCJ6Z.js.map} +1 -1
  7. package/dist/{chunk-DBBUDKKB.js → chunk-6QFPYU4Z.js} +3 -3
  8. package/dist/{chunk-GXJNJUEV.js → chunk-HFQZJO73.js} +2 -2
  9. package/dist/{chunk-SENASAU4.js → chunk-IA6YHH52.js} +89 -81
  10. package/dist/chunk-IA6YHH52.js.map +1 -0
  11. package/dist/{chunk-PIK47622.js → chunk-KTDJ5JTE.js} +3 -3
  12. package/dist/chunk-WJFZJZIM.js +24 -0
  13. package/dist/{chunk-NW2UZQV6.js → chunk-XWBFWUY2.js} +23 -19
  14. package/dist/chunk-XWBFWUY2.js.map +1 -0
  15. package/dist/{chunk-SB4QTF2T.js → chunk-YUGLNTT2.js} +7 -7
  16. package/dist/{cloud-poller-SUNA6ZQC-2RG5WPRN.js → cloud-poller-SUNA6ZQC-7M5LJHHE.js} +2 -1
  17. package/dist/{dist-4IQFJJQI.js → dist-BO2R55FX.js} +6 -5
  18. package/dist/{dist-4IQFJJQI.js.map → dist-BO2R55FX.js.map} +1 -1
  19. package/dist/{dist-VHI5QOSQ.js → dist-CNABE32V.js} +6 -5
  20. package/dist/{dist-VHI5QOSQ.js.map → dist-CNABE32V.js.map} +1 -1
  21. package/dist/{dist-XC47DSCR.js → dist-CROGTJ7N.js} +6 -5
  22. package/dist/{dist-XC47DSCR.js.map → dist-CROGTJ7N.js.map} +1 -1
  23. package/dist/{dist-SL2QSMBE.js → dist-TEKY3GFT.js} +6 -5
  24. package/dist/{dist-SL2QSMBE.js.map → dist-TEKY3GFT.js.map} +1 -1
  25. package/dist/{dist-7YB7BMNG.js → dist-VAATGBAR.js} +4 -3
  26. package/dist/index.js +2254 -2064
  27. package/dist/index.js.map +1 -1
  28. package/dist/{prepared-state-MQHD3M5F-2LANTRL7.js → prepared-state-MQHD3M5F-OVABNV66.js} +3 -2
  29. package/dist/prepared-state-MQHD3M5F-OVABNV66.js.map +1 -0
  30. package/package.json +3 -3
  31. package/dist/chunk-NW2UZQV6.js.map +0 -1
  32. package/dist/chunk-QXFNLKJJ.js.map +0 -1
  33. package/dist/chunk-SENASAU4.js.map +0 -1
  34. /package/dist/{_cloud-attach-5KJWOASL.js.map → _cloud-attach-RUD4W33P.js.map} +0 -0
  35. /package/dist/{chunk-DBBUDKKB.js.map → chunk-6QFPYU4Z.js.map} +0 -0
  36. /package/dist/{chunk-GXJNJUEV.js.map → chunk-HFQZJO73.js.map} +0 -0
  37. /package/dist/{chunk-PIK47622.js.map → chunk-KTDJ5JTE.js.map} +0 -0
  38. /package/dist/{cloud-poller-SUNA6ZQC-2RG5WPRN.js.map → chunk-WJFZJZIM.js.map} +0 -0
  39. /package/dist/{chunk-SB4QTF2T.js.map → chunk-YUGLNTT2.js.map} +0 -0
  40. /package/dist/{dist-7YB7BMNG.js.map → cloud-poller-SUNA6ZQC-7M5LJHHE.js.map} +0 -0
  41. /package/dist/{prepared-state-MQHD3M5F-2LANTRL7.js.map → dist-VAATGBAR.js.map} +0 -0
@@ -6,7 +6,8 @@ import {
6
6
  readPreparedDockerState,
7
7
  resolveContextFiles,
8
8
  writePreparedDockerState
9
- } from "./chunk-DBBUDKKB.js";
9
+ } from "./chunk-6QFPYU4Z.js";
10
+ import "./chunk-WJFZJZIM.js";
10
11
  export {
11
12
  DOCKERFILE_PATH,
12
13
  computeDockerContextFingerprint,
@@ -15,4 +16,4 @@ export {
15
16
  resolveContextFiles,
16
17
  writePreparedDockerState
17
18
  };
18
- //# sourceMappingURL=prepared-state-MQHD3M5F-2LANTRL7.js.map
19
+ //# sourceMappingURL=prepared-state-MQHD3M5F-OVABNV66.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madarco/agentbox",
3
- "version": "0.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "Launch Claude Code, Codex, and other coding agents in isolated sandboxes",
5
5
  "license": "MIT",
6
6
  "author": "Marco D'Alia",
@@ -61,12 +61,12 @@
61
61
  "vitest": "^2.1.8",
62
62
  "@agentbox/config": "0.0.0",
63
63
  "@agentbox/core": "0.0.0",
64
- "@agentbox/ctl": "0.0.0",
65
64
  "@agentbox/relay": "0.0.0",
65
+ "@agentbox/ctl": "0.0.0",
66
66
  "@agentbox/integrations": "0.0.0",
67
67
  "@agentbox/sandbox-cloud": "0.0.0",
68
- "@agentbox/sandbox-daytona": "0.0.0",
69
68
  "@agentbox/sandbox-core": "0.0.0",
69
+ "@agentbox/sandbox-daytona": "0.0.0",
70
70
  "@agentbox/sandbox-docker": "0.0.0",
71
71
  "@agentbox/sandbox-e2b": "0.0.0",
72
72
  "@agentbox/sandbox-hetzner": "0.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-e2b/src/env-loader.ts","../../../packages/sandbox-e2b/src/sdk.ts","../../../packages/sandbox-e2b/src/credentials.ts","../../../packages/sandbox-e2b/src/runtime-assets.ts","../../../packages/sandbox-e2b/src/prepared-state.ts"],"sourcesContent":["/**\n * E2B env auto-loader. The `e2b` SDK reads `E2B_API_KEY` (and optionally\n * `E2B_DOMAIN` for non-default deployments) from `process.env`. We seed those\n * from `~/.agentbox/secrets.env` (written by `agentbox e2b login`) so the SDK\n * Just Works after a one-time login — same pattern as the daytona / hetzner /\n * vercel env-loaders.\n *\n * Lookup order (first wins; process.env is never overwritten):\n * 1. `process.env` (already set in the shell).\n * 2. `~/.agentbox/secrets.env` — written by `agentbox e2b login`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed. Put host credentials in\n * `~/.agentbox/secrets.env` (or the shell env).\n *\n * Only E2B-prefixed keys are imported; the rest of the file is left alone.\n * Idempotent and side-effect-free after the first call.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\nconst E2B_KEYS = ['E2B_API_KEY', 'E2B_DOMAIN'] as const;\n\nlet loaded = false;\n\nexport function ensureE2bEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importE2bFromFile(resolve(homedir(), '.agentbox', 'secrets.env'), E2B_KEYS);\n}\n\n/**\n * Force a re-read of `~/.agentbox/secrets.env`. Used by the interactive\n * `agentbox e2b login` flow after it persists the API key, so the same process\n * can pick it up without a restart.\n */\nexport function reloadE2bEnv(): void {\n loaded = false;\n ensureE2bEnvLoaded();\n}\n\nfunction importE2bFromFile(path: string, keys: readonly string[]): void {\n if (!existsSync(path)) return;\n let body: string;\n try {\n body = readFileSync(path, 'utf8');\n } catch {\n return;\n }\n const parsed = parseEnvFile(body);\n for (const key of keys) {\n if (process.env[key] !== undefined) continue;\n const value = parsed[key];\n if (typeof value === 'string') {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * Minimal `.env` parser: handles `KEY=value`, `KEY=\"value\"`, `KEY='value'`,\n * `export KEY=value`, blank lines, and `#` comments. No variable interpolation\n * — predictable over feature-complete (matches the daytona / vercel loaders).\n */\nexport function parseEnvFile(body: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) continue;\n const key = stripped.slice(0, eq).trim();\n let value = stripped.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n","/**\n * Thin wrapper around the `e2b` SDK. Resolves the API key once and re-exports\n * the SDK surface the rest of the package uses from a single place (so tests\n * can mock `./sdk.js` instead of the package).\n *\n * E2B only ships one auth mode for first-party use: a single API key. The SDK\n * already reads `process.env.E2B_API_KEY` on each call, but we still expose\n * `resolveApiKey()` so callers can fail loud with an actionable error before\n * the SDK throws a generic \"401 unauthorized\" deep inside an op.\n */\n\nimport { Sandbox, Template } from 'e2b';\nimport { ensureE2bEnvLoaded } from './env-loader.js';\n\nexport { Sandbox, Template };\nexport type { SandboxOpts, SandboxInfo, SandboxState, SandboxListOpts, LogEntry, BuildInfo } from 'e2b';\n\n/**\n * Return the configured E2B API key. Throws an actionable error when nothing\n * is configured. Idempotent — env-loader caches itself after first call.\n */\nexport function resolveApiKey(): string {\n ensureE2bEnvLoaded();\n const k = process.env.E2B_API_KEY;\n if (!k) {\n throw new Error(\n 'E2B credentials not configured.\\n' +\n 'Run `agentbox e2b login` to paste your API key (from https://e2b.dev/dashboard?tab=keys), ' +\n 'or set E2B_API_KEY in the environment / ~/.agentbox/secrets.env.',\n );\n }\n return k;\n}\n\n/** True when an API key is configured. Used by the credential gate. */\nexport function hasUsableCredentials(): boolean {\n ensureE2bEnvLoaded();\n return Boolean(process.env.E2B_API_KEY);\n}\n","/**\n * Interactive E2B credential setup. Single mode — paste an API key from\n * https://e2b.dev/dashboard?tab=keys — much simpler than vercel's three-mode\n * flow. Persists to `~/.agentbox/secrets.env` (the canonical store, matching\n * daytona / hetzner / vercel).\n *\n * Non-interactive callers (no TTY): silent no-op, so scripted/CI runs surface\n * the SDK's own \"not configured\" error instead of hanging on a prompt.\n */\n\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, resolve } from 'node:path';\nimport { hostOpenCommand } from '@agentbox/sandbox-core';\nimport {\n confirm,\n intro,\n isCancel,\n log,\n note,\n outro,\n password,\n} from '@clack/prompts';\nimport { ensureE2bEnvLoaded, reloadE2bEnv } from './env-loader.js';\nimport { hasUsableCredentials } from './sdk.js';\n\nconst DASHBOARD_KEYS_URL = 'https://e2b.dev/dashboard?tab=keys';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. On reconfigure we strip prior\n * values for these before appending so the file never accumulates duplicates.\n */\nconst MANAGED_KEYS = ['E2B_API_KEY'] as const;\n\nexport interface EnsureE2bCredentialsOptions {\n /** Re-prompt even when valid credentials are already present (`agentbox e2b login`). */\n force?: boolean;\n}\n\nexport async function ensureE2bCredentials(\n opts: EnsureE2bCredentialsOptions = {},\n): Promise<void> {\n ensureE2bEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('E2B setup');\n note(\n `AgentBox needs an E2B API key to provision sandboxes.\\n` +\n `Get one from ${DASHBOARD_KEYS_URL} (free tier available), then paste it below.\\n` +\n `The key is stored in \\`~/.agentbox/secrets.env\\` (mode 0600) — no .env.local harvesting.`,\n 'Credentials required',\n );\n\n const openIt = await confirm({\n message: `Open ${DASHBOARD_KEYS_URL} to create a key?`,\n initialValue: true,\n });\n if (isCancel(openIt)) {\n log.warn('E2B setup cancelled.');\n return;\n }\n if (openIt) openDashboard();\n\n const key = await password({\n message: 'Paste your E2B API key',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n });\n if (isCancel(key)) {\n log.warn('E2B setup cancelled.');\n return;\n }\n\n persistCredentials({ apiKey: key.trim() });\n reloadE2bEnv();\n log.success(`E2B credentials saved to ${secretsPath()}`);\n outro('Setup complete.');\n}\n\nfunction persistCredentials(creds: { apiKey: string }): void {\n writeManaged({ E2B_API_KEY: creds.apiKey });\n}\n\n/**\n * Atomically rewrite the managed E2B keys in `~/.agentbox/secrets.env`:\n * strip every prior value for a `MANAGED_KEYS` entry, then append exactly the\n * keys in `record` (mode 0600, temp-file + rename). Also mirrors the record\n * into `process.env` so the current run uses the new values immediately.\n */\nfunction writeManaged(record: Record<string, string>): void {\n for (const k of MANAGED_KEYS) delete process.env[k];\n for (const [k, v] of Object.entries(record)) process.env[k] = v;\n\n const path = secretsPath();\n mkdirSync(dirname(path), { recursive: true });\n\n let existing = '';\n if (existsSync(path)) {\n try {\n existing = readFileSync(path, 'utf8');\n } catch {\n existing = '';\n }\n }\n const kept = existing\n .split(/\\r?\\n/)\n .filter((line) => {\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) return true;\n const key = stripped.slice(0, eq).trim();\n return !(MANAGED_KEYS as readonly string[]).includes(key);\n })\n .join('\\n')\n .replace(/\\s+$/u, '');\n\n const lines = Object.entries(record).map(([k, v]) => `${k}=${v}`);\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n try {\n chmodSync(tmp, 0o600);\n } catch {\n // chmod best-effort; writeFileSync mode already covers most filesystems.\n }\n renameSync(tmp, path);\n try {\n chmodSync(path, 0o600);\n } catch {\n // ignore — already attempted above\n }\n}\n\nfunction openDashboard(): void {\n import('node:child_process')\n .then(({ spawnSync }) => {\n const r = spawnSync(hostOpenCommand(), [DASHBOARD_KEYS_URL], { stdio: 'ignore' });\n if (r.status !== 0) {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n }\n })\n .catch(() => {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n });\n}\n\nexport function secretsPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\nexport interface E2bCredStatus {\n auth: 'key' | 'none';\n token?: string;\n source: 'env' | 'secrets.env' | 'none';\n}\n\nexport function readE2bCredStatus(): E2bCredStatus {\n const shellHad = process.env.E2B_API_KEY !== undefined;\n ensureE2bEnvLoaded();\n const key = process.env.E2B_API_KEY;\n if (!key) return { auth: 'none', source: 'none' };\n return { auth: 'key', token: key, source: shellHad ? 'env' : 'secrets.env' };\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return '*'.repeat(value.length);\n return `${value.slice(0, 4)}…${'*'.repeat(8)}${value.slice(-4)}`;\n}\n","/**\n * Resolver for the runtime payload baked into the E2B base template during\n * `prepareE2b()`. Same idea as the vercel resolver: a flat list of files to\n * `template.copy` into the build context, each resolved from either the\n * staged CLI runtime tree or the monorepo source tree.\n *\n * Lookup order per file:\n * 1. The CLI's staged runtime tree: `<cliRoot>/e2b/...`.\n * 2. The monorepo source tree (dev fallback) under `packages/`.\n *\n * Any missing file throws a clear error naming the paths tried. Note: no\n * dockerd helper — E2B microVMs can't run nested containers.\n */\n\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\nexport function findStagedCliRuntimeRoot(): string | undefined {\n const candidates = [resolve(SELF, '..', 'runtime'), resolve(SELF, '..', '..', 'runtime')];\n for (const c of candidates) {\n if (existsSync(resolve(c, 'e2b', 'scripts', 'build-template.sh'))) return c;\n }\n return undefined;\n}\n\nexport interface RuntimeAsset {\n /** Logical name (used in error messages + log lines). */\n name: string;\n /** Absolute path inside the template's build filesystem (Template.copy target). */\n remotePath: string;\n /** File mode to apply after upload. */\n remoteMode: number;\n}\n\n/**\n * Where each asset lands inside the sandbox during template build. build-template.sh\n * reads them from these fixed paths. The agent/runtime helpers go straight to\n * /usr/local/bin via the script; baked config files to /tmp for the script to\n * `install` into place.\n */\nexport const RUNTIME_ASSETS: readonly RuntimeAsset[] = [\n { name: 'build-template.sh', remotePath: '/tmp/agentbox-build-template.sh', remoteMode: 0o755 },\n { name: 'agentbox-ctl', remotePath: '/tmp/agentbox-ctl', remoteMode: 0o755 },\n { name: 'agentbox-vnc-start', remotePath: '/tmp/agentbox-vnc-start', remoteMode: 0o755 },\n { name: 'agentbox-checkpoint-cleanup', remotePath: '/tmp/agentbox-checkpoint-cleanup', remoteMode: 0o755 },\n { name: 'agentbox-open', remotePath: '/tmp/agentbox-open', remoteMode: 0o755 },\n { name: 'gh-shim', remotePath: '/tmp/agentbox-gh-shim', remoteMode: 0o755 },\n { name: 'git-shim', remotePath: '/tmp/agentbox-git-shim', remoteMode: 0o755 },\n { name: 'ntn-shim', remotePath: '/tmp/agentbox-ntn-shim', remoteMode: 0o755 },\n { name: 'linear-shim', remotePath: '/tmp/agentbox-linear-shim', remoteMode: 0o755 },\n { name: 'custom-system-CLAUDE.md', remotePath: '/tmp/agentbox-custom-CLAUDE.md', remoteMode: 0o644 },\n { name: 'claude-managed-settings.json', remotePath: '/tmp/agentbox-managed-settings.json', remoteMode: 0o644 },\n { name: 'agentbox-codex-hooks.json', remotePath: '/tmp/agentbox-codex-hooks.json', remoteMode: 0o644 },\n { name: 'agentbox-setup-skill.md', remotePath: '/tmp/agentbox-setup-skill.md', remoteMode: 0o644 },\n] as const;\n\nexport interface ResolvedAsset extends RuntimeAsset {\n localPath: string;\n}\n\nexport function candidatesFor(\n name: string,\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): string[] {\n const cliRoot = opts.cliRuntimeRoot;\n const monorepo = opts.repoRoot ?? guessRepoRoot();\n\n const monorepoRelative: Record<string, string[]> = {\n 'build-template.sh': ['packages/sandbox-e2b/scripts/build-template.sh'],\n 'agentbox-ctl': ['packages/ctl/dist/bin.cjs'],\n 'agentbox-vnc-start': ['packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['packages/sandbox-e2b/scripts/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const cliRelative: Record<string, string[]> = {\n 'build-template.sh': ['e2b/scripts/build-template.sh'],\n 'agentbox-ctl': ['e2b/ctl.cjs'],\n 'agentbox-vnc-start': ['e2b/agentbox-vnc-start', 'docker/packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['e2b/agentbox-checkpoint-cleanup', 'docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['e2b/agentbox-open', 'docker/packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['e2b/gh-shim', 'docker/packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['e2b/git-shim', 'docker/packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['e2b/ntn-shim', 'docker/packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['e2b/linear-shim', 'docker/packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['e2b/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['e2b/claude-managed-settings.json', 'docker/packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['e2b/agentbox-codex-hooks.json', 'docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['e2b/agentbox-setup-skill.md', 'docker/apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const out: string[] = [];\n if (cliRoot) {\n for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));\n }\n for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));\n return out;\n}\n\nexport function resolveRuntimeAssets(\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): ResolvedAsset[] {\n const out: ResolvedAsset[] = [];\n const missing: Array<{ name: string; tried: string[] }> = [];\n for (const asset of RUNTIME_ASSETS) {\n const cands = candidatesFor(asset.name, opts);\n const hit = cands.find((p) => existsSync(p));\n if (!hit) {\n missing.push({ name: asset.name, tried: cands });\n continue;\n }\n out.push({ ...asset, localPath: hit });\n }\n if (missing.length > 0) {\n const lines = missing.flatMap((m) => [` - ${m.name}: tried`, ...m.tried.map((p) => ` ${p}`)]);\n throw new Error(\n `e2b: could not resolve runtime assets needed to bake the base template:\\n` +\n lines.join('\\n') +\n `\\n\\nIf running from the monorepo, ensure \\`pnpm -w build\\` has run so packages/ctl/dist/bin.cjs exists.`,\n );\n }\n return out;\n}\n\nfunction guessRepoRoot(): string {\n let cur = SELF;\n for (let i = 0; i < 8; i++) {\n if (existsSync(resolve(cur, 'pnpm-workspace.yaml'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return SELF;\n}\n","/**\n * Persisted record of what `agentbox prepare --provider e2b` has built.\n * Lives at `~/.agentbox/e2b-prepared.json` so the auto-prepare gate\n * (`ensureE2bBaseTemplate()`) and `backend.provision` can resolve the base\n * template every box boots from.\n *\n * Single tier for now — the shared base template (Debian + agentbox-ctl +\n * agents). Templates on E2B are id+tag-addressed reusable resources, so unlike\n * Vercel snapshots we don't worry about per-box snapshot eviction; one template\n * is reused for every create.\n *\n * Schema versioned so future shape changes can migrate; only `schema: 1` is\n * accepted today.\n */\n\nimport { computeContextSha256, readPreparedStateRaw, writePreparedStateRaw, preparedStatePathFor } from '@agentbox/sandbox-core';\nimport { UserFacingError } from '@agentbox/core';\nimport { findStagedCliRuntimeRoot, resolveRuntimeAssets } from './runtime-assets.js';\n\nconst SCHEMA = 1 as const;\n\nexport interface PreparedE2bBase {\n /** Opaque E2B template id (e.g. `tmpl_xxxx` or `name:tag`). Sandbox.create({ template }) boots from this. */\n templateId: string;\n /** Human-friendly template name passed to Template.build (e.g. `agentbox-base:latest`). */\n templateName?: string;\n /** Deterministic SHA-256 of the build context (build script + assets). */\n contextSha256?: string;\n /** CLI version that produced this template (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n /** ISO timestamp of bake completion. */\n createdAt: string;\n}\n\nexport interface PreparedE2bState {\n schema: typeof SCHEMA;\n /** The shared base template. Absent until first `agentbox prepare`. */\n base?: PreparedE2bBase;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('e2b');\n}\n\nexport function readPreparedState(): PreparedE2bState {\n const raw = readPreparedStateRaw('e2b');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedE2bState>;\n if (parsed.schema !== SCHEMA) {\n // Unknown/missing schema: refuse to read — the next prepare overwrites it.\n return { schema: SCHEMA };\n }\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedState(state: PreparedE2bState): void {\n writePreparedStateRaw('e2b', state);\n}\n\n/** Update one field of the state without forcing callers to read/merge/write. */\nexport function updatePreparedState(mutate: (s: PreparedE2bState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n\n/**\n * Compute the CURRENT build-context fingerprint for the e2b base template\n * (the SHA over every file `prepare` would copy into the Template build).\n * Side-effect-free — never builds. Returns `undefined` when the runtime\n * assets can't be resolved (dev tree without `pnpm -w build`) so the CLI\n * can degrade to \"can't tell, don't nag\" rather than flag a false stale.\n *\n * Used by `evaluateBaseFreshness` to compare against the stored value in\n * `e2b-prepared.json.base.contextSha256`. Must produce a byte-identical\n * hash to the one `prepare` writes — both go through the same\n * `resolveRuntimeAssets` + `computeContextSha256` chain.\n */\nexport async function currentE2bBaseFingerprintLive(): Promise<string | undefined> {\n try {\n const assets = resolveRuntimeAssets({ cliRuntimeRoot: findStagedCliRuntimeRoot() });\n return await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * First-use gate. If no base template is recorded, throw an actionable error\n * pointing at `agentbox prepare --provider e2b`. Called by `backend.provision`\n * (so `create` / `claude` trip it but `prepare` itself does not — same shape\n * as the hetzner/vercel gates).\n */\nexport function ensureE2bBaseTemplate(): void {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new UserFacingError(\n 'no E2B base template found.\\n' +\n 'Run `agentbox prepare --provider e2b` first — it bakes a custom template ' +\n 'with the agentbox runtime (agentbox-ctl, vscode user, claude/codex/opencode, tmux) ' +\n 'so per-box `create` boots ready in seconds.',\n );\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","resolve","existsSync","dirname","resolve","existsSync","readFileSync","resolve","homedir","dirname"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-hetzner/src/env-loader.ts","../../../packages/sandbox-hetzner/src/client.ts","../../../packages/sandbox-hetzner/src/credentials.ts","../../../packages/sandbox-hetzner/src/egress-ip.ts","../../../packages/sandbox-hetzner/src/retry.ts","../../../packages/sandbox-hetzner/src/firewall.ts","../../../packages/sandbox-hetzner/src/runtime-assets.ts","../../../packages/sandbox-hetzner/src/prepared-state.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\n/**\n * Hetzner env auto-loader — mirrors `ensureDaytonaEnvLoaded()`. The Hetzner\n * REST client reads `HCLOUD_TOKEN` from `process.env`. We pull it in from\n * `~/.agentbox/secrets.env` so the client Just Works after the user runs\n * `agentbox hetzner login` once.\n *\n * Lookup order (first wins; process.env is never overwritten):\n * 1. `process.env` (already set in the shell).\n * 2. `~/.agentbox/secrets.env` — written by `agentbox hetzner login`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed, and a `HCLOUD_TOKEN` there\n * is typically meant for in-box infrastructure work, not for the host CLI to\n * harvest and provision VPSes with.\n *\n * Only Hetzner-prefixed keys are imported. Idempotent + side-effect-free\n * after the first call.\n */\nconst HETZNER_KEYS = ['HCLOUD_TOKEN', 'HCLOUD_ENDPOINT'] as const;\n\nlet loaded = false;\n\nexport function ensureHetznerEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importHetznerFromFile(resolve(homedir(), '.agentbox', 'secrets.env'));\n}\n\nfunction importHetznerFromFile(path: string): void {\n if (!existsSync(path)) return;\n let body: string;\n try {\n body = readFileSync(path, 'utf8');\n } catch {\n return;\n }\n const parsed = parseEnvFile(body);\n for (const key of HETZNER_KEYS) {\n if (process.env[key] !== undefined) continue;\n const value = parsed[key];\n if (typeof value === 'string') {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * Minimal `.env` parser: handles `KEY=value`, `KEY=\"value with spaces\"`,\n * `KEY='value with $special chars'`, `export KEY=value`, blank lines, and\n * `#` comments. Same shape as the daytona env-loader's parser — kept local\n * here rather than imported across packages to avoid the cycle (daytona\n * doesn't import from hetzner and shouldn't start now).\n */\nexport function parseEnvFile(body: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) continue;\n const key = stripped.slice(0, eq).trim();\n let value = stripped.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n","/**\n * Hetzner Cloud REST API client — hand-rolled fetch wrapper.\n *\n * Why not an SDK: the Hetzner SDK options are limited (no official JS SDK\n * with strict types at the time of writing), and the subset of the API we\n * need is small (servers, images, firewalls, plus a handful of read-only\n * lookups). A hand-rolled client gives us strict typing of just the fields\n * we touch, no heavy dep tree, and full control over the retry wrapper.\n *\n * Auth: bearer token in `HCLOUD_TOKEN` env. The env-loader pulls it from\n * `~/.agentbox/secrets.env` so the user only sets it once via\n * `agentbox hetzner login`.\n *\n * Errors: REST responses get unwrapped into typed `HetznerApiError`s that\n * carry the response `status` + the API's `error.code` / `error.message`.\n * Network failures bubble up as raw `Error`s with a `code` property\n * (ECONNRESET, ETIMEDOUT, …) — the retry wrapper classifies both shapes.\n */\n\nimport { ensureHetznerEnvLoaded } from './env-loader.js';\n\nexport const DEFAULT_HCLOUD_ENDPOINT = 'https://api.hetzner.cloud/v1';\n\n/**\n * Coarse Hetzner Cloud Server lifecycle states we care about. Hetzner has a\n * dozen finer-grained ones (`initializing`, `migrating`, `rebuilding`, …);\n * we map them in `backend.ts` to the four-value `CloudState` everyone else\n * consumes. Listed here so the client return types stay narrow.\n */\nexport type HetznerServerStatus =\n | 'running'\n | 'initializing'\n | 'starting'\n | 'stopping'\n | 'off'\n | 'deleting'\n | 'migrating'\n | 'rebuilding'\n | 'unknown';\n\nexport interface HetznerServer {\n id: number;\n name: string;\n status: HetznerServerStatus;\n created: string;\n public_net: {\n ipv4: { ip: string; blocked: boolean } | null;\n ipv6: { ip: string; blocked: boolean } | null;\n };\n server_type: { name: string; cores: number; memory: number; disk: number };\n image: { id: number; name?: string; description?: string; type: string } | null;\n labels: Record<string, string>;\n}\n\nexport interface HetznerAction {\n id: number;\n command: string;\n status: 'running' | 'success' | 'error';\n progress: number;\n error?: { code: string; message: string };\n}\n\nexport interface HetznerImage {\n id: number;\n type: 'system' | 'snapshot' | 'backup' | 'app';\n status: 'available' | 'creating' | 'unavailable';\n name?: string;\n description: string;\n image_size?: number;\n disk_size: number;\n created: string;\n labels: Record<string, string>;\n bound_to?: number;\n}\n\nexport interface HetznerFirewall {\n id: number;\n name: string;\n rules: HetznerFirewallRule[];\n applied_to: Array<{ type: 'server'; server: { id: number } }>;\n}\n\nexport interface HetznerFirewallRule {\n direction: 'in' | 'out';\n protocol: 'tcp' | 'udp' | 'icmp' | 'esp' | 'gre';\n port?: string;\n source_ips?: string[];\n destination_ips?: string[];\n description?: string;\n}\n\nexport interface HetznerSshKey {\n id: number;\n name: string;\n fingerprint: string;\n public_key: string;\n labels: Record<string, string>;\n}\n\nexport interface CreateServerRequest {\n name: string;\n server_type: string;\n image: string | number;\n location?: string;\n datacenter?: string;\n user_data?: string;\n ssh_keys?: Array<string | number>;\n firewalls?: Array<{ firewall: number }>;\n labels?: Record<string, string>;\n start_after_create?: boolean;\n public_net?: {\n enable_ipv4?: boolean;\n enable_ipv6?: boolean;\n };\n}\n\nexport interface CreateFirewallRequest {\n name: string;\n rules: HetznerFirewallRule[];\n labels?: Record<string, string>;\n apply_to?: Array<{ type: 'server'; server: { id: number } }>;\n}\n\n/**\n * Strongly-typed Hetzner API error. The Hetzner API consistently returns\n * `{ error: { code, message, details? } }` for 4xx/5xx (https://docs.hetzner.cloud/#errors).\n * We unwrap that into this class so callers can do `instanceof\n * HetznerApiError` and inspect `.code` / `.statusCode` without parsing the\n * body again.\n */\nexport class HetznerApiError extends Error {\n readonly statusCode: number;\n readonly code: string;\n readonly details?: unknown;\n constructor(statusCode: number, code: string, message: string, details?: unknown) {\n super(`hetzner ${String(statusCode)} ${code}: ${message}`);\n this.name = 'HetznerApiError';\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n }\n}\n\n/**\n * Subset of the Hetzner Cloud API the agentbox provider talks to. Methods\n * map 1:1 to REST endpoints; each operation is small + idempotent-where-the-\n * API-is-idempotent. The retry wrapper around the provider methods handles\n * transient 5xx / connection failures.\n */\nexport interface HetznerClient {\n /** GET /servers/{id}. Returns null on 404 so callers don't have to try/catch. */\n getServer(id: number): Promise<HetznerServer | null>;\n /** POST /servers. Returns the created server + the create action handle. */\n createServer(req: CreateServerRequest): Promise<{ server: HetznerServer; action: HetznerAction }>;\n /** GET /servers (with optional label selector). */\n listServers(opts?: { label_selector?: string }): Promise<HetznerServer[]>;\n /** DELETE /servers/{id}. Returns the action handle. Idempotent on 404. */\n deleteServer(id: number): Promise<HetznerAction | null>;\n /** POST /servers/{id}/actions/poweron. */\n powerOn(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/poweroff. */\n powerOff(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/shutdown — graceful, sends ACPI. */\n shutdown(id: number): Promise<HetznerAction>;\n /** POST /servers/{id}/actions/create_image — snapshot of the live disk. */\n createImage(\n id: number,\n body: { type: 'snapshot' | 'backup'; description?: string; labels?: Record<string, string> },\n ): Promise<{ image: HetznerImage; action: HetznerAction }>;\n /** GET /images/{id}. Returns null on 404. */\n getImage(id: number): Promise<HetznerImage | null>;\n /** GET /images (filterable). */\n listImages(opts?: {\n type?: 'system' | 'snapshot' | 'backup' | 'app';\n label_selector?: string;\n name?: string;\n }): Promise<HetznerImage[]>;\n /** DELETE /images/{id}. Idempotent on 404. */\n deleteImage(id: number): Promise<void>;\n /** POST /firewalls. */\n createFirewall(req: CreateFirewallRequest): Promise<HetznerFirewall>;\n /** POST /firewalls/{id}/actions/set_rules. Replaces the entire rule set. */\n setFirewallRules(id: number, rules: HetznerFirewallRule[]): Promise<HetznerAction[]>;\n /** GET /firewalls/{id}. Returns null on 404. */\n getFirewall(id: number): Promise<HetznerFirewall | null>;\n /** DELETE /firewalls/{id}. Idempotent on 404. */\n deleteFirewall(id: number): Promise<void>;\n /**\n * GET /locations — used by `agentbox hetzner login` to validate the token\n * with a cheap unauthenticated-shape call (the endpoint requires a valid\n * token but returns a small, stable response).\n */\n listLocations(): Promise<Array<{ id: number; name: string; city: string; country: string }>>;\n}\n\ninterface MakeClientOptions {\n /** Override the bearer token (else read from `HCLOUD_TOKEN`). */\n token?: string;\n /** Override the API base URL (else read from `HCLOUD_ENDPOINT` or use the default). */\n endpoint?: string;\n /** Per-request fetch impl (tests inject this). */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Build a Hetzner Cloud client bound to the current `HCLOUD_TOKEN`. The token\n * is resolved at construction time, so re-running `agentbox hetzner login` in\n * the middle of a long-lived process won't pick up the new token without a\n * fresh `makeHetznerClient()` call (we accept this — the CLI re-imports the\n * provider on each invocation).\n */\nexport function makeHetznerClient(opts: MakeClientOptions = {}): HetznerClient {\n ensureHetznerEnvLoaded();\n const rawToken = opts.token ?? process.env.HCLOUD_TOKEN;\n if (!rawToken || rawToken.trim().length === 0) {\n throw new Error(\n 'Hetzner credentials not configured: HCLOUD_TOKEN is empty.\\n' +\n 'Run `agentbox hetzner login` interactively, or set HCLOUD_TOKEN in the environment.',\n );\n }\n // Bind to a const so the type narrows for the closures below — without\n // this the `req()` closure sees the original `string | undefined` shape.\n const token: string = rawToken.trim();\n const endpoint = (opts.endpoint ?? process.env.HCLOUD_ENDPOINT ?? DEFAULT_HCLOUD_ENDPOINT).replace(/\\/$/, '');\n const fetchImpl = opts.fetchImpl ?? fetch;\n\n async function req<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: unknown,\n ): Promise<T | null> {\n const url = `${endpoint}${path}`;\n const init: RequestInit = {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n };\n const res = await fetchImpl(url, init);\n if (res.status === 204) return null;\n if (res.status === 404) return null;\n if (!res.ok) {\n let parsed: { error?: { code?: string; message?: string; details?: unknown } } = {};\n try {\n parsed = (await res.json()) as typeof parsed;\n } catch {\n // body wasn't json\n }\n const code = parsed.error?.code ?? `http_${String(res.status)}`;\n const msg = parsed.error?.message ?? res.statusText ?? 'unknown error';\n throw new HetznerApiError(res.status, code, msg, parsed.error?.details);\n }\n const text = await res.text();\n if (text.length === 0) return null;\n return JSON.parse(text) as T;\n }\n\n async function reqExpect<T>(\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: unknown,\n ): Promise<T> {\n const out = await req<T>(method, path, body);\n if (out === null) {\n throw new HetznerApiError(0, 'empty_response', `expected a body from ${method} ${path}`);\n }\n return out;\n }\n\n return {\n async getServer(id) {\n const r = await req<{ server: HetznerServer }>('GET', `/servers/${String(id)}`);\n return r?.server ?? null;\n },\n async createServer(reqBody) {\n const r = await reqExpect<{ server: HetznerServer; action: HetznerAction }>(\n 'POST',\n '/servers',\n reqBody,\n );\n return { server: r.server, action: r.action };\n },\n async listServers(opts) {\n const params = new URLSearchParams();\n if (opts?.label_selector) params.set('label_selector', opts.label_selector);\n params.set('per_page', '50');\n const all: HetznerServer[] = [];\n let pageNum = 1;\n while (true) {\n params.set('page', String(pageNum));\n const r = await reqExpect<{\n servers: HetznerServer[];\n meta?: { pagination?: { next_page?: number | null } };\n }>('GET', `/servers?${params.toString()}`);\n all.push(...r.servers);\n const next = r.meta?.pagination?.next_page;\n if (typeof next !== 'number') break;\n pageNum = next;\n }\n return all;\n },\n async deleteServer(id) {\n const r = await req<{ action: HetznerAction }>('DELETE', `/servers/${String(id)}`);\n return r?.action ?? null;\n },\n async powerOn(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/poweron`,\n );\n return r.action;\n },\n async powerOff(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/poweroff`,\n );\n return r.action;\n },\n async shutdown(id) {\n const r = await reqExpect<{ action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/shutdown`,\n );\n return r.action;\n },\n async createImage(id, body) {\n const r = await reqExpect<{ image: HetznerImage; action: HetznerAction }>(\n 'POST',\n `/servers/${String(id)}/actions/create_image`,\n body,\n );\n return { image: r.image, action: r.action };\n },\n async getImage(id) {\n const r = await req<{ image: HetznerImage }>('GET', `/images/${String(id)}`);\n return r?.image ?? null;\n },\n async listImages(opts) {\n const params = new URLSearchParams();\n if (opts?.type) params.set('type', opts.type);\n if (opts?.label_selector) params.set('label_selector', opts.label_selector);\n if (opts?.name) params.set('name', opts.name);\n params.set('per_page', '50');\n const all: HetznerImage[] = [];\n let pageNum = 1;\n while (true) {\n params.set('page', String(pageNum));\n const r = await reqExpect<{\n images: HetznerImage[];\n meta?: { pagination?: { next_page?: number | null } };\n }>('GET', `/images?${params.toString()}`);\n all.push(...r.images);\n const next = r.meta?.pagination?.next_page;\n if (typeof next !== 'number') break;\n pageNum = next;\n }\n return all;\n },\n async deleteImage(id) {\n await req<unknown>('DELETE', `/images/${String(id)}`);\n },\n async createFirewall(reqBody) {\n const r = await reqExpect<{ firewall: HetznerFirewall }>('POST', '/firewalls', reqBody);\n return r.firewall;\n },\n async setFirewallRules(id, rules) {\n const r = await reqExpect<{ actions: HetznerAction[] }>(\n 'POST',\n `/firewalls/${String(id)}/actions/set_rules`,\n { rules },\n );\n return r.actions;\n },\n async getFirewall(id) {\n const r = await req<{ firewall: HetznerFirewall }>('GET', `/firewalls/${String(id)}`);\n return r?.firewall ?? null;\n },\n async deleteFirewall(id) {\n await req<unknown>('DELETE', `/firewalls/${String(id)}`);\n },\n async listLocations() {\n const r = await reqExpect<{\n locations: Array<{ id: number; name: string; city: string; country: string }>;\n }>('GET', '/locations');\n return r.locations;\n },\n };\n}\n","import { spawnSync } from 'node:child_process';\nimport { hostOpenCommand } from '@agentbox/sandbox-core';\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, resolve } from 'node:path';\nimport { confirm, isCancel, intro, log, note, outro, password, spinner } from '@clack/prompts';\nimport { makeHetznerClient } from './client.js';\nimport { ensureHetznerEnvLoaded } from './env-loader.js';\n\nconst DASHBOARD_KEYS_URL = 'https://console.hetzner.cloud/projects';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. When the user reconfigures\n * we strip prior values before appending so the file never accumulates\n * duplicates. `HCLOUD_ENDPOINT` is honored but we don't prompt for it\n * (default endpoint covers 100% of users).\n */\nconst MANAGED_KEYS = ['HCLOUD_TOKEN', 'HCLOUD_ENDPOINT'] as const;\ntype ManagedKey = (typeof MANAGED_KEYS)[number];\n\nexport interface EnsureHetznerCredentialsOptions {\n /** Re-prompt even when valid credentials are already present (used by `agentbox hetzner login`). */\n force?: boolean;\n}\n\n/**\n * First-run interactive setup for Hetzner credentials. Walks the user\n * through creating a project API token, pasting it, validating, and\n * persisting to `~/.agentbox/secrets.env`.\n *\n * No-op when credentials are already configured (env var or our secrets\n * file). Silent no-op when stdin isn't a TTY so scripted/CI callers get\n * the API \"401 unauthorized\" error instead of a hung prompt.\n *\n * Mirrors `ensureDaytonaCredentials()` in shape so the registry's first-\n * run gate stays uniform across providers.\n */\nexport async function ensureHetznerCredentials(\n opts: EnsureHetznerCredentialsOptions = {},\n): Promise<void> {\n ensureHetznerEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('Hetzner Cloud setup');\n note(\n `AgentBox needs a Hetzner Cloud API token (project-scoped) to provision VPSes.\\n\\n` +\n `1. Open ${DASHBOARD_KEYS_URL}\\n` +\n `2. Pick a project (or create one).\\n` +\n `3. Security → API Tokens → Generate API Token (Read + Write).`,\n 'API token required',\n );\n\n const open = await confirm({\n message: `Open ${DASHBOARD_KEYS_URL} in your browser?`,\n initialValue: true,\n });\n if (isCancel(open)) {\n log.warn('Hetzner setup cancelled — re-run `agentbox hetzner login` when ready.');\n return;\n }\n if (open) openDashboard();\n\n // One retry on auth failure (typos / expired token are the common case).\n for (let attempt = 0; attempt < 2; attempt++) {\n const creds = await promptForCredentials();\n if (creds === null) return;\n\n const result = await validateCredentials(creds);\n if (result.ok) {\n persistCredentials(creds);\n log.success(`Hetzner credentials saved to ${secretsPath()}`);\n outro('Setup complete.');\n return;\n }\n if (result.kind === 'auth' && attempt === 0) {\n log.error(`That token was rejected by Hetzner: ${result.message}`);\n log.info('Try again, or press Ctrl-C to cancel.');\n continue;\n }\n if (result.kind === 'network') {\n log.warn(`Could not reach Hetzner to validate (${result.message}) — saving anyway.`);\n persistCredentials(creds);\n log.success(`Hetzner credentials saved to ${secretsPath()}`);\n outro('Setup complete (unvalidated).');\n return;\n }\n throw new Error(`Hetzner credentials rejected: ${result.message}`);\n }\n}\n\nfunction hasUsableCredentials(): boolean {\n return typeof process.env.HCLOUD_TOKEN === 'string' && process.env.HCLOUD_TOKEN.length > 0;\n}\n\ninterface Credentials {\n token: string;\n endpoint?: string;\n}\n\nasync function promptForCredentials(): Promise<Credentials | null> {\n const token = await password({\n message: 'Paste your Hetzner Cloud API token',\n validate(v) {\n if (!v || v.trim().length === 0) return 'Cannot be empty';\n return undefined;\n },\n });\n if (isCancel(token)) {\n log.warn('Hetzner setup cancelled.');\n return null;\n }\n return { token: token.trim() };\n}\n\ntype ValidationResult =\n | { ok: true }\n | { ok: false; kind: 'auth'; message: string }\n | { ok: false; kind: 'network'; message: string };\n\nasync function validateCredentials(creds: Credentials): Promise<ValidationResult> {\n const s = spinner();\n s.start('Validating credentials with Hetzner');\n\n try {\n const client = makeHetznerClient({ token: creds.token, endpoint: creds.endpoint });\n // `listLocations()` is a cheap, deterministic call that exercises auth +\n // basic API reachability without provisioning anything.\n await client.listLocations();\n s.stop('Hetzner credentials accepted');\n return { ok: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n s.stop('Hetzner credentials check failed');\n if (/401|403|unauthor|forbidden|invalid|token/i.test(message)) {\n return { ok: false, kind: 'auth', message };\n }\n return { ok: false, kind: 'network', message };\n }\n}\n\nfunction persistCredentials(creds: Credentials): void {\n process.env.HCLOUD_TOKEN = creds.token;\n if (creds.endpoint) process.env.HCLOUD_ENDPOINT = creds.endpoint;\n const path = secretsPath();\n mkdirSync(dirname(path), { recursive: true });\n\n let existing = '';\n if (existsSync(path)) {\n try {\n existing = readFileSync(path, 'utf8');\n } catch {\n existing = '';\n }\n }\n\n const kept = existing\n .split(/\\r?\\n/)\n .filter((line) => {\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) return true;\n const key = stripped.slice(0, eq).trim();\n return !(MANAGED_KEYS as readonly string[]).includes(key);\n })\n .join('\\n')\n .replace(/\\s+$/u, '');\n\n const lines: string[] = [`HCLOUD_TOKEN=${creds.token}`];\n if (creds.endpoint) lines.push(`HCLOUD_ENDPOINT=${creds.endpoint}`);\n\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n try {\n chmodSync(tmp, 0o600);\n } catch {\n // chmod best-effort; writeFileSync mode already covers most filesystems.\n }\n renameSync(tmp, path);\n try {\n chmodSync(path, 0o600);\n } catch {\n // ignore — already attempted above.\n }\n}\n\nfunction openDashboard(): void {\n try {\n const r = spawnSync(hostOpenCommand(), [DASHBOARD_KEYS_URL], { stdio: 'ignore' });\n if (r.status !== 0) {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n }\n } catch {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_KEYS_URL} manually.`);\n }\n}\n\nexport function secretsPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\nexport interface HetznerCredStatus {\n token?: string;\n endpoint?: string;\n source: 'env' | 'secrets.env' | 'none';\n}\n\nexport function readHetznerCredStatus(): HetznerCredStatus {\n const shellHadToken = !!process.env.HCLOUD_TOKEN;\n ensureHetznerEnvLoaded();\n const token = process.env.HCLOUD_TOKEN;\n const endpoint = process.env.HCLOUD_ENDPOINT;\n if (!token) return { source: 'none' };\n return {\n token,\n endpoint,\n source: shellHadToken ? 'env' : 'secrets.env',\n };\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return '*'.repeat(value.length);\n return `${value.slice(0, 4)}…${'*'.repeat(8)}${value.slice(-4)}`;\n}\n\n/** Snapshot of the managed env keys (used by tests around `applyToEnv`). */\nexport function snapshotManagedEnv(): Record<ManagedKey, string | undefined> {\n const out = {} as Record<ManagedKey, string | undefined>;\n for (const k of MANAGED_KEYS) out[k] = process.env[k];\n return out;\n}\n\nexport function restoreManagedEnv(snap: Record<ManagedKey, string | undefined>): void {\n for (const k of MANAGED_KEYS) {\n if (snap[k] === undefined) delete process.env[k];\n else process.env[k] = snap[k];\n }\n}\n","/**\n * Host egress-IP detection for the Hetzner firewall lock-down. Probes three\n * independent providers in sequence; first 3s success wins. Fails loud\n * (throws) if all three fail — we do **not** silently fall back to\n * `0.0.0.0/0`, because that would defeat the safe-by-default firewall.\n *\n * The user can always override the auto-detect via\n * `--firewall-source <cidr>` (or `--firewall-source 0.0.0.0/0` for the\n * explicit dynamic-IP opt-in).\n */\n\nconst PROBES = [\n 'https://api.ipify.org',\n 'https://ifconfig.io/ip',\n 'https://icanhazip.com',\n] as const;\n\nconst TIMEOUT_MS = 3_000;\n\nconst IPV4_RE = /^(?:\\d{1,3}\\.){3}\\d{1,3}$/;\nconst IPV6_RE = /^[0-9a-fA-F:]+$/;\n\nexport interface DetectEgressIpOptions {\n /** Override the probe list (tests inject this). */\n probes?: readonly string[];\n /** Per-probe timeout in ms (default 3_000). */\n timeoutMs?: number;\n /** Override `fetch` (tests inject this). */\n fetchImpl?: typeof fetch;\n /** Best-effort logger for probe attempts. */\n onLog?: (line: string) => void;\n}\n\n/**\n * Detect the host's egress IP. Returns the bare IP string (no `/32`); the\n * caller composes the CIDR.\n *\n * Throws when no probe responded. The error message lists each probe that\n * was tried so the user can see whether their network is blocking a\n * specific provider.\n */\nexport async function detectEgressIp(opts: DetectEgressIpOptions = {}): Promise<string> {\n const probes = opts.probes ?? PROBES;\n const timeout = opts.timeoutMs ?? TIMEOUT_MS;\n const fetchImpl = opts.fetchImpl ?? fetch;\n const errors: string[] = [];\n\n for (const url of probes) {\n try {\n const ip = await raceTimeout(probe(url, fetchImpl), timeout);\n if (ip) {\n opts.onLog?.(`egress-ip: detected ${ip} via ${url}`);\n return ip;\n }\n errors.push(`${url}: empty/invalid response`);\n } catch (err) {\n errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n throw new Error(\n `could not auto-detect the host's egress IP — all ${String(probes.length)} probes failed:\\n` +\n errors.map((e) => ` - ${e}`).join('\\n') +\n `\\nOverride with --firewall-source <cidr> (e.g. --firewall-source 0.0.0.0/0 for the explicit-open opt-in).`,\n );\n}\n\nasync function probe(url: string, fetchImpl: typeof fetch): Promise<string | null> {\n const res = await fetchImpl(url, { method: 'GET' });\n if (!res.ok) return null;\n const body = (await res.text()).trim();\n if (IPV4_RE.test(body)) {\n // Cheap sanity: each octet in 0–255.\n const parts = body.split('.').map((p) => Number.parseInt(p, 10));\n if (parts.every((p) => p >= 0 && p <= 255)) return body;\n return null;\n }\n // We do not currently use IPv6 for firewall rules (Hetzner accepts them\n // but the rest of the provider talks IPv4), but accept the probe answer\n // so a v6-only network surfaces an actionable error rather than a silent\n // empty result. Composing the CIDR is the caller's job.\n if (IPV6_RE.test(body) && body.includes(':')) return body;\n return null;\n}\n\nasync function raceTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new Error(`probe timed out after ${String(ms)}ms`)), ms);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n","/**\n * Bounded retry wrapper for Hetzner Cloud API calls — mirrors\n * `withDaytonaRetry` in shape and intent. Hetzner is generally well-behaved\n * but the public API does rate-limit (429) and occasionally returns 502/504\n * during regional incidents; without bounded retries those propagate as\n * wedges in the calling lifecycle code.\n *\n * Non-idempotent ops (`provision`, `createImage`) pass\n * `retryOnAmbiguous: false` so a 504 after the request reached the origin\n * doesn't create a duplicate billable resource.\n */\n\nimport { HetznerApiError } from './client.js';\n\nexport interface WithRetryOptions {\n /** Method name, used in retry log lines. */\n method: string;\n /** Per-attempt timeout (ms). Default 30_000. */\n attemptTimeoutMs?: number;\n /** Backoff before attempts 2, 3, … (ms). Default [1000, 2000, 4000]. */\n backoffMs?: readonly number[];\n /**\n * Whether to retry on errors where we can't be sure the server applied\n * the request — connection failures, per-attempt timeouts, and 5xx\n * responses. Set false for non-idempotent operations (e.g. `provision`,\n * `createImage`) where a retry could create a duplicate resource.\n */\n retryOnAmbiguous: boolean;\n /** Override the default `process.stderr` retry sink (used by tests). */\n onRetry?: (line: string) => void;\n}\n\nconst DEFAULT_BACKOFF: readonly number[] = [1000, 2000, 4000];\nconst DEFAULT_ATTEMPT_TIMEOUT_MS = 30_000;\n\nclass AttemptTimeoutError extends Error {\n constructor(method: string, ms: number) {\n super(`hetzner ${method}: per-attempt timeout after ${String(ms)}ms`);\n this.name = 'AttemptTimeoutError';\n }\n}\n\nexport function isAttemptTimeout(err: unknown): err is AttemptTimeoutError {\n return err instanceof AttemptTimeoutError;\n}\n\n/**\n * Classify an error as retriable or not. `allowAmbiguous` gates the cases\n * where the server may or may not have applied the request — the caller\n * decides based on idempotency.\n */\nexport function isRetriable(err: unknown, allowAmbiguous: boolean): boolean {\n if (err instanceof HetznerApiError) {\n // Rate limit: always back off — the server told us to.\n if (err.statusCode === 429 || err.code === 'rate_limit_exceeded') return true;\n // 5xx: ambiguous (the API may or may not have applied the change).\n if (err.statusCode >= 500 && err.statusCode <= 599) return allowAmbiguous;\n // Hetzner conflict / locked errors: the API tells us to wait — same as\n // rate-limit semantically. `conflict` is what `delete_server` returns\n // when another action (e.g. our own poweroff) is still in flight.\n if (err.code === 'locked' || err.code === 'conflict') return true;\n // Everything else is a permanent client error (auth, validation, not_found).\n return false;\n }\n\n if (err instanceof AttemptTimeoutError) return allowAmbiguous;\n\n // Raw fetch / undici errors. The Node fetch impl wraps low-level errors in\n // `{ cause }`; we check both shapes for portability.\n if (err && typeof err === 'object') {\n const candidates: unknown[] = [err, (err as { cause?: unknown }).cause];\n for (const c of candidates) {\n if (!c || typeof c !== 'object') continue;\n const code = (c as { code?: unknown }).code;\n if (\n code === 'ECONNRESET' ||\n code === 'ETIMEDOUT' ||\n code === 'ECONNABORTED' ||\n code === 'EAI_AGAIN' ||\n code === 'ECONNREFUSED' ||\n code === 'ENOTFOUND' ||\n code === 'UND_ERR_SOCKET' ||\n code === 'UND_ERR_CONNECT_TIMEOUT'\n ) {\n return allowAmbiguous;\n }\n }\n }\n\n return false;\n}\n\n/**\n * Run `fn`, retrying on transient failures with capped exponential backoff.\n * Each attempt is bounded by `attemptTimeoutMs` via Promise.race; total\n * wall-clock = sum(backoffMs) + maxAttempts * attemptTimeoutMs.\n */\nexport async function withHetznerRetry<T>(\n opts: WithRetryOptions,\n fn: () => Promise<T>,\n): Promise<T> {\n const backoff = opts.backoffMs ?? DEFAULT_BACKOFF;\n const maxAttempts = backoff.length + 1;\n const timeoutMs = opts.attemptTimeoutMs ?? DEFAULT_ATTEMPT_TIMEOUT_MS;\n const log = opts.onRetry ?? defaultRetryLog;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await raceTimeout(fn(), timeoutMs, opts.method);\n } catch (err) {\n const last = attempt === maxAttempts;\n if (last || !isRetriable(err, opts.retryOnAmbiguous)) throw err;\n const delay = backoff[attempt - 1] ?? backoff[backoff.length - 1] ?? 4000;\n log(\n `hetzner ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`,\n );\n await sleep(delay);\n }\n }\n throw new Error(`withHetznerRetry: exhausted attempts for ${opts.method}`);\n}\n\nfunction defaultRetryLog(line: string): void {\n process.stderr.write(`\\n[hetzner-retry] ${line}\\n`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function raceTimeout<T>(p: Promise<T>, ms: number, method: string): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n p,\n new Promise<never>((_resolve, reject) => {\n timer = setTimeout(() => reject(new AttemptTimeoutError(method, ms)), ms);\n }),\n ]);\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n}\n\nfunction errorSummary(err: unknown): string {\n if (err instanceof HetznerApiError) {\n return `HetznerApiError ${String(err.statusCode)} ${err.code}: ${truncate(err.message)}`;\n }\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n return code !== undefined\n ? `${err.name}(${String(code)}): ${truncate(err.message)}`\n : `${err.name}: ${truncate(err.message)}`;\n }\n return truncate(String(err));\n}\n\nfunction truncate(s: string, max = 160): string {\n return s.length > max ? `${s.slice(0, max)}…` : s;\n}\n","/**\n * Hetzner Cloud Firewall provisioning + drift sync.\n *\n * Defense-in-depth model (recapped from\n * ~/.claude/plans/how-to-safely-create-parallel-pebble.md §\"The safety model\"):\n *\n * 1. In-VPS services bind to loopback (the load-bearing layer).\n * 2. Hetzner Cloud Firewall locks SSH to the host's egress IP — applied\n * here at provision time, before the VPS first boots. Everything else\n * is denied inbound; outbound is unrestricted.\n * 3. sshd hardening (PasswordAuthentication no, AllowUsers vscode, …)\n * written by cloud-init at first boot.\n *\n * Layer 2 is what this module provisions. The firewall is per-box (1:1 with\n * the VPS) so an egress-IP-drift on one box doesn't affect siblings, and a\n * destroy cleanly removes everything we created.\n */\n\nimport { HetznerApiError, type HetznerClient, type HetznerFirewall, type HetznerFirewallRule } from './client.js';\nimport { withHetznerRetry } from './retry.js';\n\n/**\n * Build the SSH-only inbound rule for a given source CIDR. Outbound is\n * left unrestricted (empty rules array = \"no inbound besides this one\").\n */\nexport function sshOnlyInboundRule(sourceCidr: string): HetznerFirewallRule[] {\n return [\n {\n direction: 'in',\n protocol: 'tcp',\n port: '22',\n source_ips: [sourceCidr],\n description: 'agentbox: SSH from host egress IP only',\n },\n ];\n}\n\nexport interface CreateFirewallOptions {\n /** Human-readable name persisted with the firewall (visible in the Hetzner dashboard). */\n name: string;\n /** Source CIDR (e.g. `1.2.3.4/32`). The caller is responsible for normalizing the suffix. */\n sourceCidr: string;\n /** Labels merged onto the firewall (we always add `agentbox.managed=true`). */\n labels?: Record<string, string>;\n}\n\n/**\n * Provision a fresh per-box firewall locked to the given source CIDR.\n * Returns the created `HetznerFirewall` so the caller can persist\n * `firewallId` on the box record.\n */\nexport async function createPerBoxFirewall(\n client: HetznerClient,\n opts: CreateFirewallOptions,\n): Promise<HetznerFirewall> {\n return withHetznerRetry(\n { method: 'createFirewall', retryOnAmbiguous: false, attemptTimeoutMs: 60_000 },\n () =>\n client.createFirewall({\n name: opts.name,\n rules: sshOnlyInboundRule(opts.sourceCidr),\n labels: {\n 'agentbox.managed': 'true',\n 'agentbox.role': 'box',\n ...opts.labels,\n },\n }),\n );\n}\n\n/**\n * Re-detect the egress IP and replace the firewall's rule set with the new\n * source. Used by `agentbox hetzner firewall sync <box>` after the host\n * laptop moves networks. Cheap operation — no VPS restart involved.\n *\n * Idempotent on the API: setting the same rules again is a no-op from the\n * user's point of view (the API still returns an action handle, but it\n * resolves instantly).\n */\nexport async function syncFirewallSource(\n client: HetznerClient,\n firewallId: number,\n sourceCidr: string,\n): Promise<void> {\n await withHetznerRetry(\n { method: 'setFirewallRules', retryOnAmbiguous: true, attemptTimeoutMs: 60_000 },\n () => client.setFirewallRules(firewallId, sshOnlyInboundRule(sourceCidr)),\n );\n}\n\n/**\n * Delete a per-box firewall. Idempotent on 404 (the API surfaces it as a\n * `not_found` error which the retry classifier won't retry; we swallow it\n * here so destroy paths don't need a special-case).\n *\n * Hetzner returns 409 `conflict` if the firewall is still attached to a\n * server when we try to delete it — `deleteServer()` returns as soon as the\n * delete action is *enqueued*, not after the server's firewall attachment\n * is torn down, so a quick subsequent `deleteFirewall()` will collide.\n * We poll for a short window (default 60s, intervals doubled to 8s) to\n * cover the typical 5–15s detach lag before giving up.\n */\nexport async function deletePerBoxFirewall(\n client: HetznerClient,\n firewallId: number,\n opts: { detachWaitMs?: number } = {},\n): Promise<void> {\n const deadline = Date.now() + (opts.detachWaitMs ?? 60_000);\n let interval = 1_000;\n while (true) {\n try {\n await withHetznerRetry(\n { method: 'deleteFirewall', retryOnAmbiguous: true, attemptTimeoutMs: 30_000 },\n () => client.deleteFirewall(firewallId),\n );\n return;\n } catch (err) {\n if (err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found')) {\n return;\n }\n const stillAttached =\n err instanceof HetznerApiError &&\n (err.statusCode === 409 ||\n err.code === 'conflict' ||\n err.code === 'resource_in_use');\n if (stillAttached && Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, interval));\n interval = Math.min(interval * 2, 8_000);\n continue;\n }\n throw err;\n }\n }\n}\n\n/**\n * Normalize a source spec into a CIDR. Accepts:\n * - bare IPv4 → appends `/32`\n * - bare IPv6 → appends `/128`\n * - already-CIDR (anything with `/`) → returned as-is\n *\n * Whitespace is trimmed. Does **not** validate the address itself — that's\n * either the API's job (it'll reject bad CIDRs with a clear `validation`\n * error) or `detectEgressIp`'s job (it only returns valid IPv4/IPv6).\n */\nexport function normalizeSourceCidr(raw: string): string {\n const trimmed = raw.trim();\n if (trimmed.includes('/')) return trimmed;\n if (trimmed.includes(':')) return `${trimmed}/128`;\n return `${trimmed}/32`;\n}\n","/**\n * Resolver for the on-disk files we need to ship into a fresh VPS during\n * `prepareHetzner()` — same shape as the docker provider's runtime/docker\n * staging, but lighter (no Dockerfile build, just a flat tarball of files\n * to scp into /tmp).\n *\n * Lookup order for each file:\n * 1. The CLI's staged runtime tree: `<cliRoot>/runtime/hetzner/...`\n * (populated by `apps/cli/scripts/stage-runtime.mjs`).\n * 2. The monorepo source tree (dev fallback): the file's canonical\n * package-relative path under `packages/`.\n *\n * Failure mode: any missing file throws a clear error naming the lookup\n * paths so a partial dev rebuild is obvious to debug.\n */\n\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\n/**\n * Locate the staged `runtime/hetzner/` tree. Two candidates:\n *\n * 1. Bundled CLI: the hetzner module is inlined into apps/cli's dist/, so\n * `dirname(import.meta.url)` is `<cliRoot>/dist`; the staged runtime\n * sits at `<cliRoot>/runtime/hetzner`.\n * 2. Workspace dev: this module's dist is at\n * `packages/sandbox-hetzner/dist/`. There's no staged runtime there;\n * callers fall through to the monorepo source paths in\n * `candidatesFor()`. Returns undefined in that case.\n *\n * The CLI doesn't have to pass `cliRuntimeRoot` explicitly — this helper\n * picks it up by inspecting where the module was loaded from.\n */\nexport function findStagedCliRuntimeRoot(): string | undefined {\n const candidates = [\n resolve(SELF, '..', 'runtime'), // <cliRoot>/dist/.. → <cliRoot> then /runtime\n resolve(SELF, '..', '..', 'runtime'), // chunk-NNNN.js at <cliRoot>/dist/<sub>/.. → <cliRoot>/runtime\n ];\n for (const c of candidates) {\n if (existsSync(resolve(c, 'hetzner', 'scripts', 'install-box.sh'))) return c;\n }\n return undefined;\n}\n\n/**\n * Each runtime asset has a stable, well-known destination basename in\n * `/tmp` on the prepare VPS and is resolved from one of N candidate\n * source paths on the host.\n */\nexport interface RuntimeAsset {\n /** Logical name (used in error messages + log lines). */\n name: string;\n /** Basename on the prepare VPS (under /tmp/). */\n remoteBasename: string;\n /** Optional file mode at scp-time. */\n remoteMode?: number;\n}\n\nexport const RUNTIME_ASSETS: readonly RuntimeAsset[] = [\n { name: 'install-box.sh', remoteBasename: 'agentbox-install.sh', remoteMode: 0o755 },\n { name: 'agentbox-ctl', remoteBasename: 'agentbox-ctl', remoteMode: 0o755 },\n { name: 'agentbox-vnc-start', remoteBasename: 'agentbox-vnc-start', remoteMode: 0o755 },\n { name: 'agentbox-dockerd-start', remoteBasename: 'agentbox-dockerd-start', remoteMode: 0o755 },\n { name: 'agentbox-checkpoint-cleanup', remoteBasename: 'agentbox-checkpoint-cleanup', remoteMode: 0o755 },\n { name: 'agentbox-open', remoteBasename: 'agentbox-open', remoteMode: 0o755 },\n { name: 'gh-shim', remoteBasename: 'agentbox-gh-shim', remoteMode: 0o755 },\n { name: 'git-shim', remoteBasename: 'agentbox-git-shim', remoteMode: 0o755 },\n { name: 'ntn-shim', remoteBasename: 'agentbox-ntn-shim', remoteMode: 0o755 },\n { name: 'linear-shim', remoteBasename: 'agentbox-linear-shim', remoteMode: 0o755 },\n { name: 'custom-system-CLAUDE.md', remoteBasename: 'agentbox-custom-CLAUDE.md', remoteMode: 0o644 },\n { name: 'claude-managed-settings.json', remoteBasename: 'agentbox-managed-settings.json', remoteMode: 0o644 },\n { name: 'agentbox-codex-hooks.json', remoteBasename: 'agentbox-codex-hooks.json', remoteMode: 0o644 },\n { name: 'agentbox-setup-skill.md', remoteBasename: 'agentbox-setup-skill.md', remoteMode: 0o644 },\n] as const;\n\nexport interface ResolvedAsset extends RuntimeAsset {\n localPath: string;\n}\n\n/**\n * Build the candidate search paths for a given asset. Tries CLI runtime\n * first, then the monorepo source tree. The first one that exists wins.\n *\n * `cliRuntimeRoot` is provided by the caller because we don't know how the\n * embedding CLI lays out its dist (the @madarco/agentbox CLI puts dist + a\n * sibling runtime/ next to it; tests pass a tmp dir). Use the helper\n * `findCliRuntimeRoot()` below from the calling context that has the right\n * anchor (typically `import.meta.url` of an apps/cli module).\n */\nexport function candidatesFor(\n name: string,\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): string[] {\n const cliRoot = opts.cliRuntimeRoot;\n const monorepo = opts.repoRoot ?? guessRepoRoot();\n\n // Map logical → relative paths (relative to either anchor).\n const monorepoRelative: Record<string, string[]> = {\n 'install-box.sh': ['packages/sandbox-hetzner/scripts/install-box.sh'],\n 'agentbox-ctl': ['packages/ctl/dist/bin.cjs'],\n 'agentbox-vnc-start': ['packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-dockerd-start': ['packages/sandbox-docker/scripts/agentbox-dockerd-start'],\n 'agentbox-checkpoint-cleanup': ['packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['packages/sandbox-hetzner/scripts/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n // CLI-runtime-tree relative paths (mirrors the staging layout).\n const cliRelative: Record<string, string[]> = {\n 'install-box.sh': ['hetzner/scripts/install-box.sh'],\n 'agentbox-ctl': ['hetzner/ctl.cjs'],\n 'agentbox-vnc-start': ['hetzner/agentbox-vnc-start', 'docker/packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-dockerd-start': ['hetzner/agentbox-dockerd-start', 'docker/packages/sandbox-docker/scripts/agentbox-dockerd-start'],\n 'agentbox-checkpoint-cleanup': ['hetzner/agentbox-checkpoint-cleanup', 'docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['hetzner/agentbox-open', 'docker/packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['hetzner/gh-shim', 'docker/packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['hetzner/git-shim', 'docker/packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['hetzner/ntn-shim', 'docker/packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['hetzner/linear-shim', 'docker/packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['hetzner/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['hetzner/claude-managed-settings.json', 'docker/packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['hetzner/agentbox-codex-hooks.json', 'docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['hetzner/agentbox-setup-skill.md', 'docker/apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const out: string[] = [];\n if (cliRoot) {\n for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));\n }\n for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));\n return out;\n}\n\n/**\n * Resolve every runtime asset to its absolute on-host path. Throws an\n * actionable error if any asset can't be found (lists every path tried).\n */\nexport function resolveRuntimeAssets(opts: {\n cliRuntimeRoot?: string;\n repoRoot?: string;\n} = {}): ResolvedAsset[] {\n const out: ResolvedAsset[] = [];\n const missing: Array<{ name: string; tried: string[] }> = [];\n for (const asset of RUNTIME_ASSETS) {\n const cands = candidatesFor(asset.name, opts);\n const hit = cands.find((p) => existsSync(p));\n if (!hit) {\n missing.push({ name: asset.name, tried: cands });\n continue;\n }\n out.push({ ...asset, localPath: hit });\n }\n if (missing.length > 0) {\n const lines = missing.flatMap((m) => [` - ${m.name}: tried`, ...m.tried.map((p) => ` ${p}`)]);\n throw new Error(\n `hetzner: could not resolve runtime assets — these files are needed to install on the prepare VPS:\\n` +\n lines.join('\\n') +\n `\\n\\nIf you are running from the monorepo, ensure \\`pnpm -w build\\` has run so packages/ctl/dist/bin.cjs exists. ` +\n `If you are running from a published CLI bundle, the runtime/hetzner tree should be staged automatically.`,\n );\n }\n return out;\n}\n\n/** Best-effort: walk up from this file looking for `pnpm-workspace.yaml`. */\nfunction guessRepoRoot(): string {\n let cur = SELF;\n for (let i = 0; i < 8; i++) {\n if (existsSync(resolve(cur, 'pnpm-workspace.yaml'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return SELF; // fall through to itself; resolution will fail with a clear error\n}\n","/**\n * Persisted record of what `agentbox prepare --provider hetzner` has built.\n * Lives at `~/.agentbox/hetzner-prepared.json` so the auto-prepare gate\n * (`ensureHetznerBaseSnapshot()`) and runtime image resolution can see it.\n *\n * Only the shared `base` snapshot is recorded here — built once per Hetzner\n * project / API token: Ubuntu + deps + agentbox-ctl + agents + agent-browser,\n * baked from `install-box.sh`.\n *\n * The per-project snapshot tier is NOT a separate registry: it's the existing\n * `agentbox checkpoint create --set-default` + `box.defaultCheckpointHetzner`\n * flow (see `docs/cloud-create-flow.md` §\"base vs project snapshot\"), and\n * auto-capture at the end of setup is driven by the `/agentbox-setup` skill\n * (`agentbox-ctl checkpoint --set-default`), cross-provider. So there's no\n * `projects[<hash>]` map here.\n *\n * Schema versioned so future shape changes can migrate.\n */\n\nimport { computeContextSha256, preparedStatePathFor, readPreparedStateRaw, writePreparedStateRaw } from '@agentbox/sandbox-core';\nimport { findStagedCliRuntimeRoot, resolveRuntimeAssets } from './runtime-assets.js';\n\n/**\n * Schema history:\n * 1 — `base.imageId`, `base.description`, `base.createdAt`,\n * `base.installScriptSha256?`\n * 2 — `base.installScriptSha256` → `base.contextSha256` (now covers every\n * asset we scp'd in, not just the install script); `base.cliVersion`\n * and `base.cliCommit?` added so we can warn when an old snapshot\n * predates the running CLI.\n *\n * Read-time migration is lossy in one direction: a schema-1 file is lifted\n * to schema 2 by *renaming* `installScriptSha256` to `contextSha256`. The\n * hash doesn't change but the meaning narrows (install script only → full\n * asset list), so the next `agentbox prepare --provider hetzner` run will\n * recompute and overwrite. A legacy `projects` key (an early, never-wired\n * per-project tier) is simply ignored — removing the field doesn't break\n * reads, so no schema bump is needed.\n */\nconst SCHEMA = 2 as const;\n\nexport interface PreparedBaseSnapshot {\n /** Hetzner image id (numeric — opaque, but stable across `getImage` calls). */\n imageId: number;\n /** User-facing description (matches the firewall-dashboard rows). */\n description: string;\n /** ISO timestamp of bake-completion. */\n createdAt: string;\n /**\n * Deterministic SHA-256 of the build context (every file scp'd into the\n * prepare VPS). Rebuild when it changes.\n */\n contextSha256?: string;\n /** CLI version that produced this snapshot (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n}\n\nexport interface PreparedHetznerState {\n schema: typeof SCHEMA;\n /** The shared base snapshot. Absent until first `agentbox prepare`. */\n base?: PreparedBaseSnapshot;\n}\n\ninterface LegacyV1Base {\n imageId: number;\n description: string;\n createdAt: string;\n installScriptSha256?: string;\n}\n\ninterface LegacyV1State {\n schema: 1;\n base?: LegacyV1Base;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('hetzner');\n}\n\nexport function readPreparedState(): PreparedHetznerState {\n const raw = readPreparedStateRaw('hetzner');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedHetznerState> | LegacyV1State;\n if ((parsed as { schema?: unknown }).schema === 1) {\n const v1 = parsed as LegacyV1State;\n return migrateFromV1(v1);\n }\n if (parsed.schema !== SCHEMA) {\n // Unknown schema: don't crash, just refuse to read — the file will be\n // overwritten on the next successful prepare.\n return { schema: SCHEMA };\n }\n return {\n schema: SCHEMA,\n base: parsed.base,\n };\n}\n\nfunction migrateFromV1(v1: LegacyV1State): PreparedHetznerState {\n // The v1 `installScriptSha256` covered only `install-box.sh`, not the full\n // asset list a v2 `contextSha256` represents. Lifting it forward as a\n // placeholder fingerprint means the next prepare run will mismatch and\n // rebuild — exactly what we want, since the broader hash semantics also\n // changed.\n const base: PreparedBaseSnapshot | undefined = v1.base\n ? {\n imageId: v1.base.imageId,\n description: v1.base.description,\n createdAt: v1.base.createdAt,\n contextSha256: v1.base.installScriptSha256,\n }\n : undefined;\n return {\n schema: SCHEMA,\n base,\n };\n}\n\nexport function writePreparedState(state: PreparedHetznerState): void {\n writePreparedStateRaw('hetzner', state);\n}\n\n/**\n * Convenience helper: update one field of the state without forcing callers\n * to read/merge/write themselves.\n */\nexport function updatePreparedState(mutate: (s: PreparedHetznerState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n\n/**\n * Compute the CURRENT build-context fingerprint for the hetzner base snapshot\n * (the SHA over every file `prepare` would scp into the prepare VPS).\n * Side-effect-free — never builds. Returns `undefined` when the runtime\n * assets can't be resolved (dev tree without `pnpm -w build`) so the CLI\n * can degrade to \"can't tell, don't nag\".\n *\n * Used by `evaluateBaseFreshness` to compare against the stored value in\n * `hetzner-prepared.json.base.contextSha256`. Must produce a byte-identical\n * hash to the one `prepare` writes — both go through the same\n * `resolveRuntimeAssets` + `computeContextSha256` chain.\n */\nexport async function currentHetznerBaseFingerprintLive(): Promise<string | undefined> {\n try {\n const assets = resolveRuntimeAssets({ cliRuntimeRoot: findStagedCliRuntimeRoot() });\n return await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","resolve","dirname","opts","existsSync","readFileSync","resolve","homedir","log","raceTimeout","dirname"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-vercel/src/env-loader.ts","../../../packages/sandbox-vercel/src/sbx-cli.ts","../../../packages/sandbox-vercel/src/credentials.ts","../../../packages/sandbox-vercel/src/cli-store.ts","../../../packages/sandbox-vercel/src/sdk.ts","../../../packages/sandbox-vercel/src/vercel-rest.ts","../../../packages/sandbox-vercel/src/runtime-assets.ts","../../../packages/sandbox-vercel/src/prepared-state.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\n/**\n * Vercel env auto-loader. The `@vercel/sandbox` SDK reads `VERCEL_OIDC_TOKEN`\n * from `process.env`; for the access-token path we read `VERCEL_TOKEN` +\n * `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` and thread them into every SDK call as\n * explicit `Credentials`. We pull all of these in from `~/.agentbox/secrets.env`\n * (written by `agentbox vercel login`) so the SDK Just Works after a one-time\n * login — exactly the daytona/hetzner model.\n *\n * Lookup order (first wins; process.env is never overwritten):\n * 1. `process.env` (already set in the shell).\n * 2. `~/.agentbox/secrets.env` — written by `agentbox vercel login`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed, and a `VERCEL_*` value there\n * (e.g. a `vercel env pull` OIDC token, or the app's own deploy token) is meant\n * for in-box code, not for the host CLI to harvest and provision sandboxes with.\n * Put host credentials in `~/.agentbox/secrets.env` (or the shell env).\n *\n * Only Vercel-prefixed keys are imported; the rest of the file is left alone.\n * Idempotent and side-effect-free after the first call.\n */\nconst VERCEL_KEYS = [\n 'VERCEL_OIDC_TOKEN',\n 'VERCEL_TOKEN',\n 'VERCEL_TEAM_ID',\n 'VERCEL_PROJECT_ID',\n // Marker for CLI-login mode (`agentbox vercel login` → `sandbox login`). The\n // access token is NOT stored here — it's read live from the Vercel CLI store;\n // only this marker + team/project ids are persisted.\n 'VERCEL_AUTH_SOURCE',\n] as const;\n\nlet loaded = false;\n\nexport function ensureVercelEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importVercelFromFile(resolve(homedir(), '.agentbox', 'secrets.env'), VERCEL_KEYS);\n}\n\n/**\n * Force a re-read of `~/.agentbox/secrets.env`. Used by the interactive\n * `agentbox vercel login` flow after it persists the credential trio, so the\n * same process can pick it up without a restart.\n */\nexport function reloadVercelEnv(): void {\n loaded = false;\n ensureVercelEnvLoaded();\n}\n\nfunction importVercelFromFile(path: string, keys: readonly string[]): void {\n if (!existsSync(path)) return;\n let body: string;\n try {\n body = readFileSync(path, 'utf8');\n } catch {\n return;\n }\n const parsed = parseEnvFile(body);\n for (const key of keys) {\n if (process.env[key] !== undefined) continue;\n const value = parsed[key];\n if (typeof value === 'string') {\n process.env[key] = value;\n }\n }\n}\n\n/**\n * Minimal `.env` parser: handles `KEY=value`, `KEY=\"value\"`, `KEY='value'`,\n * `export KEY=value`, blank lines, and `#` comments. No variable\n * interpolation — predictable over feature-complete (matches the daytona\n * loader's behavior).\n */\nexport function parseEnvFile(body: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const rawLine of body.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) continue;\n const key = stripped.slice(0, eq).trim();\n let value = stripped.slice(eq + 1).trim();\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n value = value.slice(1, -1);\n }\n out[key] = value;\n }\n return out;\n}\n","/**\n * Driver for the official Vercel Sandbox CLI (`sandbox` / `sbx`, installed via\n * `npm i -g sandbox`). AgentBox's CLI-login auth mode uses it for two things:\n * - `loginSbx` — run `sandbox login` interactively so the user completes\n * the browser OAuth and the CLI writes its own credential store.\n * - `refreshSbxToken` — run a cheap read command (`sandbox list`) which makes\n * the CLI lazily refresh its access token from its stored refresh token\n * when the token is stale. Verified non-interactive: a stale token is\n * refreshed without opening a browser.\n *\n * Mirrors the probe/install patterns in sandbox-docker/src/portless.ts: execa\n * with `reject:false` for never-throw probes, spawnSync with inherited stdio for\n * the interactive login.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { execa } from 'execa';\n\n/**\n * Binaries the Sandbox CLI installs. `sbx` is the short alias; we prefer it but\n * fall back to `sandbox`. Pinned here so a future rename is a one-line fix.\n */\nconst SBX_BINS = ['sbx', 'sandbox'] as const;\n\nexport interface SbxState {\n /** A Sandbox CLI binary resolved on PATH and answered `--version`. */\n installed: boolean;\n /** The binary name that answered (`sbx` or `sandbox`), when installed. */\n bin?: string;\n /** Version string, when installed. */\n version?: string;\n}\n\nlet cached: SbxState | null = null;\n\n/**\n * Probe the host for the Sandbox CLI. Cached per-process (install state can't\n * change mid-command); `resetSbxCache` clears it after an install or for tests.\n */\nexport async function detectSbx(): Promise<SbxState> {\n if (cached !== null) return cached;\n for (const bin of SBX_BINS) {\n try {\n const r = await execa(bin, ['--version'], { reject: false });\n if (r.exitCode === 0) {\n cached = { installed: true, bin, version: (r.stdout ?? '').trim() || undefined };\n return cached;\n }\n } catch {\n // try the next bin name\n }\n }\n cached = { installed: false };\n return cached;\n}\n\n/** Drop the per-process probe cache so the next `detectSbx()` re-probes. */\nexport function resetSbxCache(): void {\n cached = null;\n}\n\n/** Command the user should run to install the Sandbox CLI. */\nexport function installSbxHint(): string {\n return 'npm install -g sandbox';\n}\n\n/** Install the Sandbox CLI globally (`npm install -g sandbox`). Never throws. */\nexport async function installSbx(): Promise<boolean> {\n try {\n const r = await execa('npm', ['install', '-g', 'sandbox'], { reject: false });\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Run `sandbox login` with inherited stdio so the user sees the CLI's own\n * browser-OAuth output and can interact with it. Blocking, like the interactive\n * agent launch path. Returns the exit status (0 = success).\n */\nexport function loginSbx(bin: string): number {\n const r = spawnSync(bin, ['login'], { stdio: 'inherit' });\n return r.status ?? 1;\n}\n\n/**\n * Trigger the CLI's lazy token refresh by running a cheap read command. The CLI\n * refreshes its access token from the stored refresh token when the token is\n * stale and leaves a still-valid token untouched, so this is safe to call\n * eagerly. Non-interactive (stdin from /dev/null); returns true on exit 0.\n */\nexport async function refreshSbxToken(bin: string): Promise<boolean> {\n try {\n const r = await execa(bin, ['list'], {\n reject: false,\n timeout: 30_000,\n stdin: 'ignore',\n });\n return r.exitCode === 0;\n } catch {\n return false;\n }\n}\n","import {\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, resolve } from 'node:path';\nimport { hostOpenCommand } from '@agentbox/sandbox-core';\nimport {\n confirm,\n isCancel,\n intro,\n log,\n note,\n outro,\n password,\n select,\n spinner,\n text,\n} from '@clack/prompts';\nimport { ensureVercelEnvLoaded, reloadVercelEnv } from './env-loader.js';\nimport { hasUsableCredentials } from './sdk.js';\nimport { cliStorePaths, isNearExpiry, readCliAuth, readCliCurrentTeam } from './cli-store.js';\nimport { detectSbx, installSbx, installSbxHint, loginSbx, resetSbxCache } from './sbx-cli.js';\nimport { createProject, getUser, listProjects, type VercelProject } from './vercel-rest.js';\n\nconst DASHBOARD_TOKENS_URL = 'https://vercel.com/account/settings/tokens';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. On reconfigure we strip prior\n * values for these before appending so the file never accumulates duplicates.\n * `VERCEL_AUTH_SOURCE` is the CLI-login marker; the access token itself is never\n * stored here in that mode (it's read live from the Vercel CLI store).\n */\nconst MANAGED_KEYS = [\n 'VERCEL_OIDC_TOKEN',\n 'VERCEL_TOKEN',\n 'VERCEL_TEAM_ID',\n 'VERCEL_PROJECT_ID',\n 'VERCEL_AUTH_SOURCE',\n] as const;\n\nexport interface EnsureVercelCredentialsOptions {\n /** Re-prompt even when valid credentials are already present (`agentbox vercel login`). */\n force?: boolean;\n}\n\n/**\n * First-run interactive setup for Vercel credentials. The access-token path\n * persists a `VERCEL_TOKEN` trio to `~/.agentbox/secrets.env` (the canonical\n * store, matching daytona/hetzner). OIDC is also supported, but the token must\n * be present in the shell env or in `~/.agentbox/secrets.env` — agentbox does\n * NOT harvest `.env.local` (that file belongs to the app being developed).\n *\n * No-op when credentials are already configured. Silent no-op when stdin isn't\n * a TTY so scripted/CI callers get the SDK's \"not configured\" error instead of\n * a hung prompt.\n */\nexport async function ensureVercelCredentials(\n opts: EnsureVercelCredentialsOptions = {},\n): Promise<void> {\n ensureVercelEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('Vercel setup');\n note(\n `AgentBox needs Vercel credentials to provision sandboxes.\\n` +\n `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 — no token to paste.\\n` +\n `Access token (best for CI / headless): personal access token + team id + project id, saved to \\`~/.agentbox/secrets.env\\`.\\n` +\n `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).`,\n 'Credentials required',\n );\n\n const mode = await select({\n message: 'How do you want to authenticate?',\n options: [\n { value: 'cli', label: 'Sign in with Vercel (browser) — recommended for interactive use' },\n { value: 'token', label: 'Access token (VERCEL_TOKEN + team + project) — best for CI / headless' },\n { value: 'oidc', label: 'OIDC token (VERCEL_OIDC_TOKEN in env / secrets.env) — short interactive work' },\n ],\n initialValue: 'cli',\n });\n if (isCancel(mode)) {\n log.warn('Vercel setup cancelled — re-run `agentbox vercel login` when ready.');\n return;\n }\n\n if (mode === 'cli') {\n await runCliLogin();\n return;\n }\n\n if (mode === 'oidc') {\n note(\n `Get an OIDC token with \\`vercel link\\` then \\`vercel env pull\\`, then make it visible to AgentBox by either:\\n` +\n ` export VERCEL_OIDC_TOKEN=<token> # in this shell\\n` +\n ` echo \"VERCEL_OIDC_TOKEN=<token>\" >> ~/.agentbox/secrets.env\\n` +\n `Re-do every ~12h; the dev token expires. AgentBox does not harvest .env.local.`,\n 'OIDC setup',\n );\n // Re-read in case the user already added the token to secrets.env.\n reloadVercelEnv();\n if (process.env.VERCEL_OIDC_TOKEN) {\n log.success('Found VERCEL_OIDC_TOKEN — Vercel is configured.');\n await ensureSbxInstalled();\n outro('Setup complete.');\n } else {\n log.warn('No VERCEL_OIDC_TOKEN found yet — set it as above, then re-run `agentbox vercel login`.');\n }\n return;\n }\n\n const creds = await promptForTokenTrio();\n if (creds === null) return;\n persistCredentials(creds);\n log.success(`Vercel credentials saved to ${secretsPath()}`);\n await ensureSbxInstalled();\n outro('Setup complete.');\n}\n\ninterface TokenTrio {\n token: string;\n teamId: string;\n projectId: string;\n}\n\nasync function promptForTokenTrio(): Promise<TokenTrio | null> {\n const openIt = await confirm({\n message: `Open ${DASHBOARD_TOKENS_URL} to create a token?`,\n initialValue: true,\n });\n if (isCancel(openIt)) return null;\n if (openIt) openDashboard();\n\n const token = await password({\n message: 'Paste your Vercel access token',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n });\n if (isCancel(token)) {\n log.warn('Vercel setup cancelled.');\n return null;\n }\n const teamId = await text({\n message: 'Team ID (team settings → General)',\n placeholder: 'team_...',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n });\n if (isCancel(teamId)) {\n log.warn('Vercel setup cancelled.');\n return null;\n }\n const projectId = await text({\n message: 'Project ID (project settings → General)',\n placeholder: 'prj_...',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n });\n if (isCancel(projectId)) {\n log.warn('Vercel setup cancelled.');\n return null;\n }\n return { token: token.trim(), teamId: teamId.trim(), projectId: projectId.trim() };\n}\n\nfunction persistCredentials(creds: TokenTrio): void {\n writeManaged({\n VERCEL_TOKEN: creds.token,\n VERCEL_TEAM_ID: creds.teamId,\n VERCEL_PROJECT_ID: creds.projectId,\n });\n}\n\n/**\n * Persist the CLI-login marker + cached stable ids. The access token is\n * deliberately omitted — it's read live from the Vercel CLI store on each call\n * and refreshed there, so the only thing we cache is the team/project scope.\n */\nfunction persistCliCredentials(ids: { teamId: string; projectId: string }): void {\n writeManaged({\n VERCEL_AUTH_SOURCE: 'cli',\n VERCEL_TEAM_ID: ids.teamId,\n VERCEL_PROJECT_ID: ids.projectId,\n });\n}\n\n/**\n * Atomically rewrite the managed Vercel keys in `~/.agentbox/secrets.env`:\n * strip every prior value for a `MANAGED_KEYS` entry, then append exactly the\n * keys in `record` (mode 0600, temp-file + rename). Also mirrors the record\n * into `process.env` (and clears the other managed keys there) so the current\n * run uses the new values immediately.\n */\nfunction writeManaged(record: Record<string, string>): void {\n for (const k of MANAGED_KEYS) delete process.env[k];\n for (const [k, v] of Object.entries(record)) process.env[k] = v;\n\n const path = secretsPath();\n mkdirSync(dirname(path), { recursive: true });\n\n let existing = '';\n if (existsSync(path)) {\n try {\n existing = readFileSync(path, 'utf8');\n } catch {\n existing = '';\n }\n }\n const kept = existing\n .split(/\\r?\\n/)\n .filter((line) => {\n const stripped = line.startsWith('export ') ? line.slice('export '.length) : line;\n const eq = stripped.indexOf('=');\n if (eq <= 0) return true;\n const key = stripped.slice(0, eq).trim();\n return !(MANAGED_KEYS as readonly string[]).includes(key);\n })\n .join('\\n')\n .replace(/\\s+$/u, '');\n\n const lines = Object.entries(record).map(([k, v]) => `${k}=${v}`);\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n try {\n chmodSync(tmp, 0o600);\n } catch {\n // chmod best-effort; writeFileSync mode already covers most filesystems.\n }\n renameSync(tmp, path);\n try {\n chmodSync(path, 0o600);\n } catch {\n // ignore — already attempted above\n }\n}\n\n/**\n * The full CLI-login flow: make sure the Vercel `sandbox` CLI is installed (offer\n * to install it), run its browser OAuth, harvest the team id from the CLI store,\n * let the user pick a project to scope sandboxes to, and persist the marker +\n * ids. The access token is never stored — `resolveCredentials` reads it live.\n */\n/**\n * Make sure the Vercel `sandbox` CLI is on PATH, offering to install it. Every\n * login mode ensures it because interactive attach (`agentbox shell|claude|\n * codex|opencode` on a vercel box) drives `sbx exec` for a real PTY. Returns the\n * resolved bin, or null if absent / declined / install failed (callers warn).\n */\nasync function ensureSbxInstalled(): Promise<{ bin: string } | null> {\n let det = await detectSbx();\n if (!det.installed) {\n const doInstall = await confirm({\n message: `The Vercel sandbox CLI (needed for interactive attach) isn't installed. Install it now? (${installSbxHint()})`,\n initialValue: true,\n });\n if (isCancel(doInstall) || !doInstall) {\n log.warn(\n `Install it with \\`${installSbxHint()}\\` to use \\`agentbox shell|claude|codex|opencode\\` on Vercel boxes.`,\n );\n return null;\n }\n const sp = spinner();\n sp.start('Installing the Vercel sandbox CLI…');\n const ok = await installSbx();\n resetSbxCache();\n det = await detectSbx();\n if (!ok || !det.installed || !det.bin) {\n sp.stop('Install failed.');\n log.warn(`Could not install the sandbox CLI — run \\`${installSbxHint()}\\` manually.`);\n return null;\n }\n sp.stop(`Installed sandbox CLI${det.version ? ` ${det.version}` : ''}.`);\n }\n return det.bin ? { bin: det.bin } : null;\n}\n\nasync function runCliLogin(): Promise<void> {\n const det = await ensureSbxInstalled();\n if (!det) {\n log.warn('The Vercel sandbox CLI is required to sign in this way — install it, then re-run `agentbox vercel login`.');\n return;\n }\n\n note('A browser window will open to sign in to Vercel.', 'Vercel sign-in');\n const status = loginSbx(det.bin);\n if (status !== 0) {\n log.warn('Vercel sign-in did not complete — re-run `agentbox vercel login` to try again.');\n return;\n }\n\n const harvested = harvestCliCredentials();\n if (!harvested) {\n log.warn('Sign-in finished but no credentials were found in the Vercel CLI store. Try again.');\n return;\n }\n\n // Validate the token early so a bad/expired session fails here, not mid-op.\n try {\n await getUser(harvested.token);\n } catch (err) {\n log.warn(\n `The Vercel session looks invalid (${err instanceof Error ? err.message : String(err)}). ` +\n 'Re-run `agentbox vercel login`.',\n );\n return;\n }\n\n const projectId = await resolveProjectId(harvested.token, harvested.teamId);\n if (projectId === null) {\n log.warn('No project selected — re-run `agentbox vercel login` to finish setup.');\n return;\n }\n\n persistCliCredentials({ teamId: harvested.teamId, projectId });\n reloadVercelEnv();\n log.success(`Signed in with Vercel — credentials managed by the sandbox CLI (saved scope to ${secretsPath()}).`);\n outro('Setup complete.');\n}\n\n/**\n * Read the live token + team id from the Vercel CLI store. teamId prefers an\n * already-cached `VERCEL_TEAM_ID` (e.g. from a prior login) and falls back to\n * the CLI's `currentTeam`. Null when the CLI isn't logged in.\n */\nfunction harvestCliCredentials(): { token: string; teamId: string } | null {\n const auth = readCliAuth();\n if (!auth) return null;\n const teamId = process.env.VERCEL_TEAM_ID ?? readCliCurrentTeam();\n if (!teamId) return null;\n return { token: auth.token, teamId };\n}\n\n/**\n * Pick the Vercel project sandboxes run under. Lists the team's projects in a\n * clack select (pre-selecting an existing `agentbox` / sandbox-default project),\n * plus a \"create a new project\" entry. Returns the project id, or null if the\n * user cancelled. Non-interactive callers reuse/create an `agentbox` project.\n */\nasync function resolveProjectId(token: string, teamId: string): Promise<string | null> {\n let projects: VercelProject[] = [];\n const sp = spinner();\n sp.start('Loading your Vercel projects…');\n try {\n projects = await listProjects(token, teamId);\n sp.stop(`Found ${projects.length} project${projects.length === 1 ? '' : 's'}.`);\n } catch (err) {\n sp.stop('Could not list projects.');\n log.warn(`Failed to list Vercel projects: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n\n const CREATE = '__create__';\n const preferred =\n projects.find((p) => p.name === 'agentbox') ??\n projects.find((p) => p.name === 'vercel-sandbox-default-project');\n const choice = await select({\n message: 'Which Vercel project should sandboxes run under?',\n options: [\n ...projects.map((p) => ({ value: p.id, label: p.name })),\n { value: CREATE, label: 'Create a new project…' },\n ],\n initialValue: preferred ? preferred.id : CREATE,\n });\n if (isCancel(choice)) return null;\n\n if (choice !== CREATE) return choice;\n\n const name = await text({\n message: 'New project name',\n placeholder: 'agentbox',\n defaultValue: 'agentbox',\n validate: (v) => (v && v.trim().length > 0 ? undefined : 'Cannot be empty'),\n });\n if (isCancel(name)) return null;\n try {\n const created = await createProject(token, teamId, name.trim() || 'agentbox');\n return created.id;\n } catch (err) {\n log.warn(`Could not create the project: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n}\n\nfunction openDashboard(): void {\n // Lazy import keeps node:child_process out of the module's load cost.\n import('node:child_process')\n .then(({ spawnSync }) => {\n const r = spawnSync(hostOpenCommand(), [DASHBOARD_TOKENS_URL], { stdio: 'ignore' });\n if (r.status !== 0) {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_TOKENS_URL} manually.`);\n }\n })\n .catch(() => {\n log.warn(`Could not auto-open the browser — visit ${DASHBOARD_TOKENS_URL} manually.`);\n });\n}\n\nexport function secretsPath(): string {\n return resolve(homedir(), '.agentbox', 'secrets.env');\n}\n\nexport interface VercelCredStatus {\n /** Which auth mode is configured. */\n auth: 'oidc' | 'cli' | 'token' | 'none';\n /** Legacy alias kept for callers that branch on OIDC. */\n oidc: boolean;\n token?: string;\n teamId?: string;\n projectId?: string;\n source: 'env' | 'secrets.env' | 'cli-store' | 'none';\n /**\n * CLI mode only: details about the live Vercel CLI session. Whether the\n * `sandbox` CLI is *installed* needs an async probe (`detectSbx`) and is not\n * reported here — the status printer probes it separately.\n */\n cli?: {\n /** A logged-in session was found in the CLI store. */\n loggedIn: boolean;\n /** Unix seconds the live access token expires at, when known. */\n expiresAt?: number;\n /** True when the token is at/near expiry (a refresh would fire). */\n nearExpiry?: boolean;\n /** Path to the CLI's `auth.json`. */\n authPath: string;\n };\n}\n\nexport function readVercelCredStatus(): VercelCredStatus {\n const shellHad = !!process.env.VERCEL_OIDC_TOKEN || !!process.env.VERCEL_TOKEN;\n ensureVercelEnvLoaded();\n const oidc = !!process.env.VERCEL_OIDC_TOKEN;\n const teamId = process.env.VERCEL_TEAM_ID;\n const projectId = process.env.VERCEL_PROJECT_ID;\n\n if (oidc) {\n return { auth: 'oidc', oidc: true, teamId, projectId, source: shellHad ? 'env' : 'secrets.env' };\n }\n\n if (process.env.VERCEL_AUTH_SOURCE === 'cli') {\n const auth = readCliAuth();\n return {\n auth: 'cli',\n oidc: false,\n token: auth?.token,\n teamId,\n projectId,\n source: 'cli-store',\n cli: {\n loggedIn: !!auth,\n expiresAt: auth?.expiresAt,\n nearExpiry: auth ? isNearExpiry(auth) : undefined,\n authPath: cliStorePaths().authPath,\n },\n };\n }\n\n const token = process.env.VERCEL_TOKEN;\n if (!token) return { auth: 'none', oidc: false, source: 'none' };\n return {\n auth: 'token',\n oidc: false,\n token,\n teamId,\n projectId,\n source: shellHad ? 'env' : 'secrets.env',\n };\n}\n\nexport function maskKey(value: string): string {\n if (value.length <= 8) return '*'.repeat(value.length);\n return `${value.slice(0, 4)}…${'*'.repeat(8)}${value.slice(-4)}`;\n}\n","/**\n * Reader for the Vercel CLI's own credential store — written by `sandbox login`\n * / `vercel login` (the `sandbox`/`sbx` CLI and `vercel` CLI share one store).\n *\n * AgentBox's \"CLI-login\" auth mode (see credentials.ts) drives that CLI for the\n * browser OAuth, then reads the resulting OAuth access token live from here on\n * every SDK call rather than copying it into `secrets.env`: the token is a\n * short-lived, opaque `vca_…` access token that the CLI refreshes lazily from\n * its stored refresh token, so the CLI store is the single self-refreshing\n * source of truth. We only cache the stable bits (team/project id + a marker)\n * in `secrets.env`.\n *\n * Pure FS + path logic — no clack, no execa — so it stays trivially testable.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/** The shape of the CLI's `auth.json` (only the fields we consume). */\nexport interface VercelCliAuth {\n /** Opaque OAuth access token (`vca_…`). NOT a JWT — expiry is `expiresAt`. */\n token: string;\n /** Unix seconds. Absent on some CLI versions → treated as near-expiry. */\n expiresAt?: number;\n /** Present but unused by us; the CLI uses it to self-refresh `token`. */\n refreshToken?: string;\n}\n\n/**\n * Resolve the `com.vercel.cli` data directory the way the CLI itself does\n * (xdg-app-paths-style), per platform:\n * - macOS: ~/Library/Application Support/com.vercel.cli\n * - Windows: %APPDATA%\\com.vercel.cli\n * - else: $XDG_DATA_HOME/com.vercel.cli (default ~/.local/share/...)\n *\n * `AGENTBOX_VERCEL_CLI_DIR` overrides outright — for tests and the rare install\n * that relocates the store.\n */\nexport function vercelCliDir(): string {\n const override = process.env.AGENTBOX_VERCEL_CLI_DIR;\n if (override && override.trim().length > 0) return override.trim();\n\n const name = 'com.vercel.cli';\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Application Support', name);\n }\n if (process.platform === 'win32') {\n const appData = process.env.APPDATA;\n if (appData && appData.trim().length > 0) return join(appData, name);\n return join(homedir(), 'AppData', 'Roaming', name);\n }\n const xdg = process.env.XDG_DATA_HOME;\n const base = xdg && xdg.trim().length > 0 ? xdg.trim() : join(homedir(), '.local', 'share');\n return join(base, name);\n}\n\n/** Absolute paths to the CLI store files, for status/diagnostics. */\nexport function cliStorePaths(): { authPath: string; configPath: string } {\n const dir = vercelCliDir();\n return { authPath: join(dir, 'auth.json'), configPath: join(dir, 'config.json') };\n}\n\nfunction readJson(path: string): unknown {\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 * Read the live access token from the CLI store. Returns null when the store is\n * missing / unparseable / has no token (CLI never logged in, or logged out) so\n * callers can surface a clear \"run `agentbox vercel login`\" error.\n */\nexport function readCliAuth(): VercelCliAuth | null {\n const raw = readJson(cliStorePaths().authPath) as Record<string, unknown> | null;\n if (!raw || typeof raw.token !== 'string' || raw.token.length === 0) return null;\n return {\n token: raw.token,\n expiresAt: typeof raw.expiresAt === 'number' ? raw.expiresAt : undefined,\n refreshToken: typeof raw.refreshToken === 'string' ? raw.refreshToken : undefined,\n };\n}\n\n/** Read the CLI's currently-selected team (`config.json` `currentTeam`). */\nexport function readCliCurrentTeam(): string | null {\n const raw = readJson(cliStorePaths().configPath) as Record<string, unknown> | null;\n return raw && typeof raw.currentTeam === 'string' && raw.currentTeam.length > 0\n ? raw.currentTeam\n : null;\n}\n\n/**\n * Whether the access token is at/near expiry and should be refreshed before\n * use. A missing `expiresAt` is treated as near-expiry so we always probe a\n * refresh rather than ship a token of unknown age. `skewSec` is the safety\n * window: refresh if the token expires within that many seconds.\n */\nexport function isNearExpiry(auth: VercelCliAuth, skewSec = 120): boolean {\n if (auth.expiresAt === undefined) return true;\n return auth.expiresAt * 1000 < Date.now() + skewSec * 1000;\n}\n","/**\n * Thin loader around `@vercel/sandbox`. Resolves the auth credentials once and\n * threads them into every SDK call.\n *\n * Three auth modes (in precedence order):\n * - OIDC: `VERCEL_OIDC_TOKEN` in env → decode the JWT for owner/project and\n * pass `{ token, teamId, projectId }` explicitly.\n * - CLI-login: `VERCEL_AUTH_SOURCE=cli` → read the live OAuth access token\n * from the Vercel CLI's own store (`auth.json`) and the cached team/project\n * ids from `secrets.env`. The token is never copied to `secrets.env`; the\n * CLI store is the self-refreshing source of truth. `ensureFreshCredentials`\n * refreshes it (via the `sbx` CLI) before use when it's near expiry.\n * - Access token: `VERCEL_TOKEN` + `VERCEL_TEAM_ID` + `VERCEL_PROJECT_ID` →\n * passed explicitly as `{ token, teamId, projectId }` on each call, since\n * the SDK does NOT read those from env automatically.\n */\n\nimport { ensureVercelEnvLoaded } from './env-loader.js';\nimport { isNearExpiry, readCliAuth, readCliCurrentTeam } from './cli-store.js';\nimport { detectSbx, refreshSbxToken } from './sbx-cli.js';\n\nexport interface VercelCredentials {\n token: string;\n teamId: string;\n projectId: string;\n}\n\n/**\n * Resolve the credentials to thread into SDK calls. Throws when nothing is\n * configured (or an OIDC token has expired) so callers get a clear, actionable\n * error instead of an opaque SDK auth failure.\n *\n * For OIDC we do NOT return `{}` and let the SDK read the env var: the SDK's\n * env-OIDC path (`@vercel/oidc`) tries to *refresh* the token via the Vercel\n * CLI's `.vercel/project.json` + cached auth, which an agentbox box doesn't\n * have, so it fails with \"Could not get credentials from OIDC context\". Instead\n * we decode the OIDC JWT — which embeds `owner_id` (teamId) and `project_id` —\n * and pass `{ token, teamId, projectId }` explicitly, which uses the SDK's\n * direct-credentials path (the OIDC token is itself a valid API bearer).\n */\nexport function resolveCredentials(): VercelCredentials {\n ensureVercelEnvLoaded();\n const oidc = process.env.VERCEL_OIDC_TOKEN;\n if (oidc) {\n const claims = decodeOidcClaims(oidc);\n if (!claims) {\n throw new Error(\n 'VERCEL_OIDC_TOKEN is set but could not be decoded (not a valid Vercel OIDC JWT). ' +\n 'Re-run `vercel env pull`, or use the VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID trio.',\n );\n }\n if (claims.exp !== undefined && claims.exp * 1000 < Date.now()) {\n throw new Error(\n 'VERCEL_OIDC_TOKEN has expired (Vercel dev OIDC tokens last ~12h). ' +\n 'Re-run `vercel env pull` to refresh it, then retry.',\n );\n }\n return { token: oidc, teamId: claims.teamId, projectId: claims.projectId };\n }\n if (process.env.VERCEL_AUTH_SOURCE === 'cli') {\n const auth = readCliAuth();\n if (!auth) {\n throw new Error(\n 'Vercel CLI session not found — run `agentbox vercel login` (or `sbx login`) to sign in again.',\n );\n }\n const teamId = process.env.VERCEL_TEAM_ID ?? readCliCurrentTeam() ?? undefined;\n const projectId = process.env.VERCEL_PROJECT_ID;\n if (!teamId || !projectId) {\n throw new Error(\n 'Vercel CLI auth is missing the team/project id — re-run `agentbox vercel login`.',\n );\n }\n // Live token straight from the CLI store; nothing cached on disk.\n return { token: auth.token, teamId, projectId };\n }\n const token = process.env.VERCEL_TOKEN;\n const teamId = process.env.VERCEL_TEAM_ID;\n const projectId = process.env.VERCEL_PROJECT_ID;\n if (token && teamId && projectId) return { token, teamId, projectId };\n throw new Error(\n 'Vercel credentials not configured.\\n' +\n 'Either run `vercel link && vercel env pull` to get a VERCEL_OIDC_TOKEN, ' +\n 'or set VERCEL_TOKEN + VERCEL_TEAM_ID + VERCEL_PROJECT_ID ' +\n '(see `agentbox vercel login`).',\n );\n}\n\ninterface OidcClaims {\n teamId: string;\n projectId: string;\n exp?: number;\n}\n\n/** Decode the `owner_id`/`project_id`/`exp` claims from a Vercel OIDC JWT. */\nfunction decodeOidcClaims(token: string): OidcClaims | null {\n const parts = token.split('.');\n if (parts.length < 2 || !parts[1]) return null;\n try {\n const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8')) as {\n owner_id?: unknown;\n project_id?: unknown;\n exp?: unknown;\n };\n if (typeof payload.owner_id !== 'string' || typeof payload.project_id !== 'string') return null;\n return {\n teamId: payload.owner_id,\n projectId: payload.project_id,\n exp: typeof payload.exp === 'number' ? payload.exp : undefined,\n };\n } catch {\n return null;\n }\n}\n\n/** True when any auth mode is configured. Used by the credential gate. */\nexport function hasUsableCredentials(): boolean {\n ensureVercelEnvLoaded();\n if (process.env.VERCEL_OIDC_TOKEN) return true;\n if (\n process.env.VERCEL_AUTH_SOURCE === 'cli' &&\n process.env.VERCEL_TEAM_ID &&\n process.env.VERCEL_PROJECT_ID &&\n readCliAuth()\n ) {\n return true;\n }\n return Boolean(\n process.env.VERCEL_TOKEN && process.env.VERCEL_TEAM_ID && process.env.VERCEL_PROJECT_ID,\n );\n}\n\n/**\n * Refresh the CLI-login access token before use when it's near expiry. No-op\n * for the OIDC and access-token modes (nothing to refresh). For CLI mode: if the\n * live token in the CLI store is within the safety window, run a cheap `sbx`\n * read command, which makes the CLI rotate its own token from the stored refresh\n * token, then re-read `secrets.env`. Throws an actionable error when the CLI is\n * gone or the refresh fails (e.g. the refresh token itself expired).\n *\n * Call this once at the top of each backend operation, BEFORE the (sync)\n * `resolveCredentials()` reads the token. An in-process single-flight collapses\n * concurrent ops onto one refresh; cross-process races are harmless (the CLI\n * writes its store atomically and a fresh-token refresh is a no-op).\n */\nlet inflightRefresh: Promise<void> | null = null;\n\nexport function ensureFreshCredentials(): Promise<void> {\n ensureVercelEnvLoaded();\n if (process.env.VERCEL_AUTH_SOURCE !== 'cli') return Promise.resolve();\n if (inflightRefresh) return inflightRefresh;\n inflightRefresh = refreshCliToken().finally(() => {\n inflightRefresh = null;\n });\n return inflightRefresh;\n}\n\nasync function refreshCliToken(): Promise<void> {\n const auth = readCliAuth();\n if (!auth) return; // resolveCredentials() will throw the clear \"logged out\" error\n if (!isNearExpiry(auth)) return; // still valid — no work\n\n const det = await detectSbx();\n if (!det.installed || !det.bin) {\n throw new Error(\n 'Vercel access token is near expiry and the `sandbox` CLI is no longer installed — ' +\n 'reinstall it (`npm install -g sandbox`) or run `agentbox vercel login`.',\n );\n }\n const ok = await refreshSbxToken(det.bin);\n if (!ok) {\n throw new Error(\n 'Vercel token refresh failed — run `agentbox vercel login` (the refresh token may have expired).',\n );\n }\n // The token lives in the CLI store, not secrets.env — refreshSbxToken rotated\n // auth.json in place, so the next readCliAuth() returns the fresh token.\n const fresh = readCliAuth();\n if (!fresh || isNearExpiry(fresh, 0)) {\n throw new Error(\n 'Vercel token is still stale after a refresh attempt — run `agentbox vercel login`.',\n );\n }\n}\n\n// Re-export the SDK surface we use so the rest of the package imports from one\n// place (and tests can mock `./sdk.js` instead of the package).\nexport { Sandbox, Snapshot } from '@vercel/sandbox';\nexport type { Sandbox as SandboxType } from '@vercel/sandbox';\n","/**\n * Minimal Vercel REST helpers used by the CLI-login auth flow. The Sandbox SDK\n * requires a projectId on every call, but the OAuth access token harvested from\n * the CLI is team-scoped with no project — so after login we list the team's\n * projects (and optionally create one) to resolve a project to scope sandboxes\n * to. Plain `fetch`; no SDK. Each call takes the harvested `(token, teamId)`.\n */\n\nconst API = 'https://api.vercel.com';\n\nexport interface VercelProject {\n id: string;\n name: string;\n}\n\nclass VercelApiError extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = 'VercelApiError';\n }\n}\n\nasync function api(\n token: string,\n path: string,\n init?: { method?: string; body?: unknown },\n): Promise<unknown> {\n const res = await fetch(`${API}${path}`, {\n method: init?.method ?? 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n ...(init?.body ? { 'Content-Type': 'application/json' } : {}),\n },\n body: init?.body ? JSON.stringify(init.body) : undefined,\n });\n const text = await res.text();\n let json: unknown = null;\n try {\n json = text ? JSON.parse(text) : null;\n } catch {\n json = null;\n }\n if (!res.ok) {\n const detail =\n (json as { error?: { message?: string } } | null)?.error?.message ??\n text ??\n res.statusText;\n throw new VercelApiError(res.status, `Vercel API ${res.status}: ${detail}`);\n }\n return json;\n}\n\n/** Validate the token and return the authenticated user (probe / status). */\nexport async function getUser(token: string): Promise<{ id: string; username?: string }> {\n const json = (await api(token, '/v2/user')) as {\n user?: { id: string; username?: string };\n };\n const user = json.user;\n if (!user?.id) throw new Error('Vercel /v2/user returned no user');\n return user;\n}\n\n/** List the team's projects (id + name). Paginates up to `limit` (default 100). */\nexport async function listProjects(\n token: string,\n teamId: string,\n limit = 100,\n): Promise<VercelProject[]> {\n const json = (await api(\n token,\n `/v9/projects?teamId=${encodeURIComponent(teamId)}&limit=${limit}`,\n )) as { projects?: Array<{ id: string; name: string }> };\n return (json.projects ?? []).map((p) => ({ id: p.id, name: p.name }));\n}\n\n/**\n * Create a project under the team. On a 409 (name already taken) re-list and\n * return the existing project of that name, so the caller treats create as\n * idempotent.\n */\nexport async function createProject(\n token: string,\n teamId: string,\n name: string,\n): Promise<VercelProject> {\n try {\n const json = (await api(token, `/v9/projects?teamId=${encodeURIComponent(teamId)}`, {\n method: 'POST',\n body: { name },\n })) as { id: string; name: string };\n return { id: json.id, name: json.name };\n } catch (err) {\n if (err instanceof VercelApiError && err.status === 409) {\n const existing = (await listProjects(token, teamId)).find((p) => p.name === name);\n if (existing) return existing;\n }\n throw err;\n }\n}\n","/**\n * Resolver for the on-disk files shipped into a fresh Vercel sandbox during\n * `prepareVercel()`. Same idea as the hetzner resolver: a flat list of files to\n * upload via `sandbox.writeFiles`, each resolved from either the staged CLI\n * runtime tree or the monorepo source tree.\n *\n * Lookup order per file:\n * 1. The CLI's staged runtime tree: `<cliRoot>/runtime/vercel/...`.\n * 2. The monorepo source tree (dev fallback) under `packages/`.\n *\n * Any missing file throws a clear error naming the paths tried. Note: no\n * dockerd helper — Vercel can't run nested containers.\n */\n\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst SELF = dirname(fileURLToPath(import.meta.url));\n\nexport function findStagedCliRuntimeRoot(): string | undefined {\n const candidates = [\n resolve(SELF, '..', 'runtime'),\n resolve(SELF, '..', '..', 'runtime'),\n ];\n for (const c of candidates) {\n if (existsSync(resolve(c, 'vercel', 'scripts', 'provision.sh'))) return c;\n }\n return undefined;\n}\n\nexport interface RuntimeAsset {\n /** Logical name (used in error messages + log lines). */\n name: string;\n /** Absolute path on the box (writeFiles target). */\n remotePath: string;\n /** File mode to apply after upload. */\n remoteMode: number;\n}\n\n/**\n * Where each asset lands inside the sandbox. provision.sh reads them from these\n * fixed paths. The agent/runtime helpers go straight to /usr/local/bin; baked\n * config files to /tmp for provision.sh to `install` into place.\n */\nexport const RUNTIME_ASSETS: readonly RuntimeAsset[] = [\n { name: 'provision.sh', remotePath: '/tmp/agentbox-provision.sh', remoteMode: 0o755 },\n { name: 'agentbox-ctl', remotePath: '/tmp/agentbox-ctl', remoteMode: 0o755 },\n { name: 'agentbox-vnc-start', remotePath: '/tmp/agentbox-vnc-start', remoteMode: 0o755 },\n { name: 'agentbox-checkpoint-cleanup', remotePath: '/tmp/agentbox-checkpoint-cleanup', remoteMode: 0o755 },\n { name: 'agentbox-open', remotePath: '/tmp/agentbox-open', remoteMode: 0o755 },\n { name: 'gh-shim', remotePath: '/tmp/agentbox-gh-shim', remoteMode: 0o755 },\n { name: 'git-shim', remotePath: '/tmp/agentbox-git-shim', remoteMode: 0o755 },\n { name: 'ntn-shim', remotePath: '/tmp/agentbox-ntn-shim', remoteMode: 0o755 },\n { name: 'linear-shim', remotePath: '/tmp/agentbox-linear-shim', remoteMode: 0o755 },\n { name: 'custom-system-CLAUDE.md', remotePath: '/tmp/agentbox-custom-CLAUDE.md', remoteMode: 0o644 },\n { name: 'claude-managed-settings.json', remotePath: '/tmp/agentbox-managed-settings.json', remoteMode: 0o644 },\n { name: 'agentbox-codex-hooks.json', remotePath: '/tmp/agentbox-codex-hooks.json', remoteMode: 0o644 },\n { name: 'agentbox-setup-skill.md', remotePath: '/tmp/agentbox-setup-skill.md', remoteMode: 0o644 },\n] as const;\n\nexport interface ResolvedAsset extends RuntimeAsset {\n localPath: string;\n}\n\nexport function candidatesFor(\n name: string,\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): string[] {\n const cliRoot = opts.cliRuntimeRoot;\n const monorepo = opts.repoRoot ?? guessRepoRoot();\n\n const monorepoRelative: Record<string, string[]> = {\n 'provision.sh': ['packages/sandbox-vercel/scripts/provision.sh'],\n 'agentbox-ctl': ['packages/ctl/dist/bin.cjs'],\n 'agentbox-vnc-start': ['packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['packages/sandbox-vercel/scripts/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const cliRelative: Record<string, string[]> = {\n 'provision.sh': ['vercel/scripts/provision.sh'],\n 'agentbox-ctl': ['vercel/ctl.cjs'],\n 'agentbox-vnc-start': ['vercel/agentbox-vnc-start', 'docker/packages/sandbox-docker/scripts/agentbox-vnc-start'],\n 'agentbox-checkpoint-cleanup': ['vercel/agentbox-checkpoint-cleanup', 'docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup'],\n 'agentbox-open': ['vercel/agentbox-open', 'docker/packages/sandbox-docker/scripts/agentbox-open'],\n 'gh-shim': ['vercel/gh-shim', 'docker/packages/sandbox-docker/scripts/gh-shim'],\n 'git-shim': ['vercel/git-shim', 'docker/packages/sandbox-docker/scripts/git-shim'],\n 'ntn-shim': ['vercel/ntn-shim', 'docker/packages/sandbox-docker/scripts/ntn-shim'],\n 'linear-shim': ['vercel/linear-shim', 'docker/packages/sandbox-docker/scripts/linear-shim'],\n 'custom-system-CLAUDE.md': ['vercel/custom-system-CLAUDE.md'],\n 'claude-managed-settings.json': ['vercel/claude-managed-settings.json', 'docker/packages/sandbox-docker/scripts/claude-managed-settings.json'],\n 'agentbox-codex-hooks.json': ['vercel/agentbox-codex-hooks.json', 'docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json'],\n 'agentbox-setup-skill.md': ['vercel/agentbox-setup-skill.md', 'docker/apps/cli/share/agentbox-setup/SKILL.md'],\n };\n\n const out: string[] = [];\n if (cliRoot) {\n for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));\n }\n for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));\n return out;\n}\n\nexport function resolveRuntimeAssets(\n opts: { cliRuntimeRoot?: string; repoRoot?: string } = {},\n): ResolvedAsset[] {\n const out: ResolvedAsset[] = [];\n const missing: Array<{ name: string; tried: string[] }> = [];\n for (const asset of RUNTIME_ASSETS) {\n const cands = candidatesFor(asset.name, opts);\n const hit = cands.find((p) => existsSync(p));\n if (!hit) {\n missing.push({ name: asset.name, tried: cands });\n continue;\n }\n out.push({ ...asset, localPath: hit });\n }\n if (missing.length > 0) {\n const lines = missing.flatMap((m) => [` - ${m.name}: tried`, ...m.tried.map((p) => ` ${p}`)]);\n throw new Error(\n `vercel: could not resolve runtime assets needed to bake the base snapshot:\\n` +\n lines.join('\\n') +\n `\\n\\nIf running from the monorepo, ensure \\`pnpm -w build\\` has run so packages/ctl/dist/bin.cjs exists.`,\n );\n }\n return out;\n}\n\nfunction guessRepoRoot(): string {\n let cur = SELF;\n for (let i = 0; i < 8; i++) {\n if (existsSync(resolve(cur, 'pnpm-workspace.yaml'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return SELF;\n}\n","/**\n * Persisted record of what `agentbox prepare --provider vercel` has built.\n * Lives at `~/.agentbox/vercel-prepared.json` so the auto-prepare gate\n * (`ensureVercelBaseSnapshot()`) and `backend.provision` can resolve the base\n * snapshot to boot every box from.\n *\n * Single tier for now — the shared base snapshot (AL2023 + deps + agentbox-ctl\n * + agents). A per-project snapshot tier (matching the hetzner/daytona shape)\n * is a future optimization tracked in docs/vercel-backlog.md.\n *\n * Schema versioned so future shape changes can migrate; only `schema: 1` is\n * accepted today.\n */\n\nimport { computeContextSha256, readPreparedStateRaw, writePreparedStateRaw, preparedStatePathFor } from '@agentbox/sandbox-core';\nimport { UserFacingError } from '@agentbox/core';\nimport { findStagedCliRuntimeRoot, resolveRuntimeAssets } from './runtime-assets.js';\n\nconst SCHEMA = 1 as const;\n\nexport interface PreparedVercelBase {\n /** Vercel snapshot id (opaque). The thing `Sandbox.create({ source }) ` boots from. */\n snapshotId: string;\n /** Deterministic SHA-256 of the prepare build context (provision.sh + assets). */\n contextSha256?: string;\n /** CLI version that produced this snapshot (informational). */\n cliVersion?: string;\n /** Git short SHA of the CLI build (informational). */\n cliCommit?: string;\n /** ISO timestamp of bake completion. */\n createdAt: string;\n}\n\nexport interface PreparedVercelState {\n schema: typeof SCHEMA;\n /** The shared base snapshot. Absent until first `agentbox prepare`. */\n base?: PreparedVercelBase;\n}\n\nexport function preparedStatePath(): string {\n return preparedStatePathFor('vercel');\n}\n\nexport function readPreparedState(): PreparedVercelState {\n const raw = readPreparedStateRaw('vercel');\n if (raw === null || typeof raw !== 'object') return { schema: SCHEMA };\n const parsed = raw as Partial<PreparedVercelState>;\n if (parsed.schema !== SCHEMA) {\n // Unknown/missing schema: refuse to read — the next prepare overwrites it.\n return { schema: SCHEMA };\n }\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedState(state: PreparedVercelState): void {\n writePreparedStateRaw('vercel', state);\n}\n\n/** Update one field of the state without forcing callers to read/merge/write. */\nexport function updatePreparedState(mutate: (s: PreparedVercelState) => void): void {\n const s = readPreparedState();\n mutate(s);\n writePreparedState(s);\n}\n\n/**\n * Compute the CURRENT build-context fingerprint for the vercel base snapshot\n * (the SHA over every file `prepare` would `writeFiles` into the builder\n * sandbox). Side-effect-free — never builds. Returns `undefined` when the\n * runtime assets can't be resolved (dev tree without `pnpm -w build`) so\n * the CLI can degrade to \"can't tell, don't nag\".\n *\n * Used by `evaluateBaseFreshness` to compare against the stored value in\n * `vercel-prepared.json.base.contextSha256`. Must produce a byte-identical\n * hash to the one `prepare` writes — both go through the same\n * `resolveRuntimeAssets` + `computeContextSha256` chain.\n */\nexport async function currentVercelBaseFingerprintLive(): Promise<string | undefined> {\n try {\n const assets = resolveRuntimeAssets({ cliRuntimeRoot: findStagedCliRuntimeRoot() });\n return await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n } catch {\n return undefined;\n }\n}\n\n/**\n * First-use gate. If no base snapshot is recorded, throw an actionable error\n * pointing at `agentbox prepare --provider vercel`. Called by `backend.provision`\n * (indirectly via the snapshot resolution) and usable by the CLI.\n */\nexport function ensureVercelBaseSnapshot(): void {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new UserFacingError(\n 'no Vercel base snapshot found.\\n' +\n 'Run `agentbox prepare --provider vercel` first — Vercel cannot build images ' +\n 'from a Dockerfile, so the base snapshot is a one-time prerequisite for cloud boxes.',\n );\n}\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","resolve","existsSync","readFileSync","homedir","dirname","resolve","homedir","existsSync","readFileSync","teamId","projectId","text","spawnSync","resolve","dirname"]}