@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,9 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- hostOpenCommand
4
- } from "./chunk-SNTHHWKY.js";
3
+ DOCKER_CONTEXT_FILE_MAP,
4
+ computeContextSha256,
5
+ hostOpenCommand,
6
+ readCliStamp,
7
+ readPreparedStateRaw,
8
+ resolveContextFilesFrom,
9
+ writePreparedStateRaw
10
+ } from "./chunk-XKH7NTT7.js";
5
11
 
6
- // ../../packages/sandbox-daytona/dist/chunk-MLQU4RFU.js
12
+ // ../../packages/sandbox-daytona/dist/chunk-35HJOOGT.js
7
13
  import { existsSync } from "fs";
8
14
  import { dirname, resolve } from "path";
9
15
  import { fileURLToPath } from "url";
@@ -34,6 +40,9 @@ import {
34
40
  import { homedir as homedir2 } from "os";
35
41
  import { dirname as dirname2, resolve as resolve3 } from "path";
36
42
  import { confirm, isCancel, intro, log, note, outro, password, spinner, text } from "@clack/prompts";
43
+ import { existsSync as existsSync4 } from "fs";
44
+ import { dirname as dirname3, resolve as resolve4 } from "path";
45
+ import { fileURLToPath as fileURLToPath2 } from "url";
37
46
  function resolveDockerfileContext() {
38
47
  const override = process.env.AGENTBOX_DOCKER_CONTEXT;
39
48
  if (override && existsSync(resolve(override, "Dockerfile.box"))) {
@@ -174,7 +183,7 @@ function defaultRetryLog(line) {
174
183
  `);
175
184
  }
176
185
  function sleep(ms) {
177
- return new Promise((resolve4) => setTimeout(resolve4, ms));
186
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
178
187
  }
179
188
  async function raceTimeout(p, ms, method) {
180
189
  let timer;
@@ -743,6 +752,66 @@ function maskKey(value) {
743
752
  if (value.length <= 8) return "*".repeat(value.length);
744
753
  return `${value.slice(0, 4)}\u2026${"*".repeat(8)}${value.slice(-4)}`;
745
754
  }
755
+ var SCHEMA = 1;
756
+ function resolveDaytonaContextFiles() {
757
+ const ctx = resolveDockerfileContext();
758
+ if (!ctx) return null;
759
+ const here = dirname3(fileURLToPath2(import.meta.url));
760
+ const packageRoot = resolve4(here, "..");
761
+ const monorepoRoot = resolve4(here, "..", "..", "..");
762
+ const dockerPackageRoot = resolve4(monorepoRoot, "packages", "sandbox-docker");
763
+ const docker = resolveContextFilesFrom(DOCKER_CONTEXT_FILE_MAP, {
764
+ contextDir: ctx.context,
765
+ devRoot: existsSync4(dockerPackageRoot) ? dockerPackageRoot : packageRoot
766
+ });
767
+ if (!docker) return null;
768
+ const overlay = resolveDaytonaCustomClaudeMd();
769
+ if (!overlay) return null;
770
+ return [
771
+ ...docker,
772
+ // Daytona-specific overlay: separate logical name so a docker/daytona
773
+ // CLAUDE.md drift produces different fingerprints (the daytona snapshot
774
+ // contains both files in distinct locations).
775
+ { rel: "daytona/custom-system-CLAUDE.md", abs: overlay }
776
+ ];
777
+ }
778
+ async function computeDaytonaContextFingerprint() {
779
+ const files = resolveDaytonaContextFiles();
780
+ if (!files) return null;
781
+ return { contextSha256: await computeContextSha256(files), files };
782
+ }
783
+ async function currentDaytonaBaseFingerprintLive() {
784
+ try {
785
+ const fp = await computeDaytonaContextFingerprint();
786
+ return fp?.contextSha256;
787
+ } catch {
788
+ return void 0;
789
+ }
790
+ }
791
+ function readPreparedDaytonaState() {
792
+ const raw = readPreparedStateRaw("daytona");
793
+ if (raw === null || typeof raw !== "object") return null;
794
+ const parsed = raw;
795
+ if (parsed.schema !== SCHEMA) return null;
796
+ return { schema: SCHEMA, base: parsed.base };
797
+ }
798
+ function writePreparedDaytonaState(opts) {
799
+ const stamp = readCliStamp();
800
+ const state = {
801
+ schema: SCHEMA,
802
+ base: {
803
+ imageRef: opts.snapshotName,
804
+ contextSha256: opts.contextSha256,
805
+ cliVersion: stamp.cliVersion,
806
+ cliCommit: stamp.cliCommit,
807
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
808
+ }
809
+ };
810
+ writePreparedStateRaw("daytona", state);
811
+ }
812
+ function preparedMatches(state, current) {
813
+ return state?.base?.contextSha256 === current;
814
+ }
746
815
 
747
816
  export {
748
817
  resolveDockerfileContext,
@@ -754,6 +823,11 @@ export {
754
823
  ensureDaytonaCredentials,
755
824
  secretsPath,
756
825
  readDaytonaCredStatus,
757
- maskKey
826
+ maskKey,
827
+ computeDaytonaContextFingerprint,
828
+ currentDaytonaBaseFingerprintLive,
829
+ readPreparedDaytonaState,
830
+ writePreparedDaytonaState,
831
+ preparedMatches
758
832
  };
759
- //# sourceMappingURL=chunk-2LF5YILI.js.map
833
+ //# sourceMappingURL=chunk-RSKG7AFU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../packages/sandbox-daytona/src/dockerfile-context.ts","../../../packages/sandbox-daytona/src/env-loader.ts","../../../packages/sandbox-daytona/src/backend.ts","../../../packages/sandbox-daytona/src/retry.ts","../../../packages/sandbox-daytona/src/credentials.ts","../../../packages/sandbox-daytona/src/prepared-state.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Locate `Dockerfile.box` + its build context so Daytona can `Image.fromDockerfile`\n * the same image the Docker provider builds locally. The Dockerfile COPYs from\n * the monorepo (packages/ctl/dist/bin.cjs, apps/cli/share/..., scripts/), so\n * the context dir must contain that tree.\n *\n * Mirrors `@agentbox/sandbox-docker`'s `resolveDockerBuild`, intentionally\n * inlined: sandbox-daytona must not depend on sandbox-docker (cross-provider\n * dep would defeat the point of `@agentbox/sandbox-cloud`).\n *\n * Resolution order:\n * 0. AGENTBOX_DOCKER_CONTEXT env override.\n * 1. Staged context shipped with the bundled `agent-box` package (sibling\n * of dist/, uniform in dev + installed).\n * 2. Legacy monorepo layout: Dockerfile.box at sandbox-docker's package\n * root, context = monorepo root.\n */\nexport interface DockerfileContext {\n dockerfile: string;\n context: string;\n}\n\nexport function resolveDockerfileContext(): DockerfileContext | null {\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 here = dirname(fileURLToPath(import.meta.url));\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 monorepo: this module is at packages/sandbox-daytona/dist; the\n // Dockerfile lives at packages/sandbox-docker/Dockerfile.box; the build\n // context is the monorepo root.\n const monorepoRoot = resolve(here, '..', '..', '..');\n const dockerfile = resolve(monorepoRoot, 'packages', 'sandbox-docker', 'Dockerfile.box');\n if (existsSync(dockerfile)) {\n return { dockerfile, context: monorepoRoot };\n }\n return null;\n}\n\n/**\n * Locate the daytona-specific `custom-system-CLAUDE.md` that overlays the\n * docker-shaped one baked into `Dockerfile.box`. Daytona boxes have no host\n * `.git/` bind-mount, so the in-box hint needs daytona-specific git wording\n * (use `agentbox-ctl git` for any host-touching op). Same two-tier lookup\n * shape as `resolveDockerfileContext()`: staged CLI runtime first, monorepo\n * source as the dev fallback.\n */\nexport function resolveDaytonaCustomClaudeMd(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n const staged = resolve(here, '..', 'runtime', 'daytona', 'custom-system-CLAUDE.md');\n if (existsSync(staged)) return staged;\n const monorepoRoot = resolve(here, '..', '..', '..');\n const dev = resolve(\n monorepoRoot,\n 'packages',\n 'sandbox-daytona',\n 'scripts',\n 'custom-system-CLAUDE.md',\n );\n if (existsSync(dev)) return dev;\n return null;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\n/**\n * Daytona env auto-loader. The SDK reads `DAYTONA_API_KEY` /\n * `DAYTONA_JWT_TOKEN` + `DAYTONA_ORGANIZATION_ID` from `process.env`. We pull\n * those keys in from `~/.agentbox/secrets.env` so the SDK Just Works after\n * the user runs `agentbox daytona 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 daytona login`.\n *\n * Project-level `.env` / `.env.local` are intentionally NOT consulted: those\n * files belong to the app code being developed, and a `DAYTONA_API_KEY`\n * there is typically meant for in-box code execution, not for the host CLI\n * to harvest and provision sandboxes with.\n *\n * Only Daytona-prefixed keys are imported; the rest of the file is left\n * alone. The loader is idempotent and side-effect-free after the first call.\n */\nconst DAYTONA_KEYS = [\n 'DAYTONA_API_KEY',\n 'DAYTONA_JWT_TOKEN',\n 'DAYTONA_ORGANIZATION_ID',\n 'DAYTONA_API_URL',\n 'DAYTONA_TARGET',\n] as const;\n\nlet loaded = false;\n\nexport function ensureDaytonaEnvLoaded(): void {\n if (loaded) return;\n loaded = true;\n importDaytonaFromFile(resolve(homedir(), '.agentbox', 'secrets.env'));\n}\n\nfunction importDaytonaFromFile(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 DAYTONA_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. Doesn't do variable interpolation — that's surprising to\n * users coming from full dotenv, but secrets typically don't reference each\n * other and we'd rather be predictable.\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 // Strip surrounding quotes (single or double).\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 * Daytona `CloudBackend` — maps the provider-neutral cloud primitives onto\n * `@daytonaio/sdk`. Lazy SDK client + lazy sandbox handle resolution so\n * importing this module costs nothing until a daytona-tagged box does something.\n */\n\nimport { Daytona, DaytonaNotFoundError, Image, SandboxState, type Sandbox } from '@daytonaio/sdk';\nimport type { CloudSandboxSummary } from '@agentbox/core';\nimport type {\n CloudBackend,\n CloudExecOptions,\n CloudExecResult,\n CloudFileEntry,\n CloudHandle,\n CloudPreviewUrl,\n CloudProvisionRequest,\n CloudState,\n CloudVolumeMount,\n} from '@agentbox/core';\nimport { resolveDockerfileContext } from './dockerfile-context.js';\nimport { ensureDaytonaEnvLoaded } from './env-loader.js';\nimport { withDaytonaRetry } from './retry.js';\n\n/**\n * Thin shorthand for `withDaytonaRetry` with our defaults. Most methods are\n * idempotent and use `retryOnAmbiguous: true`; the few that aren't override.\n */\nfunction retry<T>(\n method: string,\n fn: () => Promise<T>,\n opts: {\n attemptTimeoutMs?: number;\n retryOnAmbiguous?: boolean;\n /** When true, single-shot — no backoff list, no retries. */\n noRetry?: boolean;\n } = {},\n): Promise<T> {\n return withDaytonaRetry(\n {\n method,\n retryOnAmbiguous: opts.retryOnAmbiguous ?? true,\n attemptTimeoutMs: opts.attemptTimeoutMs,\n backoffMs: opts.noRetry === true ? [] : undefined,\n },\n fn,\n );\n}\n\n/**\n * Sentinel image ref the cloud-provider hands to us when the user didn't pass\n * `--image`. We translate it to `Image.fromDockerfile(...)` so Daytona builds\n * the same box image the Docker provider builds locally.\n */\nexport const DEFAULT_BOX_IMAGE_REF = 'agentbox/box:dev';\n\nlet client: Daytona | null = null;\nexport function getClient(): Daytona {\n if (!client) {\n // Pull DAYTONA_* keys from `.env.local` / `.env` / `~/.agentbox/secrets.env`\n // into process.env first — the SDK reads from process.env and most users\n // keep secrets in a project file rather than their shell rc.\n ensureDaytonaEnvLoaded();\n try {\n // Daytona() reads DAYTONA_API_KEY / DAYTONA_JWT_TOKEN + DAYTONA_ORGANIZATION_ID\n // from env.\n client = new Daytona();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // The interactive prompt in `agentbox daytona login` handles first-run\n // setup; this error path is for non-TTY callers (CI, scripts) where the\n // prompt was skipped.\n throw new Error(\n `Daytona credentials not configured: ${msg}\\n` +\n `Run \\`agentbox daytona login\\` interactively, or set DAYTONA_API_KEY in the environment.`,\n );\n }\n }\n return client;\n}\n\nasync function getSandbox(id: string): Promise<Sandbox> {\n return getClient().get(id);\n}\n\nasync function maybeGetSandbox(id: string): Promise<Sandbox | null> {\n try {\n return await getClient().get(id);\n } catch {\n return null;\n }\n}\n\n/**\n * Map Daytona's `SandboxState` (16 fine-grained values incl. transitional ones)\n * onto our 4-value `CloudState`. Transitional states ('starting', 'creating')\n * are reported as 'running' so callers don't ping-pong; 'archived' maps to\n * 'paused' (our pause is Daytona's archive).\n */\nfunction mapState(s: SandboxState | string | undefined): CloudState {\n switch (s) {\n case SandboxState.STARTED:\n return 'running';\n case SandboxState.STARTING:\n case SandboxState.CREATING:\n case SandboxState.RESTORING:\n case SandboxState.BUILDING_SNAPSHOT:\n case SandboxState.PULLING_SNAPSHOT:\n case SandboxState.PENDING_BUILD:\n case SandboxState.STOPPING:\n return 'running';\n case SandboxState.STOPPED:\n return 'stopped';\n case SandboxState.ARCHIVED:\n case SandboxState.ARCHIVING:\n return 'paused';\n case SandboxState.DESTROYED:\n case SandboxState.DESTROYING:\n case SandboxState.ERROR:\n case SandboxState.BUILD_FAILED:\n case SandboxState.UNKNOWN:\n default:\n return 'missing';\n }\n}\n\n/**\n * Translate our provider-neutral `CloudVolumeMount` into the SDK shape Daytona\n * expects. The SDK's `VolumeMount` carries `volumeId` + `mountPath` (+ optional\n * `subpath` for S3-prefix mounts); a 1:1 mapping with our type.\n */\nfunction toDaytonaVolumeMount(v: CloudVolumeMount): {\n volumeId: string;\n mountPath: string;\n subpath?: string;\n} {\n return {\n volumeId: v.volumeId,\n mountPath: v.mountPath,\n ...(v.subpath ? { subpath: v.subpath } : {}),\n };\n}\n\n/** Translate the request's image ref into something Daytona's `create` accepts. */\nfunction resolveImage(ref: string): string | Image {\n if (ref !== DEFAULT_BOX_IMAGE_REF) return ref;\n const ctx = resolveDockerfileContext();\n if (!ctx) {\n throw new Error(\n \"could not locate the AgentBox Dockerfile.box build context for the Daytona snapshot. \" +\n \"Set AGENTBOX_DOCKER_CONTEXT to a directory containing Dockerfile.box, or pass --image <ref> with a Daytona-compatible image.\",\n );\n }\n // Image.fromDockerfile bundles the directory the Dockerfile lives in and\n // ships it to Daytona to build a snapshot. The Dockerfile.box COPYs from\n // the monorepo tree; the staged `runtime/docker` context already mirrors\n // that tree, so the build resolves COPY paths correctly.\n return Image.fromDockerfile(ctx.dockerfile);\n}\n\n/**\n * Parse a `cpu-memory-disk` GB size spec (e.g. `4-8-20`) into Daytona's\n * `resources` shape. Returns `undefined` on any malformed input — three\n * positive integer slots are required.\n */\nexport function parseDaytonaSize(\n spec: string | undefined,\n): { cpu: number; memory: number; disk: number } | undefined {\n if (!spec) return undefined;\n const parts = spec.trim().split('-');\n if (parts.length !== 3) return undefined;\n const nums = parts.map((p) => Number(p));\n if (nums.some((n) => !Number.isInteger(n) || n <= 0)) return undefined;\n return { cpu: nums[0]!, memory: nums[1]!, disk: nums[2]! };\n}\n\nexport const daytonaBackend: CloudBackend = {\n name: 'daytona',\n\n async provision(req: CloudProvisionRequest): Promise<CloudHandle> {\n // No-retry: provision is non-idempotent — a 504 after the request reaches\n // the origin could create a duplicate billable sandbox we can't reference\n // for cleanup. The wrapper still bounds wall-clock at 900s (matching the\n // existing inline SDK timeout) so a wedged connection fails cleanly.\n return retry(\n 'provision',\n async () => {\n // Two SDK overloads:\n // - `CreateSandboxFromSnapshotParams` takes `snapshot:` and no\n // `onSnapshotCreateLogs` (the snapshot already exists, nothing to build).\n // - `CreateSandboxFromImageParams` takes `image:` and accepts\n // `onSnapshotCreateLogs` for streaming the Dockerfile build.\n // TypeScript can't infer the right overload from a union literal, so\n // split the call.\n // A `--size` / `box.sizeDaytona` like `4-8-20` overrides the default\n // resources. Note: Daytona rejects `resources` on the snapshot path\n // (stripped below), so this only takes effect when creating from an\n // image — snapshot-resume keeps the snapshot's baked-in resources.\n let sizeResources: { cpu: number; memory: number; disk: number } | undefined;\n if (req.size && req.size.length > 0) {\n sizeResources = parseDaytonaSize(req.size);\n if (!sizeResources) {\n req.onLog?.(\n `daytona: ignoring invalid size '${req.size}' (expected 'cpu-memory-disk' GB, e.g. '4-8-20')`,\n );\n }\n }\n const resources = sizeResources ?? req.resources;\n const baseParams = {\n ...(resources ? { resources } : {}),\n envVars: req.env,\n ...(req.volumes && req.volumes.length > 0\n ? { volumes: req.volumes.map(toDaytonaVolumeMount) }\n : {}),\n labels: { 'agentbox.name': req.name },\n };\n const client = getClient();\n // The first-time Dockerfile.box snapshot build is ~41 layers and pulls\n // Chromium — comfortably 5+ minutes wall time. Daytona's default ready\n // timeout is too short for that; override with 15 min so a cold build\n // doesn't fail mid-snapshot. Cached snapshots and snapshot-based\n // creates come up in seconds.\n // Resolve `req.image` against Daytona's snapshot registry first when\n // it's set to a non-default value: `agentbox prepare --provider\n // daytona` registers a named snapshot and writes `box.image:\n // <name>` into project config; subsequent creates should boot from\n // that snapshot, not try to pull `<name>:latest` from Docker Hub.\n // Default ref (agentbox/box:dev) skips the lookup and goes through\n // resolveImage (Image.fromDockerfile). Explicit `req.snapshot` always\n // wins (cloud checkpoint path).\n let snapshotName = req.snapshot;\n if (!snapshotName && req.image && req.image !== DEFAULT_BOX_IMAGE_REF) {\n try {\n const snap = await client.snapshot.get(req.image);\n if (snap && snap.name) snapshotName = snap.name;\n } catch {\n // Not a known snapshot — fall through and treat as a Docker image ref.\n }\n }\n // Daytona rejects `resources` on the snapshot path — the snapshot's\n // own params encode them. Strip resources only for the snapshot\n // branch; the image branch keeps them.\n const snapshotParams: Record<string, unknown> = { ...baseParams };\n delete snapshotParams.resources;\n const sandbox = snapshotName\n ? await client.create({ snapshot: snapshotName, ...snapshotParams }, { timeout: 900 })\n : await client.create(\n { image: resolveImage(req.image), ...baseParams },\n {\n timeout: 900,\n ...(req.onLog ? { onSnapshotCreateLogs: req.onLog } : {}),\n },\n );\n return { sandboxId: sandbox.id };\n },\n { retryOnAmbiguous: false, attemptTimeoutMs: 900_000 },\n );\n },\n\n async ensureVolume(name: string): Promise<{ volumeId: string }> {\n // Daytona's `volume.get(name, create=true)` returns the existing volume or\n // initiates creation on first call. Critically, a freshly-created volume\n // comes back in `creating`/`pending_create` state — passing such a volume\n // into `Daytona.create({ volumes: […] })` is rejected with\n // \"Volume is not in a ready state. Current state: creating\". So poll\n // `volume.get` until the state lands on `ready` (or a terminal failure).\n //\n // Volumes are org-scoped on Daytona — every sandbox in the same Daytona\n // organization sees the same id, which is what we want for sharing agent\n // credentials across all of a user's boxes.\n //\n // Each individual `volume.get` call is retry-wrapped so a transient edge\n // hiccup mid-poll doesn't fail the whole ensure.\n const client = getClient();\n let vol = await retry('volume.get(create)', () => client.volume.get(name, true));\n // Volumes typically transition from creating → ready within a few seconds.\n // Allow up to 60s in case of slow control-plane operations.\n const deadline = Date.now() + 60_000;\n while (vol.state !== 'ready') {\n if (vol.state === 'error' || vol.state === 'deleted' || vol.state === 'deleting') {\n throw new Error(\n `Daytona volume '${name}' is in unrecoverable state '${vol.state}'. ` +\n `Delete it from the Daytona dashboard and retry.`,\n );\n }\n if (Date.now() >= deadline) {\n throw new Error(\n `Daytona volume '${name}' did not become ready within 60s (state: ${vol.state}). ` +\n `Try again — the Daytona control plane may be slow.`,\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n vol = await retry('volume.get(poll)', () => client.volume.get(name));\n }\n return { volumeId: vol.id };\n },\n\n async get(sandboxId: string): Promise<CloudHandle | null> {\n return retry('get', async () => {\n const sb = await maybeGetSandbox(sandboxId);\n return sb ? { sandboxId: sb.id } : null;\n });\n },\n\n async list(): Promise<CloudSandboxSummary[]> {\n return retry('list', async () => {\n const client = getClient();\n // `client.list()` returns `PaginatedSandboxes { items: Sandbox[] }`\n // (page 1 by default). For prune we don't need multi-page traversal\n // yet — sandboxes per org are bounded; if that changes, loop on page.\n const page = await client.list();\n const items = Array.isArray(page) ? page : (page.items ?? []);\n return items.map((sb): CloudSandboxSummary => {\n const summary: CloudSandboxSummary = { sandboxId: sb.id };\n const raw = sb as unknown as {\n name?: string;\n labels?: Record<string, string>;\n state?: string;\n createdAt?: string;\n };\n const friendly = raw.labels?.['agentbox.name'] ?? raw.name;\n if (friendly) summary.name = friendly;\n if (raw.createdAt) summary.createdAt = raw.createdAt;\n if (typeof raw.state === 'string') summary.state = mapState(raw.state);\n return summary;\n });\n });\n },\n\n async start(h: CloudHandle): Promise<void> {\n return retry(\n 'start',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.start();\n },\n { attemptTimeoutMs: 60_000 },\n );\n },\n\n async stop(h: CloudHandle): Promise<void> {\n return retry(\n 'stop',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.stop();\n },\n { attemptTimeoutMs: 60_000 },\n );\n },\n\n async pause(h: CloudHandle): Promise<void> {\n // Our pause == cold storage (Daytona archive). The tradeoff is documented\n // in CloudBackend's interface comment.\n return retry(\n 'pause',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.archive();\n },\n { attemptTimeoutMs: 60_000 },\n );\n },\n\n async resume(h: CloudHandle): Promise<void> {\n return retry(\n 'resume',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.start();\n },\n { attemptTimeoutMs: 60_000 },\n );\n },\n\n async destroy(h: CloudHandle): Promise<void> {\n return retry(\n 'destroy',\n async () => {\n const sb = await maybeGetSandbox(h.sandboxId);\n if (!sb) return; // already gone — destroy is idempotent\n // Daytona's `delete()` on a running sandbox is queued, not synchronous —\n // observed in practice: `delete()` returns ok, the sandbox stays in\n // 'started' for tens of seconds, then eventually disappears. Stopping\n // first makes the delete synchronous so callers (and the dashboard) see\n // it gone immediately. Swallow stop errors — if the sandbox is already\n // stopped/archived, delete still works.\n try {\n await sb.stop(60);\n } catch {\n /* best-effort */\n }\n try {\n await sb.delete(60);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // Already deleted between stop and delete — fine.\n if (!/not found/i.test(msg)) throw err;\n }\n },\n { attemptTimeoutMs: 120_000 },\n );\n },\n\n async state(h: CloudHandle): Promise<CloudState> {\n return retry('state', async () => {\n const sb = await maybeGetSandbox(h.sandboxId);\n if (!sb) return 'missing';\n return mapState(sb.state);\n });\n },\n\n async exec(\n h: CloudHandle,\n cmd: string,\n opts?: CloudExecOptions,\n ): Promise<CloudExecResult> {\n return retry(\n 'exec',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n // Daytona's ExecuteResponse returns combined output in `result` with no\n // separate stderr stream. Surface it as stdout and leave stderr empty —\n // callers that need split streams must redirect inside `cmd` itself.\n const r = await sb.process.executeCommand(cmd, opts?.cwd, opts?.env);\n return { exitCode: r.exitCode, stdout: r.result, stderr: '' };\n },\n { attemptTimeoutMs: opts?.attemptTimeoutMs ?? 120_000, noRetry: opts?.noRetry },\n );\n },\n\n async uploadFile(h: CloudHandle, localPath: string, remotePath: string): Promise<void> {\n return retry(\n 'uploadFile',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.fs.uploadFile(localPath, remotePath);\n },\n { attemptTimeoutMs: 300_000 },\n );\n },\n\n async downloadFile(h: CloudHandle, remotePath: string, localPath: string): Promise<void> {\n return retry(\n 'downloadFile',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.fs.downloadFile(remotePath, localPath);\n },\n { attemptTimeoutMs: 300_000 },\n );\n },\n\n async listFiles(h: CloudHandle, remoteDir: string): Promise<CloudFileEntry[]> {\n return retry('listFiles', async () => {\n const sb = await getSandbox(h.sandboxId);\n const files = await sb.fs.listFiles(remoteDir);\n return files.map((f) => ({\n name: f.name,\n isDir: Boolean((f as { isDir?: boolean }).isDir),\n }));\n });\n },\n\n async previewUrl(h: CloudHandle, port: number): Promise<CloudPreviewUrl> {\n return retry('previewUrl', async () => {\n const sb = await getSandbox(h.sandboxId);\n const p = await sb.getPreviewLink(port);\n // The host CloudBoxPoller attaches `token` as `x-daytona-preview-token`\n // for every /bridge call. Browser-bound URLs use `signedPreviewUrl` below\n // instead (the two token kinds are not interchangeable on Daytona).\n return { url: p.url, token: p.token };\n });\n },\n\n async signedPreviewUrl(\n h: CloudHandle,\n port: number,\n expiresInSeconds: number,\n ): Promise<CloudPreviewUrl> {\n return retry('signedPreviewUrl', async () => {\n const sb = await getSandbox(h.sandboxId);\n const s = await sb.getSignedPreviewUrl(port, expiresInSeconds);\n return { url: s.url, token: s.token };\n });\n },\n\n async attachArgv(h: CloudHandle): Promise<string[]> {\n return retry('attachArgv', async () => {\n const sb = await getSandbox(h.sandboxId);\n // 60 min default expiry matches the SDK default; an interactive session\n // longer than that is rare. `sandbox-cloud`'s buildAttach appends\n // `-t '<inner cmd>'` for the per-session tmux attach.\n const ssh = await sb.createSshAccess(60);\n return [\n 'ssh',\n // First-connect to a never-seen host fingerprint should be silent in a\n // PTY — the user already authenticated via Daytona's API.\n '-o', 'StrictHostKeyChecking=accept-new',\n // Daytona's SSH gateway terminates per-token; no key file, no port.\n `${ssh.token}@ssh.app.daytona.io`,\n ];\n });\n },\n\n async revokeAttachToken(h: CloudHandle, argv: string[]): Promise<void> {\n // argv[3] = `${token}@ssh.app.daytona.io`; pull the token off the front.\n const userhost = argv[argv.length - 1] ?? '';\n const atIdx = userhost.indexOf('@');\n if (atIdx <= 0) return;\n const token = userhost.slice(0, atIdx);\n if (token.length === 0) return;\n try {\n await retry('revokeAttachToken', async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb.revokeSshAccess(token);\n });\n } catch {\n // Best-effort — tokens auto-expire after 60 min anyway.\n }\n },\n\n async createSnapshot(h: CloudHandle, snapshotName: string): Promise<void> {\n // Daytona's `_experimental_createSnapshot` puts the sandbox into the\n // `snapshotting` state, captures its filesystem, then returns. The\n // resulting snapshot is org-scoped and visible via the Daytona dashboard\n // and `client.snapshot.list()`. We give it a generous timeout (15min,\n // matching `provision`) because a large `/workspace` plus warmed agent\n // volumes can take a while to snapshot.\n //\n // No retry on ambiguous failures: a 504 mid-snapshot could leave a\n // half-built named snapshot in Daytona that a retry would collide on.\n // Matches `provision`'s policy.\n return retry(\n 'createSnapshot',\n async () => {\n const sb = await getSandbox(h.sandboxId);\n await sb._experimental_createSnapshot(snapshotName);\n },\n { attemptTimeoutMs: 900_000, retryOnAmbiguous: false },\n );\n },\n\n async deleteSnapshot(snapshotName: string): Promise<void> {\n return retry('deleteSnapshot', async () => {\n try {\n const client = getClient();\n const snapshot = await client.snapshot.get(snapshotName);\n await client.snapshot.delete(snapshot);\n } catch (err) {\n // Idempotent: a snapshot that's already gone is success from the\n // caller's perspective (mirrors `destroy()`'s \"not found\" handling).\n if (err instanceof DaytonaNotFoundError) return;\n const msg = err instanceof Error ? err.message : String(err);\n if (/not found/i.test(msg)) return;\n throw err;\n }\n });\n },\n};\n","/**\n * Bounded retry wrapper for `daytonaBackend` SDK calls. Daytona's CloudFront\n * edge intermittently 504s on `executeCommand` and other API calls (backlog\n * item 6.1) — without bounded retries an edge hiccup propagates as an\n * unbounded wedge in the calling code. This helper classifies transient\n * failures vs. permanent ones using the SDK's typed error classes, bounds\n * each attempt with a timeout, and caps the total wall-clock cost.\n *\n * Non-idempotent ops (`provision`) pass `retryOnAmbiguous: false` so a 504\n * after the request reached the origin doesn't create a duplicate sandbox.\n */\n\nimport {\n DaytonaAuthenticationError,\n DaytonaAuthorizationError,\n DaytonaConflictError,\n DaytonaConnectionError,\n DaytonaError,\n DaytonaNotFoundError,\n DaytonaRateLimitError,\n DaytonaTimeoutError,\n DaytonaValidationError,\n} from '@daytonaio/sdk';\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 (since 504 from CloudFront can mean \"origin still processing\").\n * Set false for non-idempotent operations (e.g. `provision`) where a\n * retry could create a duplicate.\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\n/** Internal sentinel used by the per-attempt timeout race. */\nclass AttemptTimeoutError extends Error {\n constructor(method: string, ms: number) {\n super(`daytona ${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 // Rate-limit responses always carry an intent from the server: back off.\n if (err instanceof DaytonaRateLimitError) return true;\n\n // Permanent client-side failures: never retry — the next call will get\n // the same answer and we'd just be wasting wall-clock.\n if (\n err instanceof DaytonaNotFoundError ||\n err instanceof DaytonaAuthenticationError ||\n err instanceof DaytonaAuthorizationError ||\n err instanceof DaytonaValidationError ||\n err instanceof DaytonaConflictError\n ) {\n return false;\n }\n\n // Connection / per-attempt timeout: the request may not have reached\n // the server. Gated by allowAmbiguous so non-idempotent callers can opt\n // out of double-execute risk.\n if (\n err instanceof DaytonaConnectionError ||\n err instanceof DaytonaTimeoutError ||\n err instanceof AttemptTimeoutError\n ) {\n return allowAmbiguous;\n }\n\n // Base DaytonaError: branch on statusCode. 5xx is ambiguous; 4xx we\n // didn't catch above is a permanent failure we hadn't seen before.\n if (err instanceof DaytonaError) {\n const status = err.statusCode;\n if (typeof status === 'number' && status >= 500 && status <= 599) {\n return allowAmbiguous;\n }\n return false;\n }\n\n // Axios-style fallback for raw errors that leak through without an SDK\n // wrapper. Match the same shape the SDK uses internally.\n if (err && typeof err === 'object') {\n const code = (err 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 ) {\n return allowAmbiguous;\n }\n const status =\n (err as { response?: { status?: unknown } }).response?.status ??\n (err as { status?: unknown }).status ??\n (err as { statusCode?: unknown }).statusCode;\n if (typeof status === 'number' && status >= 500 && status <= 599) {\n return allowAmbiguous;\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 withDaytonaRetry<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 `daytona ${opts.method}: attempt ${String(attempt)} failed (${errorSummary(err)}); retrying in ${String(delay)}ms`,\n );\n await sleep(delay);\n }\n }\n // Unreachable: the loop above either returns or throws.\n throw new Error(`withDaytonaRetry: exhausted attempts for ${opts.method}`);\n}\n\nfunction defaultRetryLog(line: string): void {\n // Prefix so log scrapers + users can distinguish retry chatter from real\n // CLI output. `\\n` before is intentional — many CLI surfaces use clack\n // spinners on stdout, and stderr lines without a leading newline can\n // collide with a redraw.\n process.stderr.write(`\\n[daytona-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 DaytonaError) {\n const status = err.statusCode;\n const cls = err.constructor.name;\n return `${cls}${typeof status === 'number' ? ` ${String(status)}` : ''}: ${truncate(err.message)}`;\n }\n if (err instanceof Error) {\n const code = (err as { code?: unknown }).code;\n return code !== undefined ? `${err.name}(${String(code)}): ${truncate(err.message)}` : `${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","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, text } from '@clack/prompts';\nimport { ensureDaytonaEnvLoaded } from './env-loader.js';\n\nconst DASHBOARD_KEYS_URL = 'https://app.daytona.io/dashboard/keys';\n\n/**\n * Keys we manage in `~/.agentbox/secrets.env`. When the user reconfigures we\n * strip any prior values for these keys before appending the new ones so the\n * file never accumulates duplicates.\n */\nconst MANAGED_KEYS = ['DAYTONA_API_KEY', 'DAYTONA_JWT_TOKEN', 'DAYTONA_ORGANIZATION_ID'] as const;\ntype ManagedKey = (typeof MANAGED_KEYS)[number];\n\nexport interface EnsureDaytonaCredentialsOptions {\n /** Re-prompt even when valid credentials are already present (used by `agentbox daytona login`). */\n force?: boolean;\n}\n\n/**\n * First-run interactive setup for Daytona credentials. Walks the user through\n * opening the dashboard, pasting an API key (or JWT + organization ID), and\n * persists the result to `~/.agentbox/secrets.env` — which the env-loader\n * already picks up for every cloud command.\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 the\n * \"credentials not configured\" error from the SDK instead of a hung prompt.\n */\nexport async function ensureDaytonaCredentials(\n opts: EnsureDaytonaCredentialsOptions = {},\n): Promise<void> {\n ensureDaytonaEnvLoaded();\n\n if (!opts.force && hasUsableCredentials()) return;\n if (!process.stdin.isTTY) return;\n\n intro('Daytona setup');\n note(\n `AgentBox needs a Daytona API key to provision cloud boxes.\\n` +\n `Generate one at ${DASHBOARD_KEYS_URL}`,\n 'API key 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('Daytona setup cancelled — re-run `agentbox daytona login` when ready.');\n return;\n }\n if (open) openDashboard();\n\n // One retry on auth failure (typos are the common case). Beyond that we bail\n // and surface the validation error; the user can re-run `agentbox daytona login`.\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(`Daytona credentials saved to ${secretsPath()}`);\n outro('Setup complete.');\n return;\n }\n if (result.kind === 'auth' && attempt === 0) {\n log.error(`That key was rejected by Daytona: ${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 Daytona to validate (${result.message}) — saving anyway.`);\n persistCredentials(creds);\n log.success(`Daytona credentials saved to ${secretsPath()}`);\n outro('Setup complete (unvalidated).');\n return;\n }\n throw new Error(`Daytona credentials rejected: ${result.message}`);\n }\n}\n\nfunction hasUsableCredentials(): boolean {\n if (process.env.DAYTONA_API_KEY) return true;\n if (process.env.DAYTONA_JWT_TOKEN && process.env.DAYTONA_ORGANIZATION_ID) return true;\n return false;\n}\n\ninterface Credentials {\n apiKey?: string;\n jwtToken?: string;\n organizationId?: string;\n}\n\nasync function promptForCredentials(): Promise<Credentials | null> {\n const key = await password({\n message: 'Paste your Daytona API key (or JWT token)',\n validate(v) {\n if (!v || v.trim().length === 0) return 'Cannot be empty';\n return undefined;\n },\n });\n if (isCancel(key)) {\n log.warn('Daytona setup cancelled.');\n return null;\n }\n const trimmed = key.trim();\n\n // JWTs start with `eyJ` (base64-encoded `{\"`). API keys don't, and don't need\n // an org ID — the SDK derives it from the key. Only ask for org ID for JWTs.\n if (trimmed.startsWith('eyJ')) {\n const org = await text({\n message: 'Paste your Daytona organization ID',\n placeholder: 'org_...',\n validate(v) {\n if (!v || v.trim().length === 0) return 'Cannot be empty';\n return undefined;\n },\n });\n if (isCancel(org)) {\n log.warn('Daytona setup cancelled.');\n return null;\n }\n return { jwtToken: trimmed, organizationId: org.trim() };\n }\n\n return { apiKey: trimmed };\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 Daytona');\n\n // Snapshot existing env so we can restore on failure — never poison\n // process.env with a bad key.\n const snapshot = snapshotManagedEnv();\n applyToEnv(creds);\n\n try {\n // Dynamic import so the SDK only loads when we actually need it (keeps the\n // Docker hot path lean, same reason as the provider registry).\n const { Daytona } = await import('@daytonaio/sdk');\n const client = new Daytona();\n await client.list();\n s.stop('Daytona credentials accepted');\n return { ok: true };\n } catch (err) {\n restoreManagedEnv(snapshot);\n const message = err instanceof Error ? err.message : String(err);\n s.stop('Daytona credentials check failed');\n if (/401|403|unauthor|forbidden|invalid/i.test(message)) {\n return { ok: false, kind: 'auth', message };\n }\n return { ok: false, kind: 'network', message };\n }\n}\n\nfunction 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\nfunction 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\nfunction applyToEnv(creds: Credentials): void {\n // Wipe the other auth method so the SDK doesn't get confused by stale env\n // (e.g. an old JWT lingering from a previous shell export).\n for (const k of MANAGED_KEYS) delete process.env[k];\n if (creds.apiKey) process.env.DAYTONA_API_KEY = creds.apiKey;\n if (creds.jwtToken) process.env.DAYTONA_JWT_TOKEN = creds.jwtToken;\n if (creds.organizationId) process.env.DAYTONA_ORGANIZATION_ID = creds.organizationId;\n}\n\nfunction persistCredentials(creds: Credentials): void {\n applyToEnv(creds);\n const path = secretsPath();\n mkdirSync(dirname(path), { recursive: true });\n\n // Read existing file, strip any managed keys, append fresh values. Keeps\n // unrelated DAYTONA_API_URL / DAYTONA_TARGET (or anything else the user\n // dropped here) untouched.\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[] = [];\n if (creds.apiKey) lines.push(`DAYTONA_API_KEY=${creds.apiKey}`);\n if (creds.jwtToken) lines.push(`DAYTONA_JWT_TOKEN=${creds.jwtToken}`);\n if (creds.organizationId) lines.push(`DAYTONA_ORGANIZATION_ID=${creds.organizationId}`);\n\n const body = (kept ? `${kept}\\n` : '') + lines.join('\\n') + '\\n';\n\n // Atomic write — rename(2) is atomic on the same filesystem, so partially\n // written secrets can't be left behind on a crash.\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\n/** What's currently configured. Used by `daytona login --status`. */\nexport interface DaytonaCredStatus {\n apiKey?: string;\n jwtToken?: string;\n organizationId?: string;\n source: 'env' | 'secrets.env' | 'none';\n}\n\nexport function readDaytonaCredStatus(): DaytonaCredStatus {\n // Snapshot what the shell already had before the loader runs so we can\n // distinguish env-from-shell from env-loaded-from-secrets.env.\n const shellHadKey = !!process.env.DAYTONA_API_KEY || !!process.env.DAYTONA_JWT_TOKEN;\n ensureDaytonaEnvLoaded();\n const apiKey = process.env.DAYTONA_API_KEY;\n const jwtToken = process.env.DAYTONA_JWT_TOKEN;\n const organizationId = process.env.DAYTONA_ORGANIZATION_ID;\n if (!apiKey && !jwtToken) return { source: 'none' };\n return {\n apiKey,\n jwtToken,\n organizationId,\n source: shellHadKey ? '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 * Daytona provider's `~/.agentbox/daytona-prepared.json` reader/writer +\n * build-context fingerprinting for the org-scoped base snapshot.\n *\n * The daytona prepare bakes the docker `Dockerfile.box` plus a daytona-\n * specific `custom-system-CLAUDE.md` overlay. The fingerprint covers both\n * — same canonical file map as the docker provider for the dockerfile\n * inputs, plus one extra entry for the daytona overlay.\n */\n\nimport { existsSync } from 'node:fs';\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 { resolveDaytonaCustomClaudeMd, resolveDockerfileContext } from './dockerfile-context.js';\n\nconst SCHEMA = 1 as const;\n\nexport type PreparedDaytonaState = PreparedBaseSnapshot<string, never>;\n\n/**\n * Resolve every file that influences the daytona base snapshot: the docker\n * build context (shared map from sandbox-core) plus the daytona-specific\n * CLAUDE.md overlay added by `Image.addLocalFile` in `prepare.ts`.\n *\n * Returns `null` if any file is missing — callers degrade to \"always\n * rebuild\" rather than stamp a misleading fingerprint.\n */\nexport function resolveDaytonaContextFiles(): ContextFile[] | null {\n const ctx = resolveDockerfileContext();\n if (!ctx) return null;\n // sandbox-daytona's package root = parent of src/ or parent of dist/.\n // Mirrors the `resolve(here, '..', '..', '..')` walk in dockerfile-context.ts.\n const here = dirname(fileURLToPath(import.meta.url));\n const packageRoot = resolve(here, '..');\n const monorepoRoot = resolve(here, '..', '..', '..');\n // Docker's dev fallback is anchored at sandbox-docker's root, not\n // sandbox-daytona's, so we pass the monorepo root and prefix the dev\n // paths to walk into packages/sandbox-docker/.\n //\n // Simpler: just point devRoot at sandbox-docker's package root when it\n // exists (legacy monorepo layout).\n const dockerPackageRoot = resolve(monorepoRoot, 'packages', 'sandbox-docker');\n const docker = resolveContextFilesFrom(DOCKER_CONTEXT_FILE_MAP, {\n contextDir: ctx.context,\n devRoot: existsSync(dockerPackageRoot) ? dockerPackageRoot : packageRoot,\n });\n if (!docker) return null;\n const overlay = resolveDaytonaCustomClaudeMd();\n if (!overlay) return null;\n return [\n ...docker,\n // Daytona-specific overlay: separate logical name so a docker/daytona\n // CLAUDE.md drift produces different fingerprints (the daytona snapshot\n // contains both files in distinct locations).\n { rel: 'daytona/custom-system-CLAUDE.md', abs: overlay },\n ];\n}\n\nexport interface DaytonaFingerprint {\n contextSha256: string;\n files: ContextFile[];\n}\n\nexport async function computeDaytonaContextFingerprint(): Promise<DaytonaFingerprint | null> {\n const files = resolveDaytonaContextFiles();\n if (!files) return null;\n return { contextSha256: await computeContextSha256(files), files };\n}\n\n/**\n * Compute the CURRENT build-context fingerprint for the daytona base snapshot.\n * Side-effect-free wrapper around `computeDaytonaContextFingerprint` that\n * returns just the SHA (or `undefined` when assets can't be resolved). Used\n * by the CLI's `evaluateBaseFreshness` to compare against the stored\n * `daytona-prepared.json.base.contextSha256`.\n */\nexport async function currentDaytonaBaseFingerprintLive(): Promise<string | undefined> {\n try {\n const fp = await computeDaytonaContextFingerprint();\n return fp?.contextSha256;\n } catch {\n return undefined;\n }\n}\n\nexport function readPreparedDaytonaState(): PreparedDaytonaState | null {\n const raw = readPreparedStateRaw('daytona');\n if (raw === null || typeof raw !== 'object') return null;\n const parsed = raw as Partial<PreparedDaytonaState>;\n if (parsed.schema !== SCHEMA) return null;\n return { schema: SCHEMA, base: parsed.base };\n}\n\nexport function writePreparedDaytonaState(opts: {\n snapshotName: string;\n contextSha256: string;\n}): void {\n const stamp = readCliStamp();\n const state: PreparedDaytonaState = {\n schema: SCHEMA,\n base: {\n imageRef: opts.snapshotName,\n contextSha256: opts.contextSha256,\n cliVersion: stamp.cliVersion,\n cliCommit: stamp.cliCommit,\n createdAt: new Date().toISOString(),\n },\n };\n writePreparedStateRaw('daytona', state);\n}\n\nexport function preparedMatches(\n state: PreparedDaytonaState | null,\n current: string,\n): boolean {\n return state?.base?.contextSha256 === current;\n}\n"],"mappings":"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","names":["existsSync","resolve","DaytonaNotFoundError","existsSync","readFileSync","homedir","dirname","resolve","fileURLToPath","resolve","existsSync","log","client","DaytonaNotFoundError","Daytona","dirname","readFileSync","homedir","fileURLToPath"]}
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../../packages/sandbox-core/dist/index.js
4
- import { mkdir, readFile, writeFile } from "fs/promises";
4
+ import { mkdir, open, readFile, rename, rm, stat, writeFile } from "fs/promises";
5
5
  import { homedir } from "os";
6
6
  import { dirname, join } from "path";
7
+ import { setTimeout as delay } from "timers/promises";
7
8
  import { execa } from "execa";
8
- import { readdir, stat } from "fs/promises";
9
+ import { readdir, stat as stat2 } from "fs/promises";
9
10
  import { join as join2 } from "path";
10
11
  import { createHash } from "crypto";
11
12
  import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
@@ -15,6 +16,49 @@ import { dirname as dirname2, resolve as pathResolve } from "path";
15
16
  var STATE_DIR = join(homedir(), ".agentbox");
16
17
  var STATE_FILE = join(STATE_DIR, "state.json");
17
18
  var EMPTY = { version: 1, boxes: [] };
19
+ var LOCK_STALE_MS = 15e3;
20
+ var LOCK_ACQUIRE_TIMEOUT_MS = 2e4;
21
+ var LOCK_RETRY_MS = 25;
22
+ async function withStateLock(path, fn) {
23
+ const lockPath = `${path}.lock`;
24
+ await mkdir(dirname(path), { recursive: true });
25
+ const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;
26
+ let held = false;
27
+ while (!held) {
28
+ try {
29
+ const fh = await open(lockPath, "wx");
30
+ await fh.writeFile(`${String(process.pid)}
31
+ `);
32
+ await fh.close();
33
+ held = true;
34
+ } catch (err) {
35
+ if (err.code !== "EEXIST") throw err;
36
+ try {
37
+ const st = await stat(lockPath);
38
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {
39
+ await rm(lockPath, { force: true });
40
+ continue;
41
+ }
42
+ } catch {
43
+ continue;
44
+ }
45
+ if (Date.now() >= deadline) break;
46
+ await delay(LOCK_RETRY_MS);
47
+ }
48
+ }
49
+ try {
50
+ return await fn();
51
+ } finally {
52
+ if (held) await rm(lockPath, { force: true }).catch(() => {
53
+ });
54
+ }
55
+ }
56
+ async function mutateState(mutator, path = STATE_FILE) {
57
+ await withStateLock(path, async () => {
58
+ const state = await readState(path);
59
+ await writeState(mutator(state), path);
60
+ });
61
+ }
18
62
  async function readState(path = STATE_FILE) {
19
63
  try {
20
64
  const raw = await readFile(path, "utf8");
@@ -38,16 +82,31 @@ async function readState(path = STATE_FILE) {
38
82
  }
39
83
  async function writeState(state, path = STATE_FILE) {
40
84
  await mkdir(dirname(path), { recursive: true });
41
- await writeFile(path, JSON.stringify(state, null, 2) + "\n", "utf8");
85
+ const tmp = `${path}.tmp.${String(process.pid)}.${String(Date.now())}`;
86
+ await writeFile(tmp, JSON.stringify(state, null, 2) + "\n", "utf8");
87
+ await rename(tmp, path);
42
88
  }
43
89
  async function recordBox(box, path = STATE_FILE) {
44
90
  const toWrite = (box.provider ?? "docker") === "docker" && !box.docker ? { ...box, docker: projectDockerFields(box) } : box;
45
- const state = await readState(path);
46
- const next = {
47
- version: 1,
48
- boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite]
49
- };
50
- await writeState(next, path);
91
+ await mutateState(
92
+ (state) => ({
93
+ version: 1,
94
+ boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite]
95
+ }),
96
+ path
97
+ );
98
+ }
99
+ async function reserveProjectIndex(record, projectRoot, path = STATE_FILE) {
100
+ let index = 1;
101
+ await mutateState((state) => {
102
+ index = allocateProjectIndex(state, projectRoot);
103
+ const reserved = { ...record, projectRoot, projectIndex: index };
104
+ return {
105
+ version: 1,
106
+ boxes: [...state.boxes.filter((b) => b.id !== record.id), reserved]
107
+ };
108
+ }, path);
109
+ return index;
51
110
  }
52
111
  function projectDockerFields(box) {
53
112
  return {
@@ -72,15 +131,13 @@ function projectDockerFields(box) {
72
131
  };
73
132
  }
74
133
  async function removeBoxRecord(id, path = STATE_FILE) {
75
- const state = await readState(path);
76
- const before = state.boxes.length;
77
- const next = {
78
- version: 1,
79
- boxes: state.boxes.filter((b) => b.id !== id)
80
- };
81
- if (next.boxes.length === before) return false;
82
- await writeState(next, path);
83
- return true;
134
+ let removed = false;
135
+ await mutateState((state) => {
136
+ const next = state.boxes.filter((b) => b.id !== id);
137
+ removed = next.length !== state.boxes.length;
138
+ return { version: 1, boxes: next };
139
+ }, path);
140
+ return removed;
84
141
  }
85
142
  function findBox(idOrName, state) {
86
143
  const q = idOrName.trim();
@@ -149,7 +206,7 @@ async function detectGitRepos(workspace) {
149
206
  }
150
207
  async function isGitDir(path) {
151
208
  try {
152
- const s = await stat(path);
209
+ const s = await stat2(path);
153
210
  return s.isDirectory();
154
211
  } catch {
155
212
  return false;
@@ -373,7 +430,7 @@ async function buildImage(opts = {}) {
373
430
  return ref;
374
431
  }
375
432
  async function pullOrBuild(ref, fingerprint, opts = {}) {
376
- const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-KE4DT3GX.js");
433
+ const { writePreparedDockerState: writePreparedDockerState2 } = await import("./prepared-state-MQHD3M5F-Q27AZU53.js");
377
434
  const registry = opts.registry ?? BOX_IMAGE_REGISTRY;
378
435
  const allowPull = opts.allowPull !== false;
379
436
  if (allowPull && registry && fingerprint) {
@@ -399,7 +456,7 @@ async function pullOrBuild(ref, fingerprint, opts = {}) {
399
456
  return { source: "built" };
400
457
  }
401
458
  async function ensureImage(ref = DEFAULT_BOX_IMAGE, opts = {}) {
402
- const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-KE4DT3GX.js");
459
+ const { computeDockerContextFingerprint: computeDockerContextFingerprint2, readPreparedDockerState: readPreparedDockerState2, preparedMatches: preparedMatches2 } = await import("./prepared-state-MQHD3M5F-Q27AZU53.js");
403
460
  const fingerprint = await computeDockerContextFingerprint2({
404
461
  contextDir: opts.contextDir
405
462
  });
@@ -473,6 +530,7 @@ export {
473
530
  STATE_FILE,
474
531
  readState,
475
532
  recordBox,
533
+ reserveProjectIndex,
476
534
  removeBoxRecord,
477
535
  findBox,
478
536
  allocateProjectIndex,
@@ -505,4 +563,4 @@ export {
505
563
  writePreparedDockerState,
506
564
  preparedMatches
507
565
  };
508
- //# sourceMappingURL=chunk-SNTHHWKY.js.map
566
+ //# sourceMappingURL=chunk-XKH7NTT7.js.map
@@ -0,0 +1 @@
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, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\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\n// Cross-process lock tunables. The lock guards the read-modify-write of\n// `state.json` so concurrent `agentbox create`/`destroy` processes can't lose\n// each other's records or interleave a half-written file. Held only for the\n// duration of one read+write (sub-millisecond), so contention clears fast even\n// with a burst of parallel creates.\nconst LOCK_STALE_MS = 15_000; // a lock older than this is presumed abandoned\nconst LOCK_ACQUIRE_TIMEOUT_MS = 20_000;\nconst LOCK_RETRY_MS = 25;\n\n/**\n * Run `fn` while holding an exclusive cross-process lock on `${path}.lock`.\n *\n * Acquisition: create the lockfile with `wx` (O_EXCL) — atomic on local FSes.\n * On contention, retry with a short backoff until {@link LOCK_ACQUIRE_TIMEOUT_MS};\n * a lockfile older than {@link LOCK_STALE_MS} is treated as abandoned (crashed\n * holder) and forcibly broken. If the lock still can't be taken before the\n * timeout we proceed anyway — the write is atomic (temp+rename) so the worst\n * case degrades to a possible lost update, never a corrupt file. The lock is\n * always released in `finally`.\n */\nasync function withStateLock<T>(path: string, fn: () => Promise<T>): Promise<T> {\n const lockPath = `${path}.lock`;\n await mkdir(dirname(path), { recursive: true });\n const deadline = Date.now() + LOCK_ACQUIRE_TIMEOUT_MS;\n let held = false;\n while (!held) {\n try {\n const fh = await open(lockPath, 'wx');\n await fh.writeFile(`${String(process.pid)}\\n`);\n await fh.close();\n held = true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n // Break a stale lock left by a crashed holder.\n try {\n const st = await stat(lockPath);\n if (Date.now() - st.mtimeMs > LOCK_STALE_MS) {\n await rm(lockPath, { force: true });\n continue;\n }\n } catch {\n // lock vanished between open and stat — retry immediately\n continue;\n }\n if (Date.now() >= deadline) break; // give up waiting; proceed best-effort\n await delay(LOCK_RETRY_MS);\n }\n }\n try {\n return await fn();\n } finally {\n if (held) await rm(lockPath, { force: true }).catch(() => {});\n }\n}\n\n/**\n * Locked read-modify-write of the state file. `mutator` receives the current\n * state and returns the next one; the read and the (atomic) write happen under\n * the same lock so concurrent mutators serialize instead of clobbering.\n */\nexport async function mutateState(\n mutator: (state: StateFile) => StateFile,\n path: string = STATE_FILE,\n): Promise<void> {\n await withStateLock(path, async () => {\n const state = await readState(path);\n await writeState(mutator(state), path);\n });\n}\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 // Atomic: write a sibling temp file then rename over the target. rename(2) is\n // atomic on local filesystems, so a concurrent writer (or a reader) never\n // observes a half-written / interleaved JSON file. The pid+time suffix keeps\n // parallel writers from colliding on the temp path itself.\n const tmp = `${path}.tmp.${String(process.pid)}.${String(Date.now())}`;\n await writeFile(tmp, JSON.stringify(state, null, 2) + '\\n', 'utf8');\n await rename(tmp, path);\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 await mutateState(\n (state) => ({\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== toWrite.id), toWrite],\n }),\n path,\n );\n}\n\n/**\n * Atomically allocate the next per-project index AND persist `record` claiming\n * it, under the state lock. Returns the reserved index (also stamped onto the\n * persisted record).\n *\n * Callers bake this index into on-disk paths (`<id>-<n>-<mnemonic>` box dir,\n * socket, snapshot), and host helpers re-derive those paths from the *recorded*\n * index — so the index must be settled before the paths are built. Allocating\n * with an unlocked `readState` and bumping a clash at record time would desync\n * the recorded index from the box's actual directory (status/ctl reads would\n * then miss it). Reserving up front, under the lock, guarantees the index a\n * create uses for its dirs is exactly the one in state.json, with no two\n * concurrent creates in the same project claiming the same number.\n */\nexport async function reserveProjectIndex(\n record: BoxRecord,\n projectRoot: string,\n path: string = STATE_FILE,\n): Promise<number> {\n let index = 1;\n await mutateState((state) => {\n index = allocateProjectIndex(state, projectRoot);\n const reserved: BoxRecord = { ...record, projectRoot, projectIndex: index };\n return {\n version: 1,\n boxes: [...state.boxes.filter((b) => b.id !== record.id), reserved],\n };\n }, path);\n return index;\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 let removed = false;\n await mutateState((state) => {\n const next = state.boxes.filter((b) => b.id !== id);\n removed = next.length !== state.boxes.length;\n return { version: 1, boxes: next };\n }, path);\n return removed;\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' | 'e2b';\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":["stat","join","readFile","homedir","dirname","dirname","resolve","fileURLToPath","execa","existsSync","dirname","writePreparedDockerState","computeDockerContextFingerprint","readPreparedDockerState","preparedMatches","here","fileURLToPath","resolve"]}