@madarco/agentbox 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +11 -8
  3. package/dist/{_cloud-attach-HJC672UR.js → _cloud-attach-R6TRWG5L.js} +4 -4
  4. package/dist/{chunk-QYRK5H6Q.js → chunk-43Q5GWP6.js} +108 -56
  5. package/dist/chunk-43Q5GWP6.js.map +1 -0
  6. package/dist/{chunk-ECLLV5JH.js → chunk-72CJTXN6.js} +156 -5
  7. package/dist/chunk-72CJTXN6.js.map +1 -0
  8. package/dist/{chunk-R5XIDQFR.js → chunk-BKU34KYY.js} +170 -6
  9. package/dist/chunk-BKU34KYY.js.map +1 -0
  10. package/dist/{chunk-4NQXNQ53.js → chunk-E7CHS7ZR.js} +168 -58
  11. package/dist/chunk-E7CHS7ZR.js.map +1 -0
  12. package/dist/chunk-MCOU6CZS.js +346 -0
  13. package/dist/chunk-MCOU6CZS.js.map +1 -0
  14. package/dist/{chunk-B4QG2MCW.js → chunk-MLMFNN4T.js} +762 -483
  15. package/dist/chunk-MLMFNN4T.js.map +1 -0
  16. package/dist/{chunk-2LF5YILI.js → chunk-RSKG7AFU.js} +80 -6
  17. package/dist/chunk-RSKG7AFU.js.map +1 -0
  18. package/dist/{chunk-SNTHHWKY.js → chunk-XKH7NTT7.js} +80 -22
  19. package/dist/chunk-XKH7NTT7.js.map +1 -0
  20. package/dist/{dist-7KVUIKJX.js → dist-AGTIA7AD.js} +37 -226
  21. package/dist/dist-AGTIA7AD.js.map +1 -0
  22. package/dist/{dist-OPIBZ7XM.js → dist-FIFEFKJ7.js} +14 -69
  23. package/dist/dist-FIFEFKJ7.js.map +1 -0
  24. package/dist/dist-JZ3XO6EB.js +662 -0
  25. package/dist/dist-JZ3XO6EB.js.map +1 -0
  26. package/dist/{dist-OG6NW6SM.js → dist-OGJGZETZ.js} +5 -3
  27. package/dist/{dist-JAN5VABY.js → dist-S4XR4ACV.js} +25 -177
  28. package/dist/dist-S4XR4ACV.js.map +1 -0
  29. package/dist/index.js +2229 -1314
  30. package/dist/index.js.map +1 -1
  31. package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js → prepared-state-MQHD3M5F-Q27AZU53.js} +2 -2
  32. package/package.json +6 -4
  33. package/runtime/docker/Dockerfile.box +21 -26
  34. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +67 -1
  35. package/runtime/docker/packages/ctl/dist/bin.cjs +361 -43
  36. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +17 -6
  37. package/runtime/docker/packages/sandbox-docker/scripts/chromium-resolver +57 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +2 -1
  39. package/runtime/e2b/agentbox-checkpoint-cleanup +52 -0
  40. package/runtime/e2b/agentbox-codex-hooks.json +68 -0
  41. package/runtime/e2b/agentbox-open +28 -0
  42. package/runtime/e2b/agentbox-setup-skill.md +263 -0
  43. package/runtime/e2b/agentbox-vnc-start +102 -0
  44. package/runtime/e2b/attach-helper.cjs +167 -0
  45. package/runtime/e2b/claude-managed-settings.json +116 -0
  46. package/runtime/e2b/ctl.cjs +24158 -0
  47. package/runtime/e2b/custom-system-CLAUDE.md +46 -0
  48. package/runtime/e2b/gh-shim +344 -0
  49. package/runtime/e2b/git-shim +131 -0
  50. package/runtime/e2b/scripts/build-template.sh +295 -0
  51. package/runtime/hetzner/agentbox-setup-skill.md +67 -1
  52. package/runtime/hetzner/agentbox-vnc-start +17 -6
  53. package/runtime/hetzner/claude-managed-settings.json +2 -1
  54. package/runtime/hetzner/ctl.cjs +361 -43
  55. package/runtime/relay/bin.cjs +380 -233
  56. package/runtime/vercel/agentbox-setup-skill.md +67 -1
  57. package/runtime/vercel/agentbox-vnc-start +17 -6
  58. package/runtime/vercel/claude-managed-settings.json +2 -1
  59. package/runtime/vercel/ctl.cjs +361 -43
  60. package/share/agentbox-setup/SKILL.md +67 -1
  61. package/share/host-skills/agentbox-info/SKILL.md +47 -35
  62. package/dist/chunk-2LF5YILI.js.map +0 -1
  63. package/dist/chunk-4NQXNQ53.js.map +0 -1
  64. package/dist/chunk-B4QG2MCW.js.map +0 -1
  65. package/dist/chunk-ECLLV5JH.js.map +0 -1
  66. package/dist/chunk-QYRK5H6Q.js.map +0 -1
  67. package/dist/chunk-R5XIDQFR.js.map +0 -1
  68. package/dist/chunk-SNTHHWKY.js.map +0 -1
  69. package/dist/dist-7KVUIKJX.js.map +0 -1
  70. package/dist/dist-JAN5VABY.js.map +0 -1
  71. package/dist/dist-OPIBZ7XM.js.map +0 -1
  72. /package/dist/{_cloud-attach-HJC672UR.js.map → _cloud-attach-R6TRWG5L.js.map} +0 -0
  73. /package/dist/{dist-OG6NW6SM.js.map → dist-OGJGZETZ.js.map} +0 -0
  74. /package/dist/{prepared-state-MQHD3M5F-KE4DT3GX.js.map → prepared-state-MQHD3M5F-Q27AZU53.js.map} +0 -0
@@ -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"],"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"],"mappings":"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","names":["existsSync","readFileSync","homedir","resolve","opts","log","raceTimeout"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-core/src/state.ts","../../../packages/sandbox-core/src/git-detect.ts","../../../packages/sandbox-core/src/host-open.ts","../../../packages/sandbox-core/src/prepared-state.ts","../../../packages/sandbox-docker/src/prepared-state.ts","../../../packages/sandbox-docker/src/image.ts"],"sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport type { BoxRecord, DockerBoxFields, FindBoxResult, StateFile } from '@agentbox/core';\n\nexport const STATE_DIR = join(homedir(), '.agentbox');\nexport const STATE_FILE = join(STATE_DIR, 'state.json');\n\nconst EMPTY: StateFile = { version: 1, boxes: [] };\n\nexport async function readState(path: string = STATE_FILE): Promise<StateFile> {\n try {\n const raw = await readFile(path, 'utf8');\n const parsed = JSON.parse(raw) as StateFile;\n if (parsed.version !== 1 || !Array.isArray(parsed.boxes)) {\n throw new Error(`unrecognized state file shape at ${path}`);\n }\n // Migrate-on-read: records written before the multi-provider split carry no\n // `provider` field — they are all Docker boxes. Default it so every\n // consumer (provider registry, `findBox`) sees a discriminated record.\n // Also backfill `box.docker` from the flat fields for Docker records so\n // forward-looking readers (7.1) see the nested shape without waiting\n // for the box to be re-recorded.\n for (const b of parsed.boxes) {\n b.provider ??= 'docker';\n if ((b.provider ?? 'docker') === 'docker' && !b.docker) {\n b.docker = projectDockerFields(b);\n }\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n return { ...EMPTY };\n }\n throw err;\n }\n}\n\nexport async function writeState(state: StateFile, path: string = STATE_FILE): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, JSON.stringify(state, null, 2) + '\\n', 'utf8');\n}\n\nexport async function recordBox(box: BoxRecord, path: string = STATE_FILE): Promise<void> {\n // Forward-looking shape: every Docker write also mirrors the flat\n // docker-specific fields into `box.docker` so readers can move to the\n // nested form opportunistically (7.1). Cloud records skip the mirror —\n // the discriminator is `box.provider !== 'docker'`.\n const toWrite: BoxRecord =\n (box.provider ?? 'docker') === 'docker' && !box.docker\n ? { ...box, docker: projectDockerFields(box) }\n : box;\n const state = await readState(path);\n const next: StateFile = {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n };\n await writeState(next, path);\n}\n\n/**\n * Build a `DockerBoxFields` payload from the flat Docker-specific fields\n * still living on `BoxRecord` for back-compat. Pure function, no\n * filesystem; safe for both `readState` migration and `recordBox` mirror.\n *\n * Once every reader uses `box.docker?.<field>` (the rest of 7.1), the\n * flat fields can be dropped and this projection becomes the canonical\n * shape. Until then, every write produces both shapes from the same\n * source so they can't drift.\n */\nfunction projectDockerFields(box: BoxRecord): DockerBoxFields {\n return {\n container: box.container,\n image: box.image,\n snapshotDir: box.snapshotDir ?? null,\n socketPath: box.socketPath,\n claudeConfigVolume: box.claudeConfigVolume,\n codexConfigVolume: box.codexConfigVolume,\n opencodeConfigVolume: box.opencodeConfigVolume,\n vscodeServerVolume: box.vscodeServerVolume,\n cursorServerVolume: box.cursorServerVolume,\n vncHostPort: box.vncHostPort,\n webHostPort: box.webHostPort,\n portlessAlias: box.portlessAlias,\n portlessUrl: box.portlessUrl,\n portlessVncAlias: box.portlessVncAlias,\n portlessVncUrl: box.portlessVncUrl,\n dockerVolume: box.dockerVolume,\n dockerCacheShared: box.dockerCacheShared,\n checkpointImage: box.checkpointImage,\n };\n}\n\nexport async function removeBoxRecord(id: string, path: string = STATE_FILE): Promise<boolean> {\n const state = await readState(path);\n const before = state.boxes.length;\n const next: StateFile = {\n version: 1,\n boxes: state.boxes.filter((b) => b.id !== id),\n };\n if (next.boxes.length === before) return false;\n await writeState(next, path);\n return true;\n}\n\n/**\n * Resolve a user-supplied identifier against the state file. Matching\n * precedence mirrors `docker`'s container reference resolution:\n *\n * 1. exact id\n * 2. unique id prefix\n * 3. exact name\n * 4. exact container name\n *\n * Returns `'ambiguous'` if step 2 finds more than one match (steps 1, 3, 4\n * are exact-match so they cannot be ambiguous on their own).\n */\nexport function findBox(idOrName: string, state: StateFile): FindBoxResult {\n const q = idOrName.trim();\n if (q.length === 0) return { kind: 'none' };\n\n const exactId = state.boxes.find((b) => b.id === q);\n if (exactId) return { kind: 'ok', box: exactId };\n\n const prefixMatches = state.boxes.filter((b) => b.id.startsWith(q));\n if (prefixMatches.length === 1) return { kind: 'ok', box: prefixMatches[0]! };\n if (prefixMatches.length > 1) return { kind: 'ambiguous', matches: prefixMatches };\n\n const byName = state.boxes.find((b) => b.name === q);\n if (byName) return { kind: 'ok', box: byName };\n\n // For docker records `container` is the docker container name; for cloud\n // records it's `cloud:<sandboxId>` (post 7.2 — no more synthetic\n // agentbox-cloud-* prefix). Either form is a valid byContainer lookup\n // key for `findBox`.\n const byContainer = state.boxes.find((b) => b.container === q);\n if (byContainer) return { kind: 'ok', box: byContainer };\n\n return { kind: 'none' };\n}\n\n/**\n * Next monotonic 1-based index for the given project. Reads only `state.boxes`\n * — caller is responsible for persisting the assignment. Boxes without\n * `projectRoot` are ignored (legacy records); boxes in *other* projects are\n * also ignored. Indices are never recycled, so a destroyed #2 leaves a gap.\n */\nexport function allocateProjectIndex(state: StateFile, projectRoot: string): number {\n let max = 0;\n for (const b of state.boxes) {\n if (b.projectRoot !== projectRoot) continue;\n if (typeof b.projectIndex === 'number' && b.projectIndex > max) {\n max = b.projectIndex;\n }\n }\n return max + 1;\n}\n\n/**\n * Auto-pick when a command's `[box]` argument is omitted. Returns the unique\n * box for `projectRoot`, an `ambiguous` carrying all candidates so the CLI can\n * print a chooser, or `none`.\n */\nexport function autoPickProjectBox(state: StateFile, projectRoot: string): FindBoxResult {\n const matches = state.boxes.filter((b) => b.projectRoot === projectRoot);\n if (matches.length === 0) return { kind: 'none' };\n if (matches.length === 1) return { kind: 'ok', box: matches[0]! };\n return { kind: 'ambiguous', matches };\n}\n\n/**\n * Top-level resolver every CLI command goes through. Combines numeric-index\n * lookup with the legacy `findBox` matcher:\n *\n * - `ref === undefined` and `projectRoot` known → autoPickProjectBox.\n * - `ref` is a pure positive integer and `projectRoot` known → resolve as\n * project index. **Never** falls through to `findBox` on miss, so\n * `agentbox open 3` is reserved for the index and won't accidentally\n * match a hex id like `3abc…`.\n * - Otherwise → `findBox` (id → prefix → name → container).\n */\nexport function resolveBoxRef(\n ref: string | undefined,\n state: StateFile,\n projectRoot: string | undefined,\n): FindBoxResult {\n if (ref === undefined) {\n if (projectRoot === undefined) return { kind: 'none' };\n return autoPickProjectBox(state, projectRoot);\n }\n const trimmed = ref.trim();\n if (projectRoot !== undefined && /^[1-9][0-9]*$/.test(trimmed)) {\n const idx = Number.parseInt(trimmed, 10);\n const hit = state.boxes.find(\n (b) => b.projectRoot === projectRoot && b.projectIndex === idx,\n );\n return hit ? { kind: 'ok', box: hit } : { kind: 'none' };\n }\n return findBox(trimmed, state);\n}\n","import { execa } from 'execa';\nimport { readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface DetectedGitRepo {\n kind: 'root' | 'nested';\n /** Absolute host path of the repo working tree (== `<workspace>` for root). */\n hostMainRepo: string;\n /** Path relative to the workspace where the repo lives. Empty string for root. */\n relPathFromWorkspace: string;\n}\n\n/**\n * Look for `.git` directories at the workspace root and at every 1st-level\n * subdirectory. Worktree-form `.git` files (regular file containing\n * `gitdir: …`) are intentionally skipped — turning an existing worktree into\n * another worktree gets weird, and the user case for it is rare.\n *\n * Pure host-side detection: it only tells callers where the repos are. Docker\n * boxes create the worktree inside the container against the bind-mounted\n * `.git/`; cloud boxes clone from a bundle. Either way this is the host probe.\n */\nexport async function detectGitRepos(workspace: string): Promise<DetectedGitRepo[]> {\n const out: DetectedGitRepo[] = [];\n if (await isGitDir(join(workspace, '.git'))) {\n out.push({ kind: 'root', hostMainRepo: workspace, relPathFromWorkspace: '' });\n }\n let entries: Array<{ name: string; isDirectory: () => boolean }>;\n try {\n entries = await readdir(workspace, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const e of entries) {\n if (!e.isDirectory() || e.name.startsWith('.')) continue;\n const sub = join(workspace, e.name);\n if (await isGitDir(join(sub, '.git'))) {\n out.push({ kind: 'nested', hostMainRepo: sub, relPathFromWorkspace: e.name });\n }\n }\n return out;\n}\n\nasync function isGitDir(path: string): Promise<boolean> {\n try {\n const s = await stat(path);\n return s.isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Pick `<base>`, `<base>-2`, `<base>-3`, … until git reports no such branch\n * exists. Avoids collision when the user reruns `agentbox create -n same-name`\n * after destroying — the destroyed box's branch still lives in the host repo.\n */\nexport async function pickFreshBranch(hostMainRepo: string, base: string): Promise<string> {\n let candidate = base;\n let suffix = 2;\n while (await branchExists(hostMainRepo, candidate)) {\n candidate = `${base}-${String(suffix++)}`;\n if (suffix > 100) throw new GitWorktreeError(`could not find a free branch name near ${base}`);\n }\n return candidate;\n}\n\nasync function branchExists(hostMainRepo: string, name: string): Promise<boolean> {\n const result = await execa(\n 'git',\n ['-C', hostMainRepo, 'show-ref', '--verify', '--quiet', `refs/heads/${name}`],\n { reject: false },\n );\n return result.exitCode === 0;\n}\n\nexport class GitWorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'GitWorktreeError';\n }\n}\n","/**\n * The host command that opens a URL or file path in the OS default handler.\n *\n * macOS ships `open`; Linux uses `xdg-open` (from `xdg-utils`, present on any\n * desktop install). We deliberately return only the binary name and let each\n * call site keep its own spawn semantics (sync/async, stdio, detached) — the\n * single platform decision lives here so adding a host platform is a one-line\n * change. Callers already treat a non-zero exit / ENOENT as \"couldn't\n * auto-open\" and print the target, so an absent `xdg-open` degrades cleanly.\n */\nexport function hostOpenCommand(): string {\n return process.platform === 'linux' ? 'xdg-open' : 'open';\n}\n","/**\n * Cross-provider versioning primitives for `~/.agentbox/<provider>-prepared.json`.\n *\n * Each provider records what it has baked (docker image / hetzner snapshot /\n * daytona snapshot) under a per-provider JSON file with a shared `base.*`\n * substructure so the CLI can detect when the on-disk artifact is stale\n * relative to the current CLI's build context.\n *\n * The invalidation key is `base.contextSha256`: a deterministic SHA-256\n * over every file in the build context (Dockerfile + scripts + baked\n * config), keyed by the file's relative path. Two CLIs with the same\n * staged runtime tree produce the same hash; an edit to any baked asset\n * — even a one-byte tweak to `custom-system-CLAUDE.md` — flips it.\n *\n * Checkpoints embed the captured `contextSha256` so restoring an older\n * checkpoint can warn the user that the baked layers predate the current\n * base image.\n */\n\nimport { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, resolve as pathResolve } from 'node:path';\n\nexport type PreparedProviderKind = 'docker' | 'daytona' | 'hetzner' | 'vercel';\n\n/**\n * The cross-provider record. `TImage` is the provider's opaque image\n * identifier: a string tag for docker/daytona, a numeric image id for\n * hetzner. The `TExtra` slot lets a provider attach provider-specific\n * fields (e.g. hetzner's `description` and `projects[]`) without forking\n * the whole shape.\n */\nexport interface PreparedBaseSnapshot<TImage = string, TExtra = unknown> {\n /** Schema version. Bumped when the on-disk shape changes incompatibly. */\n schema: number;\n base?: {\n /** Provider-opaque image identifier (docker tag | hetzner imageId | daytona snapshot name). */\n imageRef: TImage;\n /** Deterministic SHA-256 of the build context — the invalidation key. */\n contextSha256: string;\n /** Informational: CLI version that produced this artifact. */\n cliVersion: string;\n /** Informational: git short SHA injected at CLI build time (or 'dev'). */\n cliCommit?: string;\n /** ISO timestamp of bake completion. */\n createdAt: string;\n };\n /** Provider-specific extras (e.g. hetzner's per-project snapshot tier). */\n extras?: TExtra;\n}\n\nexport function preparedStatePathFor(provider: PreparedProviderKind): string {\n return pathResolve(homedir(), '.agentbox', `${provider}-prepared.json`);\n}\n\n/**\n * Read the prepared-state file for `provider`. Returns `null` when the file\n * is missing, malformed, or carries a schema this code doesn't recognise —\n * callers treat all three as \"rebuild needed\". Sync so it can run from\n * non-async setup paths (mirrors the hetzner helper it generalises).\n */\nexport function readPreparedStateRaw(provider: PreparedProviderKind): unknown {\n const path = preparedStatePathFor(provider);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, 'utf8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Atomic write: write to `<path>.tmp` then rename. `mode: 0o600` because\n * the file is informational but lives alongside `secrets.env` — same dir,\n * same permissions hygiene.\n */\nexport function writePreparedStateRaw(provider: PreparedProviderKind, state: unknown): void {\n const path = preparedStatePathFor(provider);\n mkdirSync(dirname(path), { recursive: true });\n const body = JSON.stringify(state, null, 2) + '\\n';\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, body, { mode: 0o600 });\n renameSync(tmp, path);\n}\n\nexport async function sha256OfFile(path: string): Promise<string> {\n const buf = await readFile(path);\n return createHash('sha256').update(buf).digest('hex');\n}\n\nexport interface ContextFile {\n /**\n * Logical relative path. Used as the canonical key for hash determinism\n * — two stagings with identical contents but different absolute paths\n * must hash the same.\n */\n rel: string;\n /** Absolute path the file is read from. */\n abs: string;\n}\n\n/**\n * Deterministic hash over a set of context files. Entries are sorted by\n * `rel` then hashed as `<rel>\\0<sha256(file)>\\n` lines into a final SHA-256.\n *\n * - Sort order = determinism (the caller can pass files in any order).\n * - NUL separator = no collision between a `rel` ending in hex and the\n * following digest.\n * - Trailing newline per record = stable framing.\n *\n * Missing files raise — silently skipping would let a partial dev rebuild\n * stamp a hash that doesn't represent what's actually in the image.\n */\nexport async function computeContextSha256(files: ContextFile[]): Promise<string> {\n const sorted = [...files].sort((a, b) => (a.rel < b.rel ? -1 : a.rel > b.rel ? 1 : 0));\n const outer = createHash('sha256');\n for (const f of sorted) {\n const inner = await sha256OfFile(f.abs);\n outer.update(`${f.rel}\\0${inner}\\n`);\n }\n return outer.digest('hex');\n}\n\n/** Short form for log lines — first 12 hex chars of a sha256. */\nexport function shortFingerprint(sha: string): string {\n return sha.slice(0, 12);\n}\n\n/**\n * CLI version stamps set by `apps/cli/src/index.ts` at startup via env vars\n * (the values themselves come from tsup's build-time `define`). Providers\n * record them onto prepared-state files and checkpoint manifests so a stale\n * artifact carries a human-readable hint about which CLI built it.\n *\n * Fallbacks cover the unit-test and unbundled-dev paths (the CLI never\n * loaded, env unset). `unknown` is a sentinel — never a real version.\n */\nexport interface CliStamp {\n cliVersion: string;\n cliCommit: string;\n}\n\nexport function readCliStamp(): CliStamp {\n return {\n cliVersion: process.env.AGENTBOX_CLI_VERSION ?? 'unknown',\n cliCommit: process.env.AGENTBOX_CLI_COMMIT ?? 'unknown',\n };\n}\n\n/**\n * Canonical map of files that go into the Docker base image build context\n * — every file `Dockerfile.box` COPYs, plus the Dockerfile itself. Two\n * layouts resolve the same logical entries:\n *\n * - staged: `<contextDir>/<staged>` (production CLI runtime + dev with `apps/cli/runtime/docker`)\n * - dev: `<sandboxDockerRoot>/<dev>` (workspace dev, no staged tree)\n *\n * Shared across providers because:\n * - sandbox-docker uses it to fingerprint its locally-built image.\n * - sandbox-daytona uses it to fingerprint the snapshot it bakes from the\n * same Dockerfile.box + the daytona-specific CLAUDE.md overlay.\n *\n * If you add a COPY line to `Dockerfile.box`, add the file here AND in\n * `apps/cli/scripts/stage-runtime.mjs` — failure to do so means the image\n * won't get re-built when that file changes.\n */\nexport const DOCKER_CONTEXT_FILE_MAP: Record<string, { staged: string; dev: string }> = {\n 'Dockerfile.box': { staged: 'Dockerfile.box', dev: 'Dockerfile.box' },\n 'ctl/bin.cjs': {\n staged: 'packages/ctl/dist/bin.cjs',\n dev: '../ctl/dist/bin.cjs',\n },\n 'share/agentbox-setup/SKILL.md': {\n staged: 'apps/cli/share/agentbox-setup/SKILL.md',\n dev: '../../apps/cli/share/agentbox-setup/SKILL.md',\n },\n 'scripts/agentbox-vnc-start': {\n staged: 'packages/sandbox-docker/scripts/agentbox-vnc-start',\n dev: 'scripts/agentbox-vnc-start',\n },\n 'scripts/agentbox-dockerd-start': {\n staged: 'packages/sandbox-docker/scripts/agentbox-dockerd-start',\n dev: 'scripts/agentbox-dockerd-start',\n },\n 'scripts/agentbox-checkpoint-cleanup': {\n staged: 'packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup',\n dev: 'scripts/agentbox-checkpoint-cleanup',\n },\n 'scripts/agentbox-open': {\n staged: 'packages/sandbox-docker/scripts/agentbox-open',\n dev: 'scripts/agentbox-open',\n },\n 'scripts/custom-system-CLAUDE.md': {\n staged: 'packages/sandbox-docker/scripts/custom-system-CLAUDE.md',\n dev: 'scripts/custom-system-CLAUDE.md',\n },\n 'scripts/claude-managed-settings.json': {\n staged: 'packages/sandbox-docker/scripts/claude-managed-settings.json',\n dev: 'scripts/claude-managed-settings.json',\n },\n 'scripts/agentbox-codex-hooks.json': {\n staged: 'packages/sandbox-docker/scripts/agentbox-codex-hooks.json',\n dev: 'scripts/agentbox-codex-hooks.json',\n },\n};\n\n/**\n * Resolve every entry in `fileMap` to an absolute path. Tries `<contextDir>/<staged>`\n * first; falls back to `<devRoot>/<dev>`. Returns `null` if any required file\n * is missing — callers treat that as \"can't fingerprint\" and skip the\n * cache-hit shortcut. Pure (no I/O beyond `existsSync`), so safe for use\n * from the provider's prepare path.\n */\nexport function resolveContextFilesFrom(\n fileMap: Record<string, { staged: string; dev: string }>,\n opts: { contextDir: string; devRoot: string },\n): ContextFile[] | null {\n const out: ContextFile[] = [];\n for (const [rel, paths] of Object.entries(fileMap)) {\n const candidates = [\n pathResolve(opts.contextDir, paths.staged),\n pathResolve(opts.devRoot, paths.dev),\n ];\n const hit = candidates.find((p) => existsSync(p));\n if (!hit) return null;\n out.push({ rel, abs: hit });\n }\n return out;\n}\n","/**\n * Docker provider's `~/.agentbox/docker-prepared.json` reader/writer + the\n * build-context fingerprint that drives base-image invalidation.\n *\n * The fingerprint is a SHA-256 over every file `docker build` would COPY\n * into the image — Dockerfile + scripts + baked config files. Two CLIs\n * with identical staged runtime trees produce the same hash; a one-byte\n * edit to any baked asset flips it, which is the signal `ensureImage()`\n * uses to rebuild instead of reusing the cached image.\n */\n\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n computeContextSha256,\n DOCKER_CONTEXT_FILE_MAP,\n readCliStamp,\n readPreparedStateRaw,\n resolveContextFilesFrom,\n writePreparedStateRaw,\n type ContextFile,\n type PreparedBaseSnapshot,\n} from '@agentbox/sandbox-core';\nimport { BUILD_CONTEXT_DIR, DEFAULT_BOX_IMAGE, DOCKERFILE_PATH } from './image.js';\n\nconst SCHEMA = 1 as const;\n\nexport type PreparedDockerState = PreparedBaseSnapshot<string, never>;\n\n/**\n * Resolve every fingerprint input to an absolute path. The canonical file\n * list lives in `@agentbox/sandbox-core` (DOCKER_CONTEXT_FILE_MAP) so the\n * daytona provider can hash the same inputs without depending on this\n * package. Two layouts are tried in order, mirroring `resolveDockerBuild()`\n * in `image.ts`:\n * 1. Build context dir (staged runtime / env override).\n * 2. Sandbox-docker package root (dev fallback).\n *\n * Returns `null` when *any* required file is missing — callers treat that\n * as \"can't fingerprint\" and skip the cache-hit shortcut (always rebuild).\n */\nexport function resolveContextFiles(opts: { contextDir?: string } = {}): ContextFile[] | null {\n const ctx = opts.contextDir ?? BUILD_CONTEXT_DIR;\n const here = dirname(fileURLToPath(import.meta.url));\n // sandbox-docker's package root = parent of src/ or parent of dist/.\n const packageRoot = resolve(here, '..');\n return resolveContextFilesFrom(DOCKER_CONTEXT_FILE_MAP, {\n contextDir: ctx,\n devRoot: packageRoot,\n });\n}\n\nexport interface ResolvedFingerprint {\n contextSha256: string;\n /** Files that fed the hash (in canonical sorted order). */\n files: ContextFile[];\n}\n\nexport async function computeDockerContextFingerprint(opts: {\n contextDir?: string;\n} = {}): Promise<ResolvedFingerprint | null> {\n const files = resolveContextFiles(opts);\n if (!files) return null;\n return { contextSha256: await computeContextSha256(files), files };\n}\n\nexport function readPreparedDockerState(): PreparedDockerState | null {\n const raw = readPreparedStateRaw('docker');\n if (raw === null || typeof raw !== 'object') return null;\n const parsed = raw as Partial<PreparedDockerState>;\n if (parsed.schema !== SCHEMA) return null;\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedDockerState(opts: {\n imageRef?: string;\n contextSha256: string;\n}): void {\n const stamp = readCliStamp();\n const state: PreparedDockerState = {\n schema: SCHEMA,\n base: {\n imageRef: opts.imageRef ?? DEFAULT_BOX_IMAGE,\n contextSha256: opts.contextSha256,\n cliVersion: stamp.cliVersion,\n cliCommit: stamp.cliCommit,\n createdAt: new Date().toISOString(),\n },\n };\n writePreparedStateRaw('docker', state);\n}\n\n/** Convenience for `ensureImage` and `prepare` — true when the stamped fingerprint matches. */\nexport function preparedMatches(state: PreparedDockerState | null, current: string): boolean {\n return state?.base?.contextSha256 === current;\n}\n\n/** Re-export so callers don't reach into image.ts just for the Dockerfile path. */\nexport { DOCKERFILE_PATH };\n","import { execa } from 'execa';\nimport { existsSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\n\nexport const DEFAULT_BOX_IMAGE = 'agentbox/box:dev';\n\n/**\n * Public registry repo the box image is published to (see\n * `.github/workflows/box-image.yml`). The CLI pulls a fingerprint-tagged\n * image from here on first use instead of building locally — a multi-minute\n * build collapses to a `docker pull`. An empty registry (config override)\n * disables pulling and always builds.\n */\nexport const BOX_IMAGE_REGISTRY = 'ghcr.io/madarco/agentbox/box';\n\n/**\n * The pull target for a given build-context fingerprint. The tag *is* the\n * content identity: a local staged context that matches a published build\n * has the same sha, so a pull hit can be retagged to `agentbox/box:dev` and\n * stamped into docker-prepared.json without risk of a stale image (a locally\n * edited context has a different sha, its tag 404s, and we build instead).\n */\nexport function registryRefForSha(sha: string, registry: string = BOX_IMAGE_REGISTRY): string {\n return `${registry}:sha-${sha.slice(0, 16)}`;\n}\n\nconst here = dirname(fileURLToPath(import.meta.url));\n\n// The Dockerfile's COPY lines reference monorepo-relative paths\n// (packages/ctl/dist/bin.cjs, apps/cli/share/..., packages/sandbox-docker/scripts/*),\n// so the build context must be a dir containing that tree.\n//\n// Resolution order:\n// 0. AGENTBOX_DOCKER_CONTEXT env override (dir holding Dockerfile.box).\n// 1. Staged context shipped with the bundled `agent-box` package: this\n// module is bundled into the CLI at <root>/dist, the stage step mirrors\n// the COPY tree at <root>/runtime/docker (sibling of dist/, uniform in\n// dev and when installed).\n// 2. Legacy monorepo: Dockerfile.box at the sandbox-docker package root,\n// build context = monorepo root.\nfunction resolveDockerBuild(): { dockerfile: string; context: string } {\n const override = process.env.AGENTBOX_DOCKER_CONTEXT;\n if (override && existsSync(resolve(override, 'Dockerfile.box'))) {\n return { dockerfile: resolve(override, 'Dockerfile.box'), context: override };\n }\n const staged = resolve(here, '..', 'runtime', 'docker');\n if (existsSync(resolve(staged, 'Dockerfile.box'))) {\n return { dockerfile: resolve(staged, 'Dockerfile.box'), context: staged };\n }\n // Legacy: src/ (or the unbundled package dist/) is one level under the\n // package root; the monorepo root is two more up.\n const packageRoot = resolve(here, '..');\n return {\n dockerfile: resolve(packageRoot, 'Dockerfile.box'),\n context: resolve(packageRoot, '..', '..'),\n };\n}\n\nconst { dockerfile: DOCKERFILE_PATH_RESOLVED, context: BUILD_CONTEXT_DIR_RESOLVED } =\n resolveDockerBuild();\nexport const DOCKERFILE_PATH = DOCKERFILE_PATH_RESOLVED;\nexport const BUILD_CONTEXT_DIR = BUILD_CONTEXT_DIR_RESOLVED;\n\nexport async function imageExists(ref: string): Promise<boolean> {\n const result = await execa('docker', ['image', 'inspect', ref], { reject: false });\n return result.exitCode === 0;\n}\n\n/**\n * Attempt `docker pull <target>`. Returns true on success, false on any\n * failure (missing tag, offline, auth) — callers fall back to a local build.\n * Never throws. Single attempt: a missing tag is the expected \"build locally\"\n * signal, not a transient error worth retrying.\n */\nexport async function pullImage(\n target: string,\n opts: { onProgress?: (line: string) => void } = {},\n): Promise<boolean> {\n const subprocess = execa('docker', ['pull', target], {\n stderr: 'pipe',\n stdout: 'pipe',\n reject: false,\n });\n if (opts.onProgress) {\n const forward = (chunk: Buffer | string): void => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onProgress?.(line);\n }\n };\n subprocess.stdout?.on('data', forward);\n subprocess.stderr?.on('data', forward);\n }\n const result = await subprocess;\n return result.exitCode === 0;\n}\n\nexport async function tagImage(source: string, target: string): Promise<void> {\n await execa('docker', ['tag', source, target]);\n}\n\nexport interface ImageInfo {\n /** Image ref (e.g. `agentbox/box:dev`). */\n ref: string;\n /** True when the engine has the image locally. */\n exists: boolean;\n /** Image size in bytes, when known. */\n sizeBytes?: number;\n /** ISO-8601 creation time, when known. */\n createdAt?: string;\n}\n\n/**\n * Read-only inspect of a Docker image. Used by `agentbox prepare` (no-args\n * status mode) to surface base-image state. Never throws — returns\n * `{ exists: false }` on any error so the status command works even when\n * the docker daemon is unreachable.\n */\nexport async function imageInfo(ref: string = DEFAULT_BOX_IMAGE): Promise<ImageInfo> {\n const result = await execa(\n 'docker',\n ['image', 'inspect', '--format', '{{.Size}}|{{.Created}}', ref],\n { reject: false },\n );\n if (result.exitCode !== 0) return { ref, exists: false };\n const [sizeStr, createdAt] = result.stdout.trim().split('|');\n const sizeBytes = sizeStr ? Number.parseInt(sizeStr, 10) : NaN;\n return {\n ref,\n exists: true,\n sizeBytes: Number.isFinite(sizeBytes) ? sizeBytes : undefined,\n createdAt: createdAt && createdAt.length > 0 ? createdAt : undefined,\n };\n}\n\nexport interface BuildImageOptions {\n ref?: string;\n dockerfile?: string;\n contextDir?: string;\n onProgress?: (line: string) => void;\n}\n\nexport async function buildImage(opts: BuildImageOptions = {}): Promise<string> {\n const ref = opts.ref ?? DEFAULT_BOX_IMAGE;\n const dockerfile = opts.dockerfile ?? DOCKERFILE_PATH;\n const contextDir = opts.contextDir ?? BUILD_CONTEXT_DIR;\n\n // Dogfood path: when building from inside an agentbox (docker-in-docker),\n // the default bridge network can't bind-mount /proc/<pid>/ns/net for the\n // build container, breaking any RUN that needs network (e.g. apt, curl).\n // Falling back to host networking sidesteps the missing capability.\n const args = ['build', '-t', ref, '-f', dockerfile, contextDir];\n if (process.env.AGENTBOX === '1') {\n args.splice(1, 0, '--network=host');\n }\n\n const subprocess = execa('docker', args, {\n stderr: 'pipe',\n stdout: 'pipe',\n });\n\n if (opts.onProgress) {\n const forward = (chunk: Buffer | string): void => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onProgress?.(line);\n }\n };\n subprocess.stdout?.on('data', forward);\n subprocess.stderr?.on('data', forward);\n }\n\n await subprocess;\n return ref;\n}\n\nexport interface PullOrBuildOptions {\n onProgress?: (line: string) => void;\n /** Dockerfile path. Defaults to `Dockerfile.box` next to this package. */\n dockerfile?: string;\n /** Build context directory. Defaults to the staged runtime / monorepo root. */\n contextDir?: string;\n /** Try the registry before building. Defaults to true. */\n allowPull?: boolean;\n /** Registry repo to pull from. Defaults to `BOX_IMAGE_REGISTRY`; empty disables pulling. */\n registry?: string;\n}\n\n/**\n * Make `ref` present locally, preferring a registry pull over a local build.\n *\n * When `fingerprint` is non-null and pulling is allowed, pull the\n * fingerprint-tagged image and retag it to `ref`; on a miss (or when pulling\n * is disabled / unfingerprintable) build from the staged context. Either way,\n * a known fingerprint is stamped into docker-prepared.json so the next\n * `ensureImage()` treats this as a cache hit.\n */\nexport async function pullOrBuild(\n ref: string,\n fingerprint: { contextSha256: string } | null,\n opts: PullOrBuildOptions = {},\n): Promise<{ source: 'pulled' | 'built' }> {\n const { writePreparedDockerState } = await import('./prepared-state.js');\n const registry = opts.registry ?? BOX_IMAGE_REGISTRY;\n const allowPull = opts.allowPull !== false;\n\n if (allowPull && registry && fingerprint) {\n const target = registryRefForSha(fingerprint.contextSha256, registry);\n opts.onProgress?.(`[image] pulling ${target}`);\n if (await pullImage(target, { onProgress: opts.onProgress })) {\n await tagImage(target, ref);\n writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });\n opts.onProgress?.(`[image] pulled ${target} -> ${ref}`);\n return { source: 'pulled' };\n }\n opts.onProgress?.(`[image] registry miss, building ${ref} locally`);\n }\n\n await buildImage({\n ref,\n dockerfile: opts.dockerfile,\n contextDir: opts.contextDir,\n onProgress: opts.onProgress,\n });\n if (fingerprint) {\n writePreparedDockerState({ imageRef: ref, contextSha256: fingerprint.contextSha256 });\n }\n return { source: 'built' };\n}\n\nexport interface EnsureImageOptions {\n onProgress?: (line: string) => void;\n /** Dockerfile path. Defaults to `Dockerfile.box` next to this package. */\n dockerfile?: string;\n /** Build context directory. Defaults to the monorepo root. */\n contextDir?: string;\n /** Try the registry before building. Defaults to true. */\n allowPull?: boolean;\n /** Registry repo to pull from. Defaults to `BOX_IMAGE_REGISTRY`; empty disables pulling. */\n registry?: string;\n}\n\nexport async function ensureImage(\n ref: string = DEFAULT_BOX_IMAGE,\n opts: EnsureImageOptions = {},\n): Promise<{ ref: string; built: boolean; reason?: string }> {\n // Lazy import: prepared-state imports back into image.ts for the default\n // DOCKERFILE_PATH/BUILD_CONTEXT_DIR constants, so loading it at top-level\n // would create a circular ESM init order.\n const { computeDockerContextFingerprint, readPreparedDockerState, preparedMatches } =\n await import('./prepared-state.js');\n\n const fingerprint = await computeDockerContextFingerprint({\n contextDir: opts.contextDir,\n });\n const prepared = readPreparedDockerState();\n const exists = await imageExists(ref);\n\n let reason: string | undefined;\n if (!exists) {\n reason = `image ${ref} not present`;\n } else if (!fingerprint) {\n // Couldn't enumerate the context (partial dev rebuild?). Don't rebuild\n // unconditionally — that would surprise users mid-iteration. Trust the\n // image-exists check and leave the prepared file untouched.\n return { ref, built: false, reason: 'image present (fingerprint skipped)' };\n } else if (!prepared) {\n reason = 'no docker-prepared.json on disk';\n } else if (!preparedMatches(prepared, fingerprint.contextSha256)) {\n reason =\n `build context changed (was ${prepared.base?.contextSha256?.slice(0, 12) ?? '<none>'}, ` +\n `now ${fingerprint.contextSha256.slice(0, 12)})`;\n }\n\n if (!reason) {\n return { ref, built: false, reason: 'image up to date' };\n }\n\n opts.onProgress?.(`[image] ${ref}: ${reason}`);\n const { source } = await pullOrBuild(ref, fingerprint, {\n onProgress: opts.onProgress,\n dockerfile: opts.dockerfile,\n contextDir: opts.contextDir,\n allowPull: opts.allowPull,\n registry: opts.registry,\n });\n return { ref, built: source === 'built', reason };\n}\n\n"],"mappings":"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","names":["join","readFile","homedir","dirname","dirname","resolve","fileURLToPath","execa","existsSync","dirname","writePreparedDockerState","computeDockerContextFingerprint","readPreparedDockerState","preparedMatches","here","fileURLToPath","resolve"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/sandbox-hetzner/src/index.ts","../../../packages/sandbox-hetzner/src/backend.ts","../../../packages/sandbox-hetzner/src/cloud-init.ts","../../../packages/sandbox-hetzner/src/poll.ts","../../../packages/sandbox-hetzner/src/prepared-state.ts","../../../packages/sandbox-hetzner/src/prepare.ts","../../../packages/sandbox-hetzner/src/runtime-assets.ts","../../../packages/sandbox-hetzner/src/ssh-key.ts","../../../packages/sandbox-hetzner/src/ssh-cli.ts","../../../packages/sandbox-hetzner/src/ssh-tunnel.ts"],"sourcesContent":["/**\n * The Hetzner Cloud VPS sandbox provider. A thin `CloudBackend` over OpenSSH\n * + the Hetzner Cloud REST API, composed via `@agentbox/sandbox-cloud`'s\n * `createCloudProvider` for the provider-agnostic scaffolding (workspace\n * seeding, ctl launch, state, relay polling, VNC, checkpoints).\n *\n * **Phase 2 status:** the SDK shim (auth + REST client + retry + firewall\n * + egress IP) is wired; `hetznerBackend` is a stub whose per-method bodies\n * throw `notImplemented` until Phase 4 plumbs in SSH ControlMaster +\n * provisioning. The provider is registered so `--provider hetzner` resolves\n * cleanly and the build is honest about what's missing.\n */\n\nimport type { Provider } from '@agentbox/core';\nimport { createCloudProvider } from '@agentbox/sandbox-cloud';\nimport { hetznerBackend, HETZNER_DEFAULT_BOX_IMAGE_REF } from './backend.js';\nimport { prepareHetznerProvider } from './prepare.js';\n\nconst cloudProvider = createCloudProvider(hetznerBackend, {\n defaultResources: { cpu: 2, memory: 4, disk: 40 },\n});\n\nexport const hetznerProvider: Provider = {\n ...cloudProvider,\n prepare: prepareHetznerProvider,\n};\n\nexport { hetznerBackend, HETZNER_DEFAULT_BOX_IMAGE_REF };\nexport { ensureHetznerEnvLoaded } from './env-loader.js';\nexport {\n ensureHetznerCredentials,\n readHetznerCredStatus,\n secretsPath,\n maskKey,\n type EnsureHetznerCredentialsOptions,\n type HetznerCredStatus,\n} from './credentials.js';\nexport {\n ensureHetznerBaseSnapshot,\n prepareHetzner,\n prepareHetznerProvider,\n type PrepareHetznerOptions,\n type PrepareHetznerResult,\n} from './prepare.js';\nexport { generateBoxCloudInit, generatePrepareCloudInit, type BoxCloudInitOptions, type PrepareCloudInitOptions } from './cloud-init.js';\nexport {\n RUNTIME_ASSETS,\n candidatesFor,\n resolveRuntimeAssets,\n type ResolvedAsset,\n type RuntimeAsset,\n} from './runtime-assets.js';\nexport { mintPrepareKey, mintSshKey, type MintedSshKey } from './ssh-key.js';\nexport {\n scpDownload,\n scpUpload,\n sshExec,\n sshOptArgs,\n waitForSsh,\n type SshExecOptions,\n type SshExecResult,\n type SshTargetArgs,\n} from './ssh-cli.js';\nexport { pollUntil, type PollOptions } from './poll.js';\nexport {\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n updatePreparedState,\n type PreparedBaseSnapshot,\n type PreparedHetznerState,\n} from './prepared-state.js';\nexport {\n makeHetznerClient,\n HetznerApiError,\n DEFAULT_HCLOUD_ENDPOINT,\n type CreateFirewallRequest,\n type CreateServerRequest,\n type HetznerAction,\n type HetznerClient,\n type HetznerFirewall,\n type HetznerFirewallRule,\n type HetznerImage,\n type HetznerServer,\n type HetznerServerStatus,\n type HetznerSshKey,\n} from './client.js';\nexport { detectEgressIp, type DetectEgressIpOptions } from './egress-ip.js';\nexport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n sshOnlyInboundRule,\n syncFirewallSource,\n type CreateFirewallOptions,\n} from './firewall.js';\nexport { withHetznerRetry, isAttemptTimeout, isRetriable } from './retry.js';\n","/**\n * Hetzner `CloudBackend` — maps the provider-neutral cloud primitives onto\n * OpenSSH + the Hetzner Cloud REST API.\n *\n * Design rationale + the safety/tunneling model live in the plan at\n * `~/.claude/plans/how-to-safely-create-parallel-pebble.md` and the live\n * status doc at `docs/hertzner_backlog.md`. The short version:\n *\n * - 1:1 VPS-per-box. Each box gets a per-box ed25519 keypair (private key\n * never leaves the host) and a per-box Hetzner Cloud Firewall locked to\n * the host's egress IP.\n * - All comms (exec, file I/O, port forwards for bridge / web / VNC) flow\n * over one persistent `ssh` ControlMaster owned by `SshTunnelManager`.\n * - `previewUrl(port)` mints an `ssh -L 127.0.0.1:<localPort>:127.0.0.1:<remote>`\n * on demand. The cloud-provider scaffolding (`createCloudProvider`) then\n * decorates those URLs with Portless aliases for symmetric\n * `<box-name>.localhost` semantics — handled provider-side rather than\n * here so the backend stays focused on plumbing.\n * - Checkpoints map to Hetzner `create_image` snapshots; default no-pause,\n * opt-in pause via `createSnapshot({pause: true})`.\n */\n\nimport { existsSync } from 'node:fs';\nimport { rm, rename, mkdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { execa } from 'execa';\nimport { resolve as resolvePath } from 'node:path';\nimport type {\n CloudBackend,\n CloudExecResult,\n CloudFileEntry,\n CloudHandle,\n CloudPreviewUrl,\n CloudProvisionRequest,\n CloudSandboxSummary,\n CloudState,\n} from '@agentbox/core';\nimport {\n stageClaudeCredentialsForUpload,\n stageCodexCredentialsForUpload,\n stageOpencodeCredentialsForUpload,\n} from '@agentbox/sandbox-cloud';\nimport { generateBoxCloudInit } from './cloud-init.js';\nimport {\n HetznerApiError,\n makeHetznerClient,\n type HetznerClient,\n type HetznerImage,\n type HetznerServer,\n type HetznerServerStatus,\n} from './client.js';\nimport { detectEgressIp } from './egress-ip.js';\nimport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n} from './firewall.js';\nimport { pollUntil } from './poll.js';\nimport { readPreparedState } from './prepared-state.js';\nimport { ensureHetznerBaseSnapshot } from './prepare.js';\nimport { mintSshKey } from './ssh-key.js';\nimport { waitForSsh, sshOptArgs, type SshTargetArgs } from './ssh-cli.js';\nimport { SshTunnelManager, defaultBoxSshDir } from './ssh-tunnel.js';\nimport { withHetznerRetry } from './retry.js';\n\nexport const HETZNER_DEFAULT_BOX_IMAGE_REF = 'agentbox-base';\n\n/**\n * The cloud-provider scaffolding defaults `req.image` to `'agentbox/box:dev'`\n * (the docker provider's local image tag) when nothing else is specified.\n * That value is meaningless on Hetzner — we recognize it (alongside our own\n * sentinel + plain undefined) as \"use the hetzner base snapshot.\"\n */\nconst SCAFFOLDING_FALLBACK_IMAGE = 'agentbox/box:dev';\nconst VPS_USER = 'vscode';\nconst PROVISION_SSH_DEADLINE_MS = 5 * 60_000;\nconst ACTION_DEADLINE_MS = 5 * 60_000;\nconst SNAPSHOT_DEADLINE_MS = 20 * 60_000;\n// `cx22` was deprecated by Hetzner in early 2026; `cx23` is the drop-in\n// replacement with the same 2 vCPU / 4 GB / 40 GB shape on x86.\nconst HETZNER_DEFAULT_SERVER_TYPE = 'cx23';\nconst HETZNER_DEFAULT_LOCATION = 'nbg1';\n\n/** Module-level tunnel manager — one ControlMaster per box for this process. */\nconst tunnels = new SshTunnelManager();\n\n/**\n * Map Hetzner's per-server status onto the four-value `CloudState` everyone\n * else consumes. Transitional ones ('starting', 'rebuilding', …) are reported\n * as 'running' so callers don't ping-pong; 'off' maps to 'paused' because\n * Hetzner's stop = power off (vs Daytona's archive).\n */\nfunction mapState(s: HetznerServerStatus | string | undefined): CloudState {\n switch (s) {\n case 'running':\n return 'running';\n case 'starting':\n case 'initializing':\n case 'stopping':\n case 'migrating':\n case 'rebuilding':\n return 'running';\n case 'off':\n return 'paused';\n case 'deleting':\n case 'unknown':\n default:\n return 'missing';\n }\n}\n\nfunction client(): HetznerClient {\n return makeHetznerClient();\n}\n\nasync function getServerStrict(id: number): Promise<HetznerServer> {\n const s = await client().getServer(id);\n if (!s) {\n throw new Error(`hetzner: server ${String(id)} not found (already destroyed?)`);\n }\n return s;\n}\n\n/** Lookup an image by description (\"snapshot name\" in user-facing terms). */\nasync function findImageByDescription(c: HetznerClient, description: string): Promise<HetznerImage | null> {\n const all = await c.listImages({ type: 'snapshot' });\n return all.find((i) => i.description === description) ?? null;\n}\n\n/**\n * Resolve a `CloudProvisionRequest.image|snapshot` into a Hetzner image id.\n * Precedence: `req.snapshot` → `req.image`:\n * - the sentinel `agentbox-base` → load id from `hetzner-prepared.json`.\n * - a numeric string → use as-is.\n * - any other string → treated as a snapshot description (checkpoint name)\n * and looked up via the API.\n */\nasync function resolveImageId(c: HetznerClient, req: CloudProvisionRequest): Promise<number | string> {\n const ref = req.snapshot ?? req.image;\n if (!ref || ref === HETZNER_DEFAULT_BOX_IMAGE_REF || ref === SCAFFOLDING_FALLBACK_IMAGE) {\n await ensureHetznerBaseSnapshot();\n const state = readPreparedState();\n if (!state.base) {\n throw new Error(\n 'no Hetzner base snapshot found — run `agentbox prepare --provider hetzner` to bake one.',\n );\n }\n return state.base.imageId;\n }\n if (/^\\d+$/.test(ref)) {\n return Number.parseInt(ref, 10);\n }\n // Try snapshot-by-description first (checkpoints use that), then fall through\n // to Hetzner stock images (the user passed e.g. `ubuntu-24.04`).\n const snap = await findImageByDescription(c, ref);\n if (snap) return snap.id;\n return ref;\n}\n\n/**\n * Capture per-box state we need across method calls. Lives on disk under\n * `~/.agentbox/hetzner/boxes/<sandboxId>/` and is the source of truth for\n * the SSH identity + firewall id + cached IP.\n */\ninterface PerBoxState {\n dir: string;\n identity: string;\n knownHosts: string;\n firewallId?: number;\n firewallSource?: string;\n vpsIp?: string;\n}\n\nfunction perBoxDir(sandboxId: string): string {\n return resolvePath(defaultBoxSshDir(sandboxId), '..');\n}\n\nasync function ensurePerBoxState(sandboxId: string): Promise<PerBoxState> {\n const dir = perBoxDir(sandboxId);\n const sshDir = join(dir, 'ssh');\n await mkdir(sshDir, { recursive: true, mode: 0o700 });\n return {\n dir,\n identity: join(sshDir, 'id_ed25519'),\n knownHosts: join(sshDir, 'known_hosts'),\n };\n}\n\nfunction bashScript(s: string): string {\n // Always run remote commands under bash -lc so /etc/profile.d/agentbox.sh\n // (and the PATH prepend / DISPLAY / AGENT_BROWSER_* it sets) get sourced.\n return `bash -lc ${shellQuote(s)}`;\n}\n\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction buildSshTarget(state: PerBoxState, vpsIp: string, controlPath?: string): SshTargetArgs {\n return {\n host: vpsIp,\n user: VPS_USER,\n identity: state.identity,\n knownHosts: state.knownHosts,\n controlPath,\n };\n}\n\nasync function ensureTunnel(sandboxId: string, state: PerBoxState, vpsIp: string): Promise<void> {\n if (tunnels.has(sandboxId)) return;\n await tunnels.open({\n boxId: sandboxId,\n vpsHost: vpsIp,\n identity: state.identity,\n });\n}\n\n/**\n * Open the ControlMaster (if not already up) and return an SshTargetArgs\n * whose `controlPath` is set to the live master so exec/scp reuse it.\n */\nasync function ensureLiveTarget(sandboxId: string): Promise<{\n target: SshTargetArgs;\n state: PerBoxState;\n vpsIp: string;\n}> {\n const id = Number.parseInt(sandboxId, 10);\n if (!Number.isFinite(id)) {\n throw new Error(`hetzner: invalid sandboxId ${sandboxId}`);\n }\n const server = await getServerStrict(id);\n const vpsIp = server.public_net.ipv4?.ip;\n if (!vpsIp) {\n throw new Error(`hetzner: server ${String(id)} has no IPv4 address`);\n }\n const state = await ensurePerBoxState(sandboxId);\n if (!existsSync(state.identity)) {\n throw new Error(\n `hetzner: per-box SSH key missing for sandbox ${sandboxId} (expected at ${state.identity}). ` +\n `If this box was created by a different host, you'll need to re-create it on this host.`,\n );\n }\n await ensureTunnel(sandboxId, state, vpsIp);\n const controlPath = tunnels.controlPath(sandboxId);\n return { target: buildSshTarget(state, vpsIp, controlPath), state, vpsIp };\n}\n\nexport const hetznerBackend: CloudBackend = {\n name: 'hetzner',\n\n async provision(req: CloudProvisionRequest): Promise<CloudHandle> {\n const c = client();\n const onLog = req.onLog ?? (() => {});\n const progress = (s: string) => onLog(`hetzner: ${s}`);\n\n // 1. Gate on the base snapshot existing (lifts the Phase-2 placeholder).\n await ensureHetznerBaseSnapshot();\n const imageRef = await resolveImageId(c, req);\n\n // 2. Detect egress IP + normalize firewall source.\n const egressOverride =\n req.env?.AGENTBOX_HETZNER_FIREWALL_SOURCE ?? process.env.AGENTBOX_HETZNER_FIREWALL_SOURCE;\n const source = egressOverride\n ? normalizeSourceCidr(egressOverride)\n : `${await detectEgressIp({ onLog })}/32`;\n progress(`firewall source: ${source}`);\n\n // 3. Mint per-box SSH key into a temp dir keyed by a fresh uuid; we\n // rename it to `~/.agentbox/hetzner/boxes/<sandboxId>/ssh/` once the\n // server id is known.\n const stamp = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);\n const tempDir = resolvePath(\n process.env.HOME ?? process.cwd(),\n '.agentbox',\n 'hetzner',\n `pending-${stamp}`,\n 'ssh',\n );\n const key = await mintSshKey(tempDir, `agentbox-box-${req.name}-${stamp}`);\n\n let firewallId: number | null = null;\n let serverId: number | null = null;\n try {\n // 4. Firewall.\n const firewall = await createPerBoxFirewall(c, {\n name: `agentbox-${req.name}-${stamp}`,\n sourceCidr: source,\n labels: {\n 'agentbox.box': req.name,\n 'agentbox.role': 'box',\n },\n });\n firewallId = firewall.id;\n\n // 5. Cloud-init for the box: vscode user, pubkey, /etc/hosts alias,\n // optional box.env passthrough from req.env.\n const boxEnv: Record<string, string> = {};\n for (const [k, v] of Object.entries(req.env ?? {})) {\n if (k.startsWith('AGENTBOX_')) boxEnv[k] = v;\n }\n const cloudInit = generateBoxCloudInit({\n sshPubkey: key.publicKey,\n boxName: req.name,\n boxEnv: Object.keys(boxEnv).length > 0 ? boxEnv : undefined,\n });\n\n // 6. Create the server.\n const serverType = (req.size && req.size.trim()) || HETZNER_DEFAULT_SERVER_TYPE;\n progress(\n `creating VPS '${req.name}' from image ${String(imageRef)} (${serverType} / ${HETZNER_DEFAULT_LOCATION})`,\n );\n const created = await withHetznerRetry(\n { method: 'createServer', retryOnAmbiguous: false, attemptTimeoutMs: 120_000 },\n () =>\n c.createServer({\n name: `agentbox-${req.name}-${stamp}`,\n server_type: serverType,\n image: imageRef,\n location: HETZNER_DEFAULT_LOCATION,\n user_data: cloudInit,\n firewalls: [{ firewall: firewall.id }],\n labels: {\n 'agentbox.managed': 'true',\n 'agentbox.role': 'box',\n 'agentbox.box': req.name,\n 'agentbox.firewall': String(firewall.id),\n },\n start_after_create: true,\n }),\n );\n serverId = created.server.id;\n const vpsIp = created.server.public_net.ipv4?.ip;\n if (!vpsIp) {\n throw new Error(`hetzner: server ${String(serverId)} came up without an IPv4 address`);\n }\n progress(`server ${String(serverId)} provisioned at ${vpsIp}; waiting for ssh`);\n\n // 7. Move the freshly-minted key from its temp location into the\n // sandboxId-keyed final dir, then rm the temp parent. We move file-\n // by-file rather than renaming the whole dir because `ensurePerBoxState`\n // had to mkdir the final dir before we knew the sandbox id (so the\n // tunnel manager + state-restoration paths can find it later).\n const sandboxId = String(serverId);\n const state = await ensurePerBoxState(sandboxId);\n await rename(key.privatePath, state.identity);\n await rename(key.publicPath, `${state.identity}.pub`);\n // Drop the now-empty temp dir + its `pending-XXX` parent.\n await rm(key.dir, { recursive: true, force: true });\n const pendingParent = resolvePath(key.dir, '..');\n await rm(pendingParent, { recursive: true, force: true });\n\n // 8. Wait for sshd to accept the new key.\n const up = await waitForSsh(buildSshTarget(state, vpsIp), PROVISION_SSH_DEADLINE_MS);\n if (!up) {\n throw new Error(`hetzner: ssh on ${vpsIp} did not come up within ${String(PROVISION_SSH_DEADLINE_MS / 1000)}s`);\n }\n\n // 9. Open ControlMaster.\n await ensureTunnel(sandboxId, state, vpsIp);\n progress('ssh up; ControlMaster open');\n\n // 10. Push renewable agent credentials (.credentials.json for claude,\n // auth.json for codex/opencode) into /home/vscode/.agentbox-creds/<agent>/\n // via scp. Hetzner has no shared-volume primitive, so we can't reuse the\n // Daytona path (`seedAgentVolumesIfFresh` returns empty mounts for\n // backends without `ensureVolume`). The symlinks baked into the snapshot\n // by install-box.sh route ~/.claude/.credentials.json etc. through to\n // this dir, so the agents find their tokens at the expected paths.\n // Best-effort: a failure here means the in-box agent falls back to\n // interactive login (the current `claude --provider hetzner` failure\n // mode), so we log + continue rather than aborting create.\n const liveTarget = buildSshTarget(state, vpsIp, tunnels.controlPath(sandboxId));\n try {\n await pushHetznerAgentCredentials(liveTarget, onLog);\n } catch (credErr) {\n onLog(\n `hetzner: WARN — agent credential push failed (${credErr instanceof Error ? credErr.message : String(credErr)}); ` +\n `in-box claude/codex/opencode will prompt for interactive login`,\n );\n }\n\n return { sandboxId };\n } catch (err) {\n // Cleanup on failure: server + firewall + temp ssh dir.\n if (serverId !== null) {\n progress(`cleanup — deleting server ${String(serverId)} after provision failure`);\n try {\n await c.deleteServer(serverId);\n } catch (cleanupErr) {\n onLog(\n `hetzner: WARN — failed to delete server ${String(serverId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n if (firewallId !== null) {\n try {\n await deletePerBoxFirewall(c, firewallId);\n } catch {\n // best-effort\n }\n }\n // Clean the temp ssh dir (or its renamed location if we got that far).\n try {\n if (existsSync(key.dir)) await rm(key.dir, { recursive: true, force: true });\n if (serverId !== null) {\n const finalDir = perBoxDir(String(serverId));\n if (existsSync(finalDir)) await rm(finalDir, { recursive: true, force: true });\n }\n } catch {\n // best-effort\n }\n throw err;\n }\n },\n\n async get(sandboxId: string): Promise<CloudHandle | null> {\n const id = Number.parseInt(sandboxId, 10);\n if (!Number.isFinite(id)) return null;\n const server = await client().getServer(id);\n return server ? { sandboxId } : null;\n },\n\n async list(): Promise<CloudSandboxSummary[]> {\n const servers = await client().listServers({ label_selector: 'agentbox.managed=true' });\n return servers.map((s) => ({\n sandboxId: String(s.id),\n name: s.labels['agentbox.box'] ?? s.name,\n createdAt: s.created,\n state: mapState(s.status),\n }));\n },\n\n async start(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n await client().powerOn(id);\n // The API reports `running` ~10-30s before sshd actually accepts\n // connections. Callers (the provider's `reEnsureCloudBox` relaunch) SSH-exec\n // immediately after start()/resume(), so wait for the API state AND for ssh\n // to be ready — otherwise the first exec hits `Connection refused` and the\n // daemons never get relaunched. Mirrors the provision flow's waitForSsh.\n await pollUntil(\n `server ${h.sandboxId} running`,\n async () => {\n const s = await client().getServer(id);\n return s?.status === 'running' ? s : null;\n },\n { deadlineMs: ACTION_DEADLINE_MS, intervalMs: 2_000, maxIntervalMs: 8_000 },\n );\n const server = await getServerStrict(id);\n const vpsIp = server.public_net.ipv4?.ip;\n if (!vpsIp) {\n throw new Error(`hetzner: server ${h.sandboxId} has no IPv4 address after start`);\n }\n const state = await ensurePerBoxState(h.sandboxId);\n const up = await waitForSsh(buildSshTarget(state, vpsIp), PROVISION_SSH_DEADLINE_MS);\n if (!up) {\n throw new Error(\n `hetzner: ssh on ${vpsIp} did not come up within ${String(PROVISION_SSH_DEADLINE_MS / 1000)}s after start`,\n );\n }\n },\n\n async stop(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n // Try graceful shutdown first; fall back to power-off after a short wait.\n try {\n await client().shutdown(id);\n await pollUntil(\n `server ${h.sandboxId} off`,\n async () => {\n const s = await client().getServer(id);\n return s?.status === 'off' ? s : null;\n },\n { deadlineMs: 60_000, intervalMs: 2_000, maxIntervalMs: 8_000 },\n );\n } catch {\n await client().powerOff(id);\n }\n await tunnels.close(h.sandboxId);\n },\n\n async pause(h: CloudHandle): Promise<void> {\n // Hetzner has no archive primitive. Pause ≡ stop.\n await this.stop(h);\n },\n\n async resume(h: CloudHandle): Promise<void> {\n await this.start(h);\n },\n\n async destroy(h: CloudHandle): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n await tunnels.close(h.sandboxId);\n // Discover the per-box firewall via labels so we don't need to\n // round-trip through the server's `firewalls[]` (which is absent from\n // our typed slice anyway).\n const c = client();\n let firewallId: number | undefined;\n try {\n const server = await c.getServer(id);\n firewallId = server\n ? Number.parseInt(server.labels['agentbox.firewall'] ?? '', 10)\n : undefined;\n } catch {\n // ignore — we'll still try to delete the server\n }\n try {\n await c.deleteServer(id);\n } catch (err) {\n if (!(err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found'))) {\n throw err;\n }\n }\n if (firewallId && Number.isFinite(firewallId)) {\n await deletePerBoxFirewall(c, firewallId);\n }\n // Clean the per-box ssh dir.\n const dir = perBoxDir(h.sandboxId);\n try {\n await rm(dir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n },\n\n async state(h: CloudHandle): Promise<CloudState> {\n const id = Number.parseInt(h.sandboxId, 10);\n const s = await client().getServer(id);\n return s ? mapState(s.status) : 'missing';\n },\n\n async exec(h, cmd, opts): Promise<CloudExecResult> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n bashScript(opts?.cwd ? `cd ${shellQuote(opts.cwd)} && ${cmd}` : cmd),\n ];\n const res = await execa('ssh', argv, {\n reject: false,\n timeout: opts?.attemptTimeoutMs ?? 120_000,\n env: { ...process.env, ...(opts?.env ?? {}) },\n });\n return {\n exitCode: typeof res.exitCode === 'number' ? res.exitCode : 1,\n stdout: typeof res.stdout === 'string' ? res.stdout : '',\n stderr: typeof res.stderr === 'string' ? res.stderr : '',\n };\n },\n\n async uploadFile(h, localPath, remotePath): Promise<void> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n localPath,\n `${target.user}@${target.host}:${remotePath}`,\n ];\n const res = await execa('scp', argv, { reject: false, timeout: 300_000 });\n if (res.exitCode !== 0) {\n throw new Error(`hetzner: scp upload failed (exit ${String(res.exitCode)}): ${res.stderr || ''}`);\n }\n },\n\n async downloadFile(h, remotePath, localPath): Promise<void> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}:${remotePath}`,\n localPath,\n ];\n const res = await execa('scp', argv, { reject: false, timeout: 300_000 });\n if (res.exitCode !== 0) {\n throw new Error(`hetzner: scp download failed (exit ${String(res.exitCode)}): ${res.stderr || ''}`);\n }\n },\n\n async listFiles(h, remoteDir): Promise<CloudFileEntry[]> {\n const res = await this.exec(\n h,\n // -L for nicer dir-detection on symlinks; `--printf` is non-portable so\n // use a small awk wrap that prints `<name>\\t<d|f>` per entry.\n `find ${shellQuote(remoteDir)} -mindepth 1 -maxdepth 1 -printf '%f\\\\t%y\\\\n'`,\n );\n if (res.exitCode !== 0) return [];\n return res.stdout\n .split(/\\r?\\n/)\n .filter((line) => line.length > 0)\n .map((line) => {\n const [name, kind] = line.split('\\t');\n return { name: name ?? line, isDir: kind === 'd' };\n });\n },\n\n async previewUrl(h, port): Promise<CloudPreviewUrl> {\n const { state, vpsIp } = await ensureLiveTarget(h.sandboxId);\n void state;\n void vpsIp;\n const localPort = await tunnels.forward(h.sandboxId, port);\n // Plain loopback URL — no preview-token here (SSH local forward is\n // already auth-gated by the tunnel itself). The cloud-provider layer\n // adds the Portless alias for symmetric `<box-name>.localhost` URLs.\n return { url: `http://127.0.0.1:${String(localPort)}` };\n },\n\n async signedPreviewUrl(h, port, _ttl): Promise<CloudPreviewUrl> {\n // SSH tunnels have no signed-URL primitive — they're already loopback-\n // only and the cloud-provider layer's bridge-token enforces auth. The\n // signed form is functionally equivalent to the unsigned one here.\n void _ttl;\n return this.previewUrl(h, port);\n },\n\n async refreshPreviewUrl(h, port): Promise<CloudPreviewUrl> {\n // Tear down the (likely dead) ControlMaster + every cached `-L` forward\n // for this box and re-open from scratch. Called by the host\n // CloudBoxPoller when ECONNREFUSED on the local port shows that the\n // master died (host sleep/wake, transient network blip). Without this\n // the poller would back off forever against a stale localPort.\n const { state, vpsIp } = await ensureLiveTarget(h.sandboxId);\n void state;\n void vpsIp;\n await tunnels.refresh({\n boxId: h.sandboxId,\n vpsHost: vpsIp,\n identity: state.identity,\n });\n const localPort = await tunnels.forward(h.sandboxId, port);\n return { url: `http://127.0.0.1:${String(localPort)}` };\n },\n\n async startInBoxPortless(h, opts): Promise<void> {\n // Bring up a `portless` proxy *inside the VPS* mirroring the host's\n // mode so `<boxName>.localhost:<P>` resolves to the same content on\n // both sides. The `portless` CLI is baked into the base snapshot by\n // install-box.sh:316. Idempotent — `portless proxy start` exits 0 if a\n // proxy is already running on the port. Best-effort: a failure here\n // just means the in-box symmetric URL won't resolve; the host URL\n // still works.\n //\n // Run as root (vscode has NOPASSWD sudo): portless's :443/:80 TLS\n // proxy self-elevates to root anyway, and its state lands in\n // /root/.portless. A subsequent `portless alias` from vscode would\n // write to /home/vscode/.portless and the proxy wouldn't see it —\n // the two state dirs are disjoint. Using sudo for both keeps them\n // pointed at the same `/root/.portless`.\n const tlsFlag = opts.tls ? '' : '--no-tls';\n const startCmd = `sudo portless proxy start ${tlsFlag} -p ${String(opts.proxyPort)}`.replace(/\\s+/g, ' ');\n const aliasCmd = `sudo portless alias ${shellQuote(opts.boxName)} ${String(opts.webPort)}`;\n await this.exec(h, `${startCmd}; ${aliasCmd}`);\n },\n\n async attachArgv(h): Promise<string[]> {\n const { target } = await ensureLiveTarget(h.sandboxId);\n // Reuse the ControlMaster via `-S <sock>` — no new auth handshake, no\n // SSH-token mint pressure (unlike Daytona). Callers append `-t '<cmd>'`\n // or similar in the `buildAttach` helper.\n return [\n 'ssh',\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n ];\n },\n\n async createSnapshot(h, name): Promise<void> {\n const id = Number.parseInt(h.sandboxId, 10);\n const c = client();\n const { image } = await withHetznerRetry(\n { method: 'createImage', retryOnAmbiguous: false, attemptTimeoutMs: 120_000 },\n () =>\n c.createImage(id, {\n type: 'snapshot',\n description: name,\n labels: { 'agentbox.role': 'ckpt', 'agentbox.box': h.sandboxId },\n }),\n );\n await pollUntil(\n `image ${String(image.id)} availability`,\n async () => {\n const img = await c.getImage(image.id);\n return img?.status === 'available' ? img : null;\n },\n { deadlineMs: SNAPSHOT_DEADLINE_MS, intervalMs: 3_000, maxIntervalMs: 10_000 },\n );\n },\n\n async deleteSnapshot(name): Promise<void> {\n const c = client();\n const img = await findImageByDescription(c, name);\n if (!img) return;\n try {\n await c.deleteImage(img.id);\n } catch (err) {\n if (err instanceof HetznerApiError && (err.statusCode === 404 || err.code === 'not_found')) return;\n throw err;\n }\n },\n};\n\n/** Exposed for the CLI's `firewall sync` / `show` subcommands. */\nexport { tunnels as _hetznerTunnels };\n\n/**\n * Push the host's renewable agent credentials (.credentials.json for claude,\n * auth.json for codex / opencode) into /home/vscode/.agentbox-creds/<agent>/\n * over the live ControlMaster. The base-snapshot symlinks route the agents'\n * expected paths through to this dir, so the in-box agents find their tokens\n * without prompting for login.\n *\n * Each stage helper returns `tarballPath: null` when there's nothing to push\n * (e.g. the user never logged in to that agent on the host) — we skip silently\n * in that case. Warnings (codex Keychain landmine, etc.) flow through `log`.\n */\nasync function pushHetznerAgentCredentials(\n target: SshTargetArgs,\n log: (line: string) => void,\n): Promise<void> {\n const specs: Array<{\n kind: 'claude' | 'codex' | 'opencode';\n stage: () => Promise<import('@agentbox/sandbox-cloud').StageResult>;\n dest: string;\n }> = [\n { kind: 'claude', stage: stageClaudeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/claude' },\n { kind: 'codex', stage: stageCodexCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/codex' },\n { kind: 'opencode', stage: stageOpencodeCredentialsForUpload, dest: '/home/vscode/.agentbox-creds/opencode' },\n ];\n\n for (const spec of specs) {\n const staged = await spec.stage();\n for (const w of staged.warnings) log(`hetzner: [${spec.kind}-creds] ${w}`);\n try {\n if (!staged.tarballPath) {\n log(`hetzner: ${spec.kind}: no host credentials to push (skipping)`);\n continue;\n }\n const remote = `/tmp/agentbox-${spec.kind}-creds.tar.gz`;\n const argv = [\n ...sshOptArgs(target),\n staged.tarballPath,\n `${target.user}@${target.host}:${remote}`,\n ];\n const scpRes = await execa('scp', argv, { reject: false, timeout: 120_000 });\n if (scpRes.exitCode !== 0) {\n throw new Error(\n `scp ${spec.kind} credentials failed (exit ${String(scpRes.exitCode)}): ${scpRes.stderr ?? ''}`,\n );\n }\n // Extract as vscode into the dest dir (which already exists as a\n // chown'd dir from install-box.sh's credential-pivot step).\n const extract = await execa(\n 'ssh',\n [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n `sudo -u vscode mkdir -p ${spec.dest} && sudo -u vscode tar -xzf ${remote} -C ${spec.dest} --no-same-permissions --no-same-owner -m && rm -f ${remote}`,\n ],\n { reject: false, timeout: 30_000 },\n );\n if (extract.exitCode !== 0) {\n throw new Error(\n `${spec.kind} credential extract failed (exit ${String(extract.exitCode)}): ${extract.stderr ?? ''}`,\n );\n }\n log(`hetzner: ${spec.kind}: credentials pushed`);\n } finally {\n await staged.cleanup();\n }\n }\n}\n","/**\n * Cloud-init `#cloud-config` user-data generators.\n *\n * Two flavors:\n * - **prepare** (`generatePrepareCloudInit`): for the temporary VPS that\n * `prepareHetzner()` boots to bake the base snapshot. Just injects a\n * `root` SSH key — the install script does everything else over ssh.\n * - **box** (`generateBoxCloudInit`): for per-box VPSes provisioned from\n * the base snapshot. Injects the per-box ssh pubkey for `vscode`,\n * optionally writes `/etc/hosts` aliases and `box.env`.\n *\n * The cloud-init format is documented at https://cloudinit.readthedocs.io.\n * We emit it as a small hand-rolled YAML doc — using a full yaml lib would\n * be overkill for the handful of fields we touch (and pulls in the same\n * `yaml` dep `sandbox-cloud` already has, but we keep this package's dep\n * surface minimal).\n */\n\nexport interface PrepareCloudInitOptions {\n /** ed25519/rsa public key string (one line, OpenSSH format). */\n sshPubkey: string;\n}\n\n/**\n * Cloud-init for the temporary prepare VPS. We log in as `root` here (it's a\n * throwaway VPS that lives ~10–15 min) and run `install-box.sh` over ssh.\n * The install script then creates the `vscode` user, installs everything,\n * and writes the sshd hardening drop-in that disables root login — which\n * doesn't take effect until we reload sshd at the end of the script (the\n * orchestrator finishes its scp/ssh dance before that point).\n */\nexport function generatePrepareCloudInit(opts: PrepareCloudInitOptions): string {\n const pubkey = opts.sshPubkey.trim();\n return [\n '#cloud-config',\n '# AgentBox temporary prepare VPS — used by `agentbox prepare --provider hetzner`',\n '# to bake the base snapshot. SSH key is single-use and discarded on VPS destroy.',\n 'disable_root: false',\n 'ssh_pwauth: false',\n // Hetzner's Ubuntu 24.04 stock image enforces a first-login password\n // change for root. With key-based auth that path can't run (no TTY),\n // so sshd refuses with \"Password change required but no TTY available.\"\n // Telling cloud-init to NOT expire passwords + clearing root's expiry\n // via `passwd -d` removes the gate. Belt-and-braces: the chpasswd block\n // covers cloud-init's own users-and-groups run; the runcmd covers the\n // case where the image's pre-baked expiry survives cloud-init.\n 'chpasswd:',\n ' expire: false',\n 'users:',\n ' - name: root',\n ' lock_passwd: false',\n ' ssh_authorized_keys:',\n ` - ${yamlScalar(pubkey)}`,\n 'runcmd:',\n ' - [ passwd, -d, root ]',\n ' - [ chage, -E, \"-1\", -I, \"-1\", -M, \"99999\", root ]',\n ' - [ bash, -lc, \"echo agentbox-prepare-ready\" ]',\n '',\n ].join('\\n');\n}\n\nexport interface BoxCloudInitOptions {\n /** ed25519/rsa public key string (one line, OpenSSH format). */\n sshPubkey: string;\n /**\n * Box name. Used to write a `<boxName>.localhost → 127.0.0.1` /etc/hosts\n * entry so non-browser in-box clients (curl, fetch in Node) can hit the\n * symmetric Portless URL the host browser sees.\n */\n boxName: string;\n /**\n * Lines for `/etc/agentbox/box.env` — set as `KEY=VALUE` pairs. Cloud-init\n * `write_files` writes them as-is; no shell escaping is applied because\n * the file is sourced via `set -a; . /etc/agentbox/box.env; set +a`.\n */\n boxEnv?: Record<string, string>;\n}\n\n/**\n * Cloud-init for per-box VPSes provisioned from the base snapshot. The\n * base snapshot already has the `vscode` user, sshd hardening, agentbox-ctl,\n * etc. — this just injects the per-box key and any per-box config.\n */\nexport function generateBoxCloudInit(opts: BoxCloudInitOptions): string {\n const pubkey = opts.sshPubkey.trim();\n const lines: string[] = [\n '#cloud-config',\n `# AgentBox per-box VPS — box '${opts.boxName}'`,\n 'disable_root: true',\n 'ssh_pwauth: false',\n // Same first-login expiry guard as the prepare cloud-init — keeps\n // Hetzner's Ubuntu hardening from blocking our key-based vscode login.\n 'chpasswd:',\n ' expire: false',\n 'users:',\n ' - name: vscode',\n ' lock_passwd: false',\n ' sudo: ALL=(ALL) NOPASSWD:ALL',\n ' ssh_authorized_keys:',\n ` - ${yamlScalar(pubkey)}`,\n ];\n\n const writeFiles: string[] = [\n ' path: /etc/hosts',\n ' append: true',\n ` content: \"127.0.0.1 ${opts.boxName}.localhost\\\\n\"`,\n ];\n lines.push('write_files:');\n lines.push(' - ' + writeFiles[0]);\n for (let i = 1; i < writeFiles.length; i++) {\n lines.push(' ' + writeFiles[i]);\n }\n\n if (opts.boxEnv && Object.keys(opts.boxEnv).length > 0) {\n const envContent = Object.entries(opts.boxEnv)\n .map(([k, v]) => `${k}=${v}`)\n .join('\\\\n') + '\\\\n';\n lines.push(' - path: /etc/agentbox/box.env');\n lines.push(' permissions: \"0644\"');\n lines.push(` content: \"${envContent}\"`);\n }\n\n lines.push('');\n return lines.join('\\n');\n}\n\n/**\n * Quote a string as a YAML scalar. ssh pubkeys may contain spaces and `+`\n * characters; the safe move is to wrap in double quotes and escape `\"` /\n * `\\` if they appear (they don't for ed25519/rsa). Returns the value\n * already-quoted (so the caller embeds it after the `- ` prefix).\n */\nfunction yamlScalar(value: string): string {\n // No `\"` or `\\` in a valid OpenSSH pubkey, so a bare double-quote wrap is\n // safe; we still guard so a future caller doesn't surprise us.\n if (/[\"\\\\]/.test(value)) {\n return JSON.stringify(value);\n }\n return `\"${value}\"`;\n}\n","/**\n * Polling helpers — wait for a Hetzner Cloud resource to reach a desired\n * state. Reused across the prepare flow + Phase 4 backend lifecycle.\n *\n * Bounded by `deadlineMs`; intervals grow modestly (1s → 2s → 4s, capped)\n * so a fast-completing action is observed quickly without hammering the\n * API for slow ones.\n */\n\nexport interface PollOptions {\n deadlineMs?: number;\n /** Starting interval; doubles per attempt up to `maxIntervalMs`. */\n intervalMs?: number;\n maxIntervalMs?: number;\n /** Optional logger for each poll attempt — useful to stream progress. */\n onPoll?: (line: string) => void;\n}\n\n/**\n * Poll `check()` until it returns a non-null value or `deadlineMs` elapses.\n * Throws on timeout with the supplied `label` in the message.\n */\nexport async function pollUntil<T>(\n label: string,\n check: () => Promise<T | null | undefined>,\n opts: PollOptions = {},\n): Promise<T> {\n const deadline = Date.now() + (opts.deadlineMs ?? 5 * 60_000);\n const max = opts.maxIntervalMs ?? 10_000;\n let interval = opts.intervalMs ?? 1_000;\n let attempt = 0;\n while (true) {\n attempt += 1;\n const out = await check();\n if (out !== null && out !== undefined) return out;\n if (Date.now() >= deadline) {\n throw new Error(`hetzner: timed out waiting for ${label} after ${String(attempt)} attempts`);\n }\n opts.onPoll?.(`${label}: not ready yet (attempt ${String(attempt)}); polling again in ${String(interval)}ms`);\n await new Promise((r) => setTimeout(r, interval));\n interval = Math.min(interval * 2, max);\n }\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 { preparedStatePathFor, readPreparedStateRaw, writePreparedStateRaw } from '@agentbox/sandbox-core';\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 * `agentbox prepare --provider hetzner` — bake the per-org Hetzner base\n * snapshot. Mirrors the user request: \"for `agentbox prepare` (and first\n * time hetzner is used) start a VM and set it up and snapshot as base image\n * (there's no way to start a VPS from an existing dockerfile).\"\n *\n * Flow:\n * 1. Mint an ephemeral SSH keypair under\n * ~/.agentbox/hetzner/prepare-<ts>/.\n * 2. Detect the host's egress IP and create a firewall locked to it,\n * named `agentbox-prepare-<ts>`.\n * 3. Create a temp VPS (Ubuntu 24.04, `cx22` default) with cloud-init\n * injecting the pubkey for `root`.\n * 4. Poll until cloud-init + sshd come up.\n * 5. scp the runtime assets (install script + agentbox-ctl + helpers +\n * baked config files) into /tmp.\n * 6. Run `bash /tmp/agentbox-install.sh` over ssh; stream stdout to the\n * prepare log via the `onLog` callback.\n * 7. `create_image` snapshot of the VPS; poll until `available`.\n * 8. Delete the VPS + firewall.\n * 9. Persist the snapshot id into `~/.agentbox/hetzner-prepared.json`.\n *\n * Failure-mode discipline: each major step is wrapped in try/catch so the\n * temp VPS + firewall are *always* cleaned up on failure (the user must\n * never end up with a forgotten €4/mo VPS due to a prepare error).\n *\n * The user requested noisy logging — every BEGIN/END marker from the\n * install script is forwarded verbatim into the prepare log, plus our own\n * step boundaries from `progress()`.\n */\n\nimport { join } from 'node:path';\nimport type { Provider } from '@agentbox/core';\nimport { computeContextSha256, readCliStamp } from '@agentbox/sandbox-core';\nimport {\n stageClaudeStaticForUpload,\n stageCodexStaticForUpload,\n stageOpencodeStaticForUpload,\n type StageResult,\n} from '@agentbox/sandbox-cloud';\nimport { ensureHetznerCredentials } from './credentials.js';\nimport { detectEgressIp } from './egress-ip.js';\nimport {\n createPerBoxFirewall,\n deletePerBoxFirewall,\n normalizeSourceCidr,\n} from './firewall.js';\nimport { makeHetznerClient } from './client.js';\nimport { generatePrepareCloudInit } from './cloud-init.js';\nimport {\n preparedStatePath,\n readPreparedState,\n writePreparedState,\n} from './prepared-state.js';\nimport { pollUntil } from './poll.js';\nimport {\n findStagedCliRuntimeRoot,\n resolveRuntimeAssets,\n type ResolvedAsset,\n} from './runtime-assets.js';\nimport { mintPrepareKey } from './ssh-key.js';\nimport {\n scpUpload,\n sshExec,\n waitForSsh,\n type SshTargetArgs,\n} from './ssh-cli.js';\n\nexport interface PrepareHetznerOptions {\n name?: string;\n hostWorkspace?: string;\n /** Force re-bake even when `~/.agentbox/hetzner-prepared.json` has a usable base. */\n force?: boolean;\n /** Hetzner location (defaults to `nbg1`). */\n location?: string;\n /** Server type (defaults to `cx22` — 2 vCPU / 4 GB / 40 GB / ~€4/mo). */\n serverType?: string;\n /**\n * Override the firewall source CIDR. Defaults to auto-detected egress IP\n * via `detectEgressIp()` (with `/32` appended). Pass `'0.0.0.0/0'` for\n * the explicit-open opt-in. Passing a bare IP appends `/32` automatically.\n */\n firewallSource?: string;\n /** CLI runtime tree (set by the CLI to its dist neighbor). */\n cliRuntimeRoot?: string;\n /** Repo root for the dev fallback (defaults to `process.cwd()` walk). */\n repoRoot?: string;\n onLog?: (line: string) => void;\n}\n\nexport interface PrepareHetznerResult {\n snapshotName?: string;\n /** Hetzner image id (numeric) — also recorded in hetzner-prepared.json. */\n imageId?: number;\n}\n\n// `cx22` was deprecated by Hetzner in early 2026; `cx23` is the drop-in\n// replacement with the same 2 vCPU / 4 GB / 40 GB shape on x86. Users can\n// still override via `prepareHetzner({serverType: ...})`.\nconst TEMP_SERVER_TYPE_DEFAULT = 'cx23';\nconst TEMP_SERVER_LOCATION_DEFAULT = 'nbg1';\nconst PREPARE_SSH_DEADLINE_MS = 5 * 60_000;\nconst INSTALL_SCRIPT_TIMEOUT_MS = 30 * 60_000;\nconst SNAPSHOT_DEADLINE_MS = 20 * 60_000;\n\n/**\n * Bake the per-org Hetzner base snapshot. Resolves only after the image is\n * `available` and the temp VPS + firewall are gone. Persists `{base.imageId,\n * base.description, base.createdAt, base.installScriptSha256}` into\n * `~/.agentbox/hetzner-prepared.json`.\n */\nexport async function prepareHetzner(\n opts: PrepareHetznerOptions = {},\n): Promise<PrepareHetznerResult> {\n await ensureHetznerCredentials();\n const client = makeHetznerClient();\n const log = opts.onLog ?? (() => {});\n const progress = (step: string) => log(`prepare-hetzner: ${step}`);\n\n // Skip-fast: if a base snapshot is already recorded *and* its image is\n // still on Hetzner *and* the build-context fingerprint hasn't changed *and*\n // --force was not passed, return the existing record.\n const existingState = readPreparedState();\n // Prefer an explicit override; otherwise auto-detect the published-CLI\n // staged runtime tree by inspecting where this module was loaded from.\n const assets = resolveRuntimeAssets({\n cliRuntimeRoot: opts.cliRuntimeRoot ?? findStagedCliRuntimeRoot(),\n repoRoot: opts.repoRoot,\n });\n // Fingerprint = hash of every asset we scp into the prepare VPS. Keyed on\n // logical name (stable across staged-vs-monorepo layouts) so two CLIs with\n // the same staged tree produce the same hash.\n const contextSha = await computeContextSha256(\n assets.map((a) => ({ rel: a.name, abs: a.localPath })),\n );\n\n if (!opts.force && existingState.base) {\n const remote = await client\n .getImage(existingState.base.imageId)\n .catch(() => null);\n if (remote && existingState.base.contextSha256 === contextSha) {\n progress(\n `base snapshot ${String(existingState.base.imageId)} already exists (fingerprint ${contextSha.slice(0, 12)} matches); skipping rebuild (pass --force to override)`,\n );\n return {\n snapshotName: existingState.base.description,\n imageId: existingState.base.imageId,\n };\n }\n if (!remote) {\n progress(`recorded base snapshot ${String(existingState.base.imageId)} is gone on Hetzner; rebuilding`);\n } else {\n progress(\n `build context changed (was ${existingState.base.contextSha256?.slice(0, 12) ?? '<none>'}, now ${contextSha.slice(0, 12)}); rebuilding base snapshot`,\n );\n }\n }\n\n // 1. Mint ephemeral key + detect egress IP in parallel.\n progress('minting ephemeral ssh key');\n const key = await mintPrepareKey();\n let firewallId: number | null = null;\n let serverId: number | null = null;\n try {\n progress('detecting host egress IP');\n const source = opts.firewallSource\n ? normalizeSourceCidr(opts.firewallSource)\n : `${await detectEgressIp({ onLog: log })}/32`;\n\n // 2. Create per-prepare firewall.\n const stamp = Date.now().toString(36);\n const firewallName = `agentbox-prepare-${stamp}`;\n progress(`creating firewall ${firewallName} (source ${source})`);\n const firewall = await createPerBoxFirewall(client, {\n name: firewallName,\n sourceCidr: source,\n labels: { 'agentbox.role': 'prepare' },\n });\n firewallId = firewall.id;\n\n // 3. Create temp VPS.\n const serverName = `agentbox-prepare-${stamp}`;\n const cloudInit = generatePrepareCloudInit({ sshPubkey: key.publicKey });\n progress(`creating temp VPS ${serverName} (${opts.serverType ?? TEMP_SERVER_TYPE_DEFAULT} / ${opts.location ?? TEMP_SERVER_LOCATION_DEFAULT})`);\n const created = await client.createServer({\n name: serverName,\n server_type: opts.serverType ?? TEMP_SERVER_TYPE_DEFAULT,\n image: 'ubuntu-24.04',\n location: opts.location ?? TEMP_SERVER_LOCATION_DEFAULT,\n user_data: cloudInit,\n firewalls: [{ firewall: firewall.id }],\n labels: { 'agentbox.managed': 'true', 'agentbox.role': 'prepare' },\n start_after_create: true,\n });\n serverId = created.server.id;\n const ip = created.server.public_net.ipv4?.ip;\n if (!ip) {\n throw new Error('hetzner: temp VPS came up without an IPv4 address');\n }\n\n // 4. Wait for sshd.\n progress(`waiting for ssh on ${ip} (deadline ${String(PREPARE_SSH_DEADLINE_MS / 1000)}s)`);\n const sshTarget: SshTargetArgs = {\n host: ip,\n user: 'root',\n identity: key.privatePath,\n knownHosts: join(key.dir, 'known_hosts'),\n };\n const up = await waitForSsh(sshTarget, PREPARE_SSH_DEADLINE_MS);\n if (!up) {\n throw new Error(`hetzner: ssh on ${ip} did not come up within ${String(PREPARE_SSH_DEADLINE_MS / 1000)}s`);\n }\n progress('ssh up — scp\\'ing runtime assets');\n\n // 5. scp every asset into /tmp/ **sequentially**. Parallel uploads\n // through 10 fresh ssh connections trip sshd's MaxStartups (10:30:100\n // default) on a freshly-booted VPS — surviving connections look fine\n // but some randomly write 0 bytes to the destination. The sequential\n // form is plenty fast (each file is small, total ~1MB).\n for (const asset of assets) {\n const remote = `/tmp/${asset.remoteBasename}`;\n log(`prepare-hetzner: scp ${asset.name} -> ${remote}`);\n await scpUpload(sshTarget, asset.localPath, remote);\n if (asset.remoteMode !== undefined) {\n const modeOctal = asset.remoteMode.toString(8);\n await sshExec(sshTarget, `chmod ${modeOctal} ${remote}`);\n }\n }\n\n // 6. Run the install script. We trace via `bash -x` and tee the full\n // output to /var/log/agentbox/install.log on the VPS so the trace\n // survives into the snapshot — handy when diagnosing a step that ran\n // (or didn't) deep inside the install. Stream stdout/stderr through\n // `onLog` so `prepare.log` shows the BEGIN/END markers in real time.\n // `set -o pipefail` so the pipe's exit code is bash's, not tee's.\n progress('running install-box.sh on temp VPS (this takes ~5-15 min)');\n const installRes = await sshExec(\n sshTarget,\n `sudo mkdir -p /var/log/agentbox && set -o pipefail && bash -x /tmp/agentbox-install.sh 2>&1 | sudo tee /var/log/agentbox/install.log`,\n {\n timeoutMs: INSTALL_SCRIPT_TIMEOUT_MS,\n onLine: (line) => log(`[install] ${line}`),\n },\n );\n if (installRes.exitCode !== 0) {\n throw new Error(\n `install-box.sh failed on temp VPS (exit ${String(installRes.exitCode)})\\n` +\n `Last stderr: ${installRes.stderr.slice(-500) || '(empty)'}\\n` +\n `The full trace was preserved at /var/log/agentbox/install.log inside any box made from the resulting snapshot.`,\n );\n }\n progress('install script complete');\n\n // 6b. Stage host agent static config (~/.claude plugins/skills/settings/\n // _claude.json, ~/.codex config + prompts, ~/.local/share/opencode), scp\n // each tarball, extract into /home/vscode/ as the `vscode` user. Mirrors\n // the Daytona bake step (`Image.addLocalFile` + `Image.runCommands`),\n // adapted for our ssh+scp model. Without this, the in-box claude/codex/\n // opencode boot with no plugins, no skills, no settings, and prompt the\n // user to log in fresh on every box.\n progress('staging host agent static config');\n const stagings: Array<{ kind: 'claude' | 'codex' | 'opencode'; tar: StageResult; dest: string }> = [];\n try {\n const claudeTar = await stageClaudeStaticForUpload({ hostWorkspace: opts.hostWorkspace });\n for (const w of claudeTar.warnings) log(`prepare-hetzner: ${w}`);\n if (claudeTar.tarballPath) stagings.push({ kind: 'claude', tar: claudeTar, dest: '/home/vscode/.claude' });\n else await claudeTar.cleanup();\n\n const codexTar = await stageCodexStaticForUpload();\n for (const w of codexTar.warnings) log(`prepare-hetzner: ${w}`);\n if (codexTar.tarballPath) stagings.push({ kind: 'codex', tar: codexTar, dest: '/home/vscode/.codex' });\n else await codexTar.cleanup();\n\n const opencodeTar = await stageOpencodeStaticForUpload();\n for (const w of opencodeTar.warnings) log(`prepare-hetzner: ${w}`);\n if (opencodeTar.tarballPath) stagings.push({ kind: 'opencode', tar: opencodeTar, dest: '/home/vscode/.local/share/opencode' });\n else await opencodeTar.cleanup();\n\n for (const s of stagings) {\n const remote = `/tmp/agentbox-${s.kind}-static.tar.gz`;\n log(`prepare-hetzner: scp ${s.kind} static (${s.tar.tarballPath}) -> ${remote}`);\n await scpUpload(sshTarget, s.tar.tarballPath as string, remote);\n // Extract as vscode so the files land owned by uid 1000. The dir\n // already exists (created by the install script's credential-pivot\n // step) — extract into it, don't replace it.\n const extractCmd =\n `sudo -u vscode mkdir -p ${s.dest} && ` +\n `sudo -u vscode tar -xzf ${remote} -C ${s.dest} --no-same-permissions --no-same-owner -m && ` +\n `rm -f ${remote}`;\n const r = await sshExec(sshTarget, extractCmd, { onLine: (line) => log(`[stage:${s.kind}] ${line}`) });\n if (r.exitCode !== 0) {\n throw new Error(\n `prepare-hetzner: ${s.kind} static extract failed (exit ${String(r.exitCode)}): ${r.stderr.slice(-300)}`,\n );\n }\n progress(`baked ${s.kind} static config into snapshot`);\n }\n } finally {\n for (const s of stagings) await s.tar.cleanup();\n }\n\n // 7. Snapshot.\n const description = opts.name ?? `agentbox-base-${stamp}`;\n progress(`creating snapshot '${description}' from VPS ${String(serverId)}`);\n const snap = await client.createImage(serverId, {\n type: 'snapshot',\n description,\n labels: { 'agentbox.role': 'base', 'agentbox.schema': '1' },\n });\n progress(`snapshot create requested (image id ${String(snap.image.id)}); polling until available`);\n const ready = await pollUntil(\n `image ${String(snap.image.id)} availability`,\n async () => {\n const img = await client.getImage(snap.image.id);\n if (!img) return null;\n if (img.status === 'available') return img;\n return null;\n },\n { deadlineMs: SNAPSHOT_DEADLINE_MS, intervalMs: 3_000, maxIntervalMs: 10_000, onPoll: (l) => log(`prepare-hetzner: ${l}`) },\n );\n\n // 8. Persist before tearing down — if the cleanup fails we still know\n // about the new snapshot.\n progress('persisting hetzner-prepared.json');\n const state = readPreparedState();\n const cliStamp = readCliStamp();\n state.base = {\n imageId: ready.id,\n description: ready.description,\n createdAt: new Date().toISOString(),\n contextSha256: contextSha,\n cliVersion: cliStamp.cliVersion,\n cliCommit: cliStamp.cliCommit,\n };\n writePreparedState(state);\n log(`prepare-hetzner: wrote ${preparedStatePath()}`);\n\n // 9. Cleanup: delete server first (cleanly detaches from firewall),\n // then the firewall.\n progress(`deleting temp VPS ${String(serverId)}`);\n await client.deleteServer(serverId);\n serverId = null;\n progress(`deleting per-prepare firewall ${String(firewallId)}`);\n await deletePerBoxFirewall(client, firewallId);\n firewallId = null;\n\n progress(`prepare complete — base snapshot ${String(ready.id)} (${ready.description})`);\n return { snapshotName: ready.description, imageId: ready.id };\n } catch (err) {\n // Failure cleanup — best-effort. Always try to delete the VPS first\n // (it costs €4/mo if left running). Surface the original error in any\n // case.\n if (serverId !== null) {\n log(`prepare-hetzner: cleanup — deleting temp VPS ${String(serverId)} after failure`);\n try {\n await client.deleteServer(serverId);\n } catch (cleanupErr) {\n log(\n `prepare-hetzner: WARN — failed to delete temp VPS ${String(serverId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n if (firewallId !== null) {\n log(`prepare-hetzner: cleanup — deleting per-prepare firewall ${String(firewallId)} after failure`);\n try {\n await deletePerBoxFirewall(client, firewallId);\n } catch (cleanupErr) {\n log(\n `prepare-hetzner: WARN — failed to delete firewall ${String(firewallId)}; check the Hetzner dashboard manually. ${\n cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)\n }`,\n );\n }\n }\n throw err;\n } finally {\n await key.cleanup();\n }\n}\n\n/**\n * Provider-level binding used by the CLI's `prepare` command. Matches the\n * shape of `daytonaProvider.prepare`.\n */\nexport const prepareHetznerProvider: NonNullable<Provider['prepare']> = (req) =>\n prepareHetzner({\n name: req.name,\n hostWorkspace: req.hostWorkspace ?? process.cwd(),\n force: req.force,\n onLog: req.onLog,\n });\n\n/**\n * First-use gate. If no base snapshot is recorded in\n * `~/.agentbox/hetzner-prepared.json`, throws an actionable error pointing\n * at `agentbox prepare --provider hetzner`.\n *\n * This is called by `backend.provision()` (lazily, from Phase 4 onward) so\n * `agentbox prepare --provider hetzner` itself can run without tripping\n * the gate.\n *\n * Phase 4 will widen this to also re-check the image is still on Hetzner\n * (404 → retrigger prepare prompt). For now it just gates on the local\n * record so the build is honest about the failure mode.\n */\nexport async function ensureHetznerBaseSnapshot(): Promise<void> {\n const state = readPreparedState();\n if (state.base !== undefined) return;\n throw new Error(\n 'no Hetzner base snapshot found.\\n' +\n 'Run `agentbox prepare --provider hetzner` first (Hetzner cannot build images from a Dockerfile,\\n' +\n 'so the base snapshot is a one-time prerequisite for cloud boxes on this backend).',\n );\n}\n\nexport type { ResolvedAsset };\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: '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 '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 '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 * Per-box (and per-prepare-run) SSH key minting.\n *\n * AgentBox mints a fresh ed25519 keypair per box at provision time. The\n * private key never leaves the host; the public key is shipped to the VPS\n * via cloud-init `users:` (NOT the Hetzner SSH-keys-import API, which\n * would make the same pubkey available to attach to other VPSes the user\n * provisions — see the plan's §\"Key & key-lifecycle hygiene\").\n *\n * Storage layout (per the plan):\n * ~/.agentbox/boxes/<box-id>/ssh/\n * id_ed25519 (private, 0600)\n * id_ed25519.pub (public, 0644)\n * known_hosts (per-box, populated post-first-connect)\n * control.sock (ssh ControlMaster socket — created at runtime)\n *\n * For the temp prepare VPS we use a parallel path:\n * ~/.agentbox/hetzner/prepare-<timestamp>/\n * deleted after the snapshot completes.\n */\n\nimport { mkdir, readFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { execa } from 'execa';\n\nexport interface MintedSshKey {\n /** Directory holding the key files. */\n dir: string;\n /** Absolute path to the private key. */\n privatePath: string;\n /** Absolute path to the public key. */\n publicPath: string;\n /** Public key contents (one OpenSSH-format line). */\n publicKey: string;\n}\n\n/**\n * Mint a fresh ed25519 keypair into `targetDir/id_ed25519` (+ `.pub`). The\n * directory is created if missing. Throws if the private key already exists\n * — callers handle reuse explicitly (we don't silently overwrite).\n *\n * `comment` is embedded in the public key (the `agentbox/<box-id>` tag) so\n * the key is identifiable in `~/.ssh/authorized_keys` on a forensic look.\n */\nexport async function mintSshKey(targetDir: string, comment: string): Promise<MintedSshKey> {\n const dir = resolve(targetDir);\n const priv = join(dir, 'id_ed25519');\n const pub = `${priv}.pub`;\n await mkdir(dir, { recursive: true, mode: 0o700 });\n\n // `ssh-keygen -N ''` for no passphrase; `-q` to suppress the random art.\n // Caller is responsible for ensuring the dir is fresh — if `priv` already\n // exists, ssh-keygen would prompt to overwrite (and we don't pipe stdin so\n // it would hang). `mintPrepareKey` creates a fresh dir per call; the\n // per-box minter in backend.ts uses a fresh stamp directory too.\n await execa(\n 'ssh-keygen',\n ['-t', 'ed25519', '-N', '', '-C', comment, '-f', priv, '-q'],\n { stdio: 'pipe' },\n );\n\n const publicKey = (await readFile(pub, 'utf8')).trim();\n return { dir, privatePath: priv, publicPath: pub, publicKey };\n}\n\n/**\n * Mint a temporary keypair for the prepare orchestrator. Returns the same\n * shape as `mintSshKey` plus a `cleanup()` that rm -rf's the directory.\n * The caller is expected to call `cleanup()` in a `finally` block.\n */\nexport async function mintPrepareKey(): Promise<MintedSshKey & { cleanup: () => Promise<void> }> {\n const root = resolve(homedirOrCwd(), '.agentbox', 'hetzner', `prepare-${Date.now().toString(36)}`);\n const key = await mintSshKey(root, `agentbox-prepare-${new Date().toISOString().replace(/[:.]/g, '-')}`);\n return {\n ...key,\n cleanup: async () => {\n try {\n const { rm } = await import('node:fs/promises');\n await rm(dirname(key.privatePath), { recursive: true, force: true });\n } catch {\n // best-effort\n }\n },\n };\n}\n\nfunction homedirOrCwd(): string {\n try {\n // Lazy require so this module is import-safe even if `os` is shimmed\n // away in some weird bundle environment.\n return process.env.HOME ?? process.cwd();\n } catch {\n return process.cwd();\n }\n}\n","/**\n * Thin wrappers around the system `ssh` / `scp` binaries used during\n * `prepareHetzner()` (and Phase 4's SshTunnelManager).\n *\n * These helpers compose flags that suppress the usual interactive prompts\n * (`StrictHostKeyChecking=accept-new`, `UserKnownHostsFile=<per-box file>`)\n * and bind to the per-key/per-box paths.\n *\n * We shell out to the system OpenSSH rather than a JS SSH library because:\n * - The provider matrix already depends on system `ssh` for `agentbox\n * shell` against Docker boxes (`docker exec` doesn't go through ssh,\n * but `agentbox open`'s sshfs flow does).\n * - OpenSSH's ControlMaster + dynamic port forwarding is exactly the\n * primitive Phase 4 needs, and is hard to replicate in pure JS.\n * - No native-dep crutch and no surprise binary sizes.\n */\n\nimport { execa, type ResultPromise } from 'execa';\n\nexport interface SshTargetArgs {\n /** VPS IP (or DNS name) — passed to ssh as user@host. */\n host: string;\n /** Remote user. Hetzner stock images come up with `root`; baked snapshots use `vscode`. */\n user: string;\n /** Absolute path to the per-box/per-prepare private key. */\n identity: string;\n /** Absolute path to the per-box known_hosts file. */\n knownHosts: string;\n /** Optional ControlMaster socket (set during Phase 4 SshTunnelManager). */\n controlPath?: string;\n /** Extra `-o key=value` settings (e.g. ConnectTimeout for prepare polling). */\n options?: Record<string, string>;\n}\n\n/** Compose the `-o … -i … -o UserKnownHostsFile=…` flags for ssh/scp. */\nexport function sshOptArgs(target: SshTargetArgs): string[] {\n const out: string[] = [\n '-i', target.identity,\n '-o', 'StrictHostKeyChecking=accept-new',\n '-o', `UserKnownHostsFile=${target.knownHosts}`,\n '-o', 'GlobalKnownHostsFile=/dev/null',\n '-o', 'BatchMode=yes',\n '-o', 'LogLevel=ERROR',\n ];\n if (target.controlPath) {\n out.push('-o', `ControlPath=${target.controlPath}`);\n }\n for (const [k, v] of Object.entries(target.options ?? {})) {\n out.push('-o', `${k}=${v}`);\n }\n return out;\n}\n\nexport interface SshExecOptions {\n /** Stream stdout/stderr line-by-line into this callback. */\n onLine?: (line: string) => void;\n /** Pipe extra env into the remote shell. */\n env?: Record<string, string>;\n /** Per-command wall-clock cap (ms). */\n timeoutMs?: number;\n}\n\nexport interface SshExecResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/**\n * Run a one-shot command on the target VPS over ssh. Returns the exit code\n * + captured stdout/stderr; non-zero exits do NOT throw — callers decide\n * what to do with them.\n */\nexport async function sshExec(\n target: SshTargetArgs,\n remoteCmd: string,\n opts: SshExecOptions = {},\n): Promise<SshExecResult> {\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}`,\n remoteCmd,\n ];\n const child = execa('ssh', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n env: { ...process.env, ...opts.env },\n stdio: opts.onLine ? ['ignore', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],\n }) as ResultPromise;\n\n if (opts.onLine) {\n const handle = (chunk: Buffer | string) => {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n for (const line of text.split(/\\r?\\n/)) {\n if (line.length > 0) opts.onLine?.(line);\n }\n };\n child.stdout?.on('data', handle);\n child.stderr?.on('data', handle);\n }\n\n const res = await child;\n return {\n exitCode: typeof res.exitCode === 'number' ? res.exitCode : 1,\n stdout: typeof res.stdout === 'string' ? res.stdout : '',\n stderr: typeof res.stderr === 'string' ? res.stderr : '',\n };\n}\n\n/** Copy a local file to the target VPS via `scp`. Throws on non-zero exit. */\nexport async function scpUpload(\n target: SshTargetArgs,\n localPath: string,\n remotePath: string,\n opts: SshExecOptions = {},\n): Promise<void> {\n const argv = [\n ...sshOptArgs(target),\n localPath,\n `${target.user}@${target.host}:${remotePath}`,\n ];\n const res = await execa('scp', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n });\n if (res.exitCode !== 0) {\n throw new Error(\n `scp upload failed (exit ${String(res.exitCode)}): ${localPath} → ${remotePath}\\n${res.stderr ?? ''}`,\n );\n }\n}\n\n/** Copy a remote file to the host via `scp`. Throws on non-zero exit. */\nexport async function scpDownload(\n target: SshTargetArgs,\n remotePath: string,\n localPath: string,\n opts: SshExecOptions = {},\n): Promise<void> {\n const argv = [\n ...sshOptArgs(target),\n `${target.user}@${target.host}:${remotePath}`,\n localPath,\n ];\n const res = await execa('scp', argv, {\n reject: false,\n timeout: opts.timeoutMs,\n });\n if (res.exitCode !== 0) {\n throw new Error(\n `scp download failed (exit ${String(res.exitCode)}): ${remotePath} → ${localPath}\\n${res.stderr ?? ''}`,\n );\n }\n}\n\n/**\n * Poll the target until ssh succeeds (or `deadlineMs` elapses). Used by the\n * prepare orchestrator after `createServer` to wait for cloud-init to bring\n * sshd up. Returns true on success, false on timeout — callers throw with\n * appropriate context.\n */\nexport async function waitForSsh(\n target: SshTargetArgs,\n deadlineMs: number,\n intervalMs = 5_000,\n): Promise<boolean> {\n const stop = Date.now() + deadlineMs;\n while (Date.now() < stop) {\n const res = await sshExec(\n { ...target, options: { ...target.options, ConnectTimeout: '5' } },\n 'true',\n { timeoutMs: 10_000 },\n );\n if (res.exitCode === 0) return true;\n await new Promise((r) => setTimeout(r, intervalMs));\n }\n return false;\n}\n","/**\n * `SshTunnelManager` — one persistent `ssh` ControlMaster per box, plus\n * dynamic `-L` port forwards minted on demand.\n *\n * This is the load-bearing piece that makes the Hetzner provider's preview-URL\n * + exec/file I/O paths work without paying the SSH handshake on every call.\n * Design rationale lives in `~/.claude/plans/how-to-safely-create-parallel-pebble.md`\n * §\"SSH tunnel design (host side)\" and is recapped here.\n *\n * Layout:\n *\n * ~/.agentbox/boxes/<box-id>/ssh/\n * id_ed25519 per-box private key (0600)\n * id_ed25519.pub per-box public key (0644)\n * known_hosts per-box host-key pinning\n * control.sock ssh ControlMaster socket (created at runtime, removed by `-O exit`)\n *\n * Lifecycle:\n *\n * - open() → spawn `ssh -fNT -M -S control.sock -i id_ed25519 vscode@vps` once per box.\n * - forward() → `ssh -O forward -L 127.0.0.1:<localPort>:127.0.0.1:<remotePort> -S control.sock dummy`.\n * Picks a free local port from the ephemeral range.\n * Idempotent per (boxId, remotePort) — returns the cached\n * localPort on a repeated call.\n * - unforward() → `ssh -O cancel -L 127.0.0.1:<localPort>:127.0.0.1:<remotePort> -S control.sock dummy`.\n * - close() → `ssh -O exit -S control.sock dummy`; removes the socket.\n *\n * Thread-safety: a single AgentBox process owns one manager per CLI\n * invocation. Concurrent `forward()` calls for the same (boxId, remotePort)\n * race-resolve to the same localPort because the cache is checked first and\n * `ssh -O forward` is idempotent on the SSH side.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, rm } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { homedir } from 'node:os';\nimport { dirname, join, resolve } from 'node:path';\nimport { execa } from 'execa';\n\nconst HOST = '127.0.0.1';\nconst EPHEMERAL_MIN = 49152;\nconst EPHEMERAL_MAX = 65535;\n\nexport interface SshTunnelOpenOptions {\n boxId: string;\n /** VPS IP/host. */\n vpsHost: string;\n /** Remote user — `vscode` for boxes baked from `install-box.sh`. */\n vpsUser?: string;\n /** Absolute path to the private key. */\n identity: string;\n /** Override `~/.agentbox/boxes/<box-id>/ssh/` (tests inject this). */\n boxSshDir?: string;\n /** Override the connect-timeout (default 10s — fast-fail on a dropped firewall rule). */\n connectTimeoutSeconds?: number;\n}\n\nexport interface PortForward {\n localPort: number;\n remotePort: number;\n}\n\ninterface BoxTunnel {\n controlPath: string;\n vpsHost: string;\n vpsUser: string;\n identity: string;\n boxSshDir: string;\n // remotePort → localPort. Used so repeated `forward()` calls for the same\n // remote port return the same local port without re-asking sshd.\n forwards: Map<number, number>;\n}\n\nexport class SshTunnelManager {\n private boxes = new Map<string, BoxTunnel>();\n\n /**\n * Open the ControlMaster for `boxId`. Idempotent: if a master is already\n * up for this box (socket exists + responsive), no-op. Otherwise spawn a\n * fresh `ssh -fNT -M` and wait for the socket to appear.\n */\n async open(opts: SshTunnelOpenOptions): Promise<void> {\n const boxSshDir = opts.boxSshDir ?? defaultBoxSshDir(opts.boxId);\n await mkdir(boxSshDir, { recursive: true, mode: 0o700 });\n const controlPath = join(boxSshDir, 'control.sock');\n const knownHosts = join(boxSshDir, 'known_hosts');\n const user = opts.vpsUser ?? 'vscode';\n const tunnel: BoxTunnel = {\n controlPath,\n vpsHost: opts.vpsHost,\n vpsUser: user,\n identity: opts.identity,\n boxSshDir,\n forwards: new Map(),\n };\n\n // Reuse an existing live master if the socket already responds.\n if (existsSync(controlPath) && (await this.isAlive(controlPath))) {\n this.boxes.set(opts.boxId, tunnel);\n return;\n }\n // Stale socket from a prior crashed process — remove before re-opening.\n if (existsSync(controlPath)) {\n await rm(controlPath, { force: true });\n }\n\n const connectTimeout = opts.connectTimeoutSeconds ?? 10;\n const argv = [\n '-fNT',\n '-M',\n '-S', controlPath,\n '-i', opts.identity,\n '-o', 'StrictHostKeyChecking=accept-new',\n '-o', `UserKnownHostsFile=${knownHosts}`,\n '-o', 'GlobalKnownHostsFile=/dev/null',\n '-o', 'BatchMode=yes',\n '-o', 'LogLevel=ERROR',\n '-o', 'ExitOnForwardFailure=yes',\n '-o', 'ServerAliveInterval=30',\n '-o', 'ServerAliveCountMax=3',\n '-o', `ConnectTimeout=${String(connectTimeout)}`,\n `${user}@${opts.vpsHost}`,\n ];\n const res = await execa('ssh', argv, { reject: false });\n if (res.exitCode !== 0 || !existsSync(controlPath)) {\n throw new Error(\n `ssh ControlMaster failed for ${opts.boxId} (exit ${String(res.exitCode)}): ${res.stderr || res.stdout || '(no output)'}`,\n );\n }\n this.boxes.set(opts.boxId, tunnel);\n }\n\n /**\n * Mint (or fetch the cached) `127.0.0.1:<localPort> → vps:127.0.0.1:<remotePort>`\n * forward. Returns the local port. Idempotent per (boxId, remotePort).\n *\n * The cached entry is only returned when the underlying ControlMaster is\n * still alive — without that check we'd happily hand back a localPort that\n * stopped listening when the master died (e.g. transient network blip,\n * host sleep/wake). When the master is dead we drop ALL cached forwards\n * for this box (they all share one tunnel) and re-mint from scratch.\n */\n async forward(boxId: string, remotePort: number): Promise<number> {\n const tunnel = this.getTunnelOrThrow(boxId);\n const cached = tunnel.forwards.get(remotePort);\n if (cached !== undefined && (await this.isAlive(tunnel.controlPath))) {\n return cached;\n }\n if (cached !== undefined) {\n // Master died — every cached local port stopped listening. Drop them\n // all; callers that still hold a stale `localPort` will get a fresh\n // one on their next forward() call.\n tunnel.forwards.clear();\n }\n const localPort = await pickFreePort();\n const argv = [\n '-O', 'forward',\n '-L', `${HOST}:${String(localPort)}:${HOST}:${String(remotePort)}`,\n '-S', tunnel.controlPath,\n 'dummy', // the target host is ignored when -O is used, but argv needs one\n ];\n const res = await execa('ssh', argv, { reject: false });\n if (res.exitCode !== 0) {\n throw new Error(\n `ssh -O forward failed for ${boxId} (exit ${String(res.exitCode)}): ${res.stderr || res.stdout || '(no output)'}`,\n );\n }\n tunnel.forwards.set(remotePort, localPort);\n return localPort;\n }\n\n /**\n * Tear down a dead ControlMaster + every cached forward for this box,\n * then re-open from scratch. Idempotent — if the master is already alive\n * the master open() is a no-op, but the cached forwards still get\n * cleared so the next forward() call re-mints them. Returns when the\n * master is open and the box's forwards map is empty (ready for fresh\n * forward() calls).\n *\n * Use case: the cloud-poller observes ECONNREFUSED on the local port and\n * asks the backend to refresh the preview URL — that path calls into\n * here so the master + forward both come back fresh.\n */\n async refresh(opts: SshTunnelOpenOptions): Promise<void> {\n const existing = this.boxes.get(opts.boxId);\n if (existing) {\n const alive = await this.isAlive(existing.controlPath);\n if (!alive && existsSync(existing.controlPath)) {\n // Stale socket from a dead master — best-effort cleanup, then reopen.\n try {\n await execa(\n 'ssh',\n ['-O', 'exit', '-S', existing.controlPath, 'dummy'],\n { reject: false },\n );\n } catch {\n // ignore — `-O exit` on a dead master can fail\n }\n await rm(existing.controlPath, { force: true });\n }\n // Either way drop the cached forwards: even if the master happened to\n // be alive, the caller asked us to refresh because *something*\n // upstream (the local port) wasn't responding.\n existing.forwards.clear();\n }\n await this.open(opts);\n }\n\n /** Tear down a single forward. Idempotent — unknown ports are no-ops. */\n async unforward(boxId: string, remotePort: number): Promise<void> {\n const tunnel = this.getTunnelOrThrow(boxId);\n const localPort = tunnel.forwards.get(remotePort);\n if (localPort === undefined) return;\n const argv = [\n '-O', 'cancel',\n '-L', `${HOST}:${String(localPort)}:${HOST}:${String(remotePort)}`,\n '-S', tunnel.controlPath,\n 'dummy',\n ];\n await execa('ssh', argv, { reject: false });\n tunnel.forwards.delete(remotePort);\n }\n\n /**\n * Close the ControlMaster (and all its forwards). Idempotent — if no\n * master is recorded, no-op. Removes the socket file.\n */\n async close(boxId: string): Promise<void> {\n const tunnel = this.boxes.get(boxId);\n if (!tunnel) return;\n if (existsSync(tunnel.controlPath)) {\n await execa('ssh', ['-O', 'exit', '-S', tunnel.controlPath, 'dummy'], { reject: false });\n await rm(tunnel.controlPath, { force: true });\n }\n this.boxes.delete(boxId);\n }\n\n /** Tear down every open box. */\n async closeAll(): Promise<void> {\n const ids = Array.from(this.boxes.keys());\n await Promise.all(ids.map((id) => this.close(id)));\n }\n\n /** Path to the ControlMaster socket for `boxId`, if open. */\n controlPath(boxId: string): string | undefined {\n return this.boxes.get(boxId)?.controlPath;\n }\n\n /** True if a ControlMaster is registered for `boxId` (regardless of liveness). */\n has(boxId: string): boolean {\n return this.boxes.has(boxId);\n }\n\n /** Per-box ssh dir (tests use this to verify the layout). */\n boxSshDir(boxId: string): string | undefined {\n return this.boxes.get(boxId)?.boxSshDir;\n }\n\n /** Re-open the manager record for an existing-on-disk control socket. */\n registerExisting(boxId: string, opts: SshTunnelOpenOptions): void {\n const boxSshDir = opts.boxSshDir ?? defaultBoxSshDir(opts.boxId);\n this.boxes.set(boxId, {\n controlPath: join(boxSshDir, 'control.sock'),\n vpsHost: opts.vpsHost,\n vpsUser: opts.vpsUser ?? 'vscode',\n identity: opts.identity,\n boxSshDir,\n forwards: new Map(),\n });\n }\n\n private async isAlive(controlPath: string): Promise<boolean> {\n const res = await execa('ssh', ['-O', 'check', '-S', controlPath, 'dummy'], { reject: false });\n return res.exitCode === 0;\n }\n\n private getTunnelOrThrow(boxId: string): BoxTunnel {\n const t = this.boxes.get(boxId);\n if (!t) throw new Error(`no SSH ControlMaster registered for box ${boxId}; call open() first`);\n return t;\n }\n}\n\n/** Default per-box ssh dir: `~/.agentbox/boxes/<box-id>/ssh/`. */\nexport function defaultBoxSshDir(boxId: string): string {\n return resolve(homedir(), '.agentbox', 'boxes', boxId, 'ssh');\n}\n\n/**\n * Ask the kernel for a free port in the ephemeral range. We bind a fresh\n * server on `127.0.0.1:0`, capture the port the kernel assigned, and close\n * — there's a tiny race where the port could be claimed before `ssh -O\n * forward` lands, but in practice ssh's idempotent socket re-creation\n * doesn't conflict with the captive process.\n */\nasync function pickFreePort(): Promise<number> {\n return new Promise((resolveOk, reject) => {\n const srv = createServer();\n srv.unref();\n srv.on('error', reject);\n srv.listen(0, HOST, () => {\n const addr = srv.address();\n if (!addr || typeof addr === 'string') {\n srv.close();\n reject(new Error('could not get a free local port'));\n return;\n }\n const port = addr.port;\n srv.close(() => {\n if (port < EPHEMERAL_MIN || port > EPHEMERAL_MAX) {\n // Some systems give back a low port — that's fine, just allow it.\n resolveOk(port);\n } else {\n resolveOk(port);\n }\n });\n });\n });\n}\n\nexport const _internalForTests = {\n pickFreePort,\n defaultBoxSshDir,\n EPHEMERAL_MIN,\n EPHEMERAL_MAX,\n // Tests use the export below; this object documents the surface.\n exports: {} as Record<string, never>,\n};\n// Avoid unused-vars in case the test wants to add helpers later — see backend.ts pattern.\nvoid dirname;\n"],"mappings":"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","names":["existsSync","rm","mkdir","join","execa","join","dirname","resolve","execa","existsSync","mkdir","join","rm","client","SNAPSHOT_DEADLINE_MS"]}