@madarco/agentbox 0.8.0 → 0.9.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 (46) hide show
  1. package/dist/{_cloud-attach-T727ZPRV.js → _cloud-attach-ZXBCNWJX.js} +4 -4
  2. package/dist/{chunk-67N47KUS.js → chunk-BXQMIEHC.js} +106 -31
  3. package/dist/chunk-BXQMIEHC.js.map +1 -0
  4. package/dist/{chunk-FODMEHD3.js → chunk-GU5LW4B5.js} +341 -25
  5. package/dist/chunk-GU5LW4B5.js.map +1 -0
  6. package/dist/{chunk-BGK32PZE.js → chunk-KL36BRN4.js} +2 -2
  7. package/dist/chunk-KL36BRN4.js.map +1 -0
  8. package/dist/chunk-MTVI44DW.js +662 -0
  9. package/dist/chunk-MTVI44DW.js.map +1 -0
  10. package/dist/{chunk-6OZDFNBF.js → chunk-NCJP5MTN.js} +201 -44
  11. package/dist/chunk-NCJP5MTN.js.map +1 -0
  12. package/dist/{dist-LOZBWMBF.js → dist-32EZBYG4.js} +9 -3
  13. package/dist/{dist-L4LCG5SJ.js → dist-CX5CGVEB.js} +4 -4
  14. package/dist/{dist-ZODPD2I6.js → dist-GDHP34ZK.js} +8 -10
  15. package/dist/dist-GDHP34ZK.js.map +1 -0
  16. package/dist/dist-XML54CNB.js +849 -0
  17. package/dist/dist-XML54CNB.js.map +1 -0
  18. package/dist/index.js +636 -340
  19. package/dist/index.js.map +1 -1
  20. package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js → prepared-state-CL4CWXQA-H5THETIM.js} +2 -2
  21. package/package.json +7 -5
  22. package/runtime/docker/packages/ctl/dist/bin.cjs +98 -29
  23. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
  24. package/runtime/hetzner/agentbox-vnc-start +15 -1
  25. package/runtime/hetzner/ctl.cjs +98 -29
  26. package/runtime/relay/bin.cjs +229 -37
  27. package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
  28. package/runtime/vercel/agentbox-codex-hooks.json +68 -0
  29. package/runtime/vercel/agentbox-open +28 -0
  30. package/runtime/vercel/agentbox-setup-skill.md +196 -0
  31. package/runtime/vercel/agentbox-vnc-start +91 -0
  32. package/runtime/vercel/claude-managed-settings.json +115 -0
  33. package/runtime/vercel/ctl.cjs +23466 -0
  34. package/runtime/vercel/custom-system-CLAUDE.md +50 -0
  35. package/runtime/vercel/gh-shim +263 -0
  36. package/runtime/vercel/git-shim +131 -0
  37. package/runtime/vercel/scripts/provision.sh +274 -0
  38. package/dist/chunk-67N47KUS.js.map +0 -1
  39. package/dist/chunk-6OZDFNBF.js.map +0 -1
  40. package/dist/chunk-BGK32PZE.js.map +0 -1
  41. package/dist/chunk-FODMEHD3.js.map +0 -1
  42. package/dist/dist-ZODPD2I6.js.map +0 -1
  43. /package/dist/{_cloud-attach-T727ZPRV.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
  44. /package/dist/{dist-LOZBWMBF.js.map → dist-32EZBYG4.js.map} +0 -0
  45. /package/dist/{dist-L4LCG5SJ.js.map → dist-CX5CGVEB.js.map} +0 -0
  46. /package/dist/{prepared-state-CL4CWXQA-ME4HSKDE.js.map → prepared-state-CL4CWXQA-H5THETIM.js.map} +0 -0
package/dist/index.js CHANGED
@@ -16,18 +16,27 @@ import {
16
16
  secretsPath as secretsPath2,
17
17
  syncFirewallSource
18
18
  } from "./chunk-I24B6AXR.js";
19
+ import {
20
+ detectSbx,
21
+ ensureVercelCredentials,
22
+ installSbxHint,
23
+ maskKey as maskKey3,
24
+ readVercelCredStatus,
25
+ secretsPath as secretsPath3
26
+ } from "./chunk-MTVI44DW.js";
19
27
  import {
20
28
  agentSpecsForCloud,
21
29
  ensureAgentVolumesForCloud,
22
30
  listCloudCheckpoints,
23
31
  resolveCloudCheckpoint,
24
32
  seedAgentVolumesIfFresh
25
- } from "./chunk-67N47KUS.js";
33
+ } from "./chunk-BXQMIEHC.js";
26
34
  import {
27
35
  ADVANCED_HINT_GROUPS,
28
36
  NEW_BOX_ID,
29
37
  NEW_BOX_LABEL,
30
38
  buildCloudAttachInnerCommand,
39
+ clipboardCaptureAvailable,
31
40
  cloudAgentAttach,
32
41
  createMenuLines,
33
42
  detectHostTerminal,
@@ -36,15 +45,20 @@ import {
36
45
  lifecycleMenuLines,
37
46
  loadPtyBackend,
38
47
  menuLines,
48
+ pasteHostClipboardImage,
49
+ popTerminalTitle,
39
50
  postAnswer,
40
51
  providerForBox,
41
52
  providerForCreate,
53
+ pushTerminalTitle,
42
54
  renderFooter,
43
55
  runWrappedAttach,
56
+ setTerminalTitle,
44
57
  sidebarLines,
45
58
  statusLine,
59
+ stripTitleGlyph,
46
60
  subscribePrompts
47
- } from "./chunk-FODMEHD3.js";
61
+ } from "./chunk-GU5LW4B5.js";
48
62
  import {
49
63
  AmbiguousBoxError,
50
64
  BOX_STATUS_EVENT,
@@ -109,6 +123,7 @@ import {
109
123
  hashRpcParams,
110
124
  hostBackupHasCredentials,
111
125
  ideProfile,
126
+ injectPrCreateHead,
112
127
  inspectBox,
113
128
  installPortless,
114
129
  killShellSession,
@@ -184,7 +199,7 @@ import {
184
199
  waitForTmuxPaneContent,
185
200
  warmUpClaudeCredentials,
186
201
  writeJob
187
- } from "./chunk-6OZDFNBF.js";
202
+ } from "./chunk-NCJP5MTN.js";
188
203
  import {
189
204
  DEFAULT_BOX_IMAGE,
190
205
  STATE_DIR,
@@ -192,15 +207,15 @@ import {
192
207
  imageInfo,
193
208
  readState,
194
209
  resolveBoxRef
195
- } from "./chunk-BGK32PZE.js";
210
+ } from "./chunk-KL36BRN4.js";
196
211
  import "./chunk-G3H2L3O2.js";
197
212
 
198
213
  // src/version.ts
199
- var AGENTBOX_VERSION = true ? "0.8.0" : "0.0.0-dev";
200
- var AGENTBOX_COMMIT = true ? "ab8f9c3" : "dev";
214
+ var AGENTBOX_VERSION = true ? "0.9.0" : "0.0.0-dev";
215
+ var AGENTBOX_COMMIT = true ? "78c06a7" : "dev";
201
216
 
202
217
  // src/index.ts
203
- import { Command as Command44 } from "commander";
218
+ import { Command as Command45 } from "commander";
204
219
 
205
220
  // src/engine-override.ts
206
221
  async function applyEngineOverrideAtStartup() {
@@ -666,12 +681,23 @@ function buildPromptArgs(agentKind, prompt, userArgs) {
666
681
  return resolveAgentLauncher(agentKind).buildArgs(prompt, userArgs);
667
682
  }
668
683
 
684
+ // src/lib/queue/parse-max-option.ts
685
+ function parseMaxOption(flag, raw) {
686
+ if (raw === void 0) return void 0;
687
+ const n = Number(raw);
688
+ if (!Number.isInteger(n) || n <= 0) {
689
+ throw new Error(`${flag}: expected a positive integer, got "${raw}"`);
690
+ }
691
+ return n;
692
+ }
693
+
669
694
  // src/lib/queue/submit.ts
670
695
  import { randomBytes } from "crypto";
671
696
  import { request as httpRequest } from "http";
672
697
  async function submitQueueJob(input) {
673
698
  const cfg = await loadQueueConfig();
674
699
  const ceiling = typeof input.maxRunningOverride === "number" && input.maxRunningOverride > 0 ? input.maxRunningOverride : cfg.maxConcurrent;
700
+ const maxWorking = typeof input.maxWorkingOverride === "number" && input.maxWorkingOverride > 0 ? input.maxWorkingOverride : void 0;
675
701
  const id = newJobId();
676
702
  const job = {
677
703
  id,
@@ -683,6 +709,7 @@ async function submitQueueJob(input) {
683
709
  agentArgs: input.agentArgs,
684
710
  createOpts: input.createOpts,
685
711
  maxConcurrent: ceiling,
712
+ ...maxWorking !== void 0 ? { maxWorking } : {},
686
713
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
687
714
  logPath: queueLogPath(id)
688
715
  };
@@ -1143,7 +1170,7 @@ async function dirSizeCapped(dir, cap) {
1143
1170
 
1144
1171
  // src/lib/carry-gate.ts
1145
1172
  async function runCarryGate(args) {
1146
- const log44 = args.onLog ?? (() => {
1173
+ const log45 = args.onLog ?? (() => {
1147
1174
  });
1148
1175
  const yamlPath = join4(args.projectRoot, "agentbox.yaml");
1149
1176
  const items = await loadCarrySection(yamlPath);
@@ -1165,7 +1192,7 @@ async function runCarryGate(args) {
1165
1192
  });
1166
1193
  if (decision === "cancel") return { decision: "cancel" };
1167
1194
  if (decision === "skip-this-run") {
1168
- log44(`carry: skipped for this box (${String(resolved.entries.length)} entry/entries not copied)`);
1195
+ log45(`carry: skipped for this box (${String(resolved.entries.length)} entry/entries not copied)`);
1169
1196
  return { decision: "skip", entries: [] };
1170
1197
  }
1171
1198
  return { decision: "approve", entries: resolved.entries };
@@ -1205,6 +1232,63 @@ async function resolveFromBranch(ref, opts) {
1205
1232
  }
1206
1233
  return ref;
1207
1234
  }
1235
+ var UseBranchError = class extends Error {
1236
+ constructor(message) {
1237
+ super(message);
1238
+ this.name = "UseBranchError";
1239
+ }
1240
+ };
1241
+ async function resolveUseBranch(name, opts) {
1242
+ if (!name || name.length === 0) return void 0;
1243
+ const remote = opts.remote ?? "origin";
1244
+ await execa("git", ["-C", opts.repo, "fetch", "--quiet", remote, name], {
1245
+ reject: false
1246
+ });
1247
+ const exists = await execa(
1248
+ "git",
1249
+ ["-C", opts.repo, "show-ref", "--verify", "--quiet", `refs/heads/${name}`],
1250
+ { reject: false }
1251
+ );
1252
+ if (exists.exitCode !== 0) {
1253
+ throw new UseBranchError(
1254
+ `--use-branch: no local branch "${name}" in ${opts.repo}. Create or check it out on the host first (--use-branch reuses an existing branch; use --from-branch to fork a new box branch from a ref).`
1255
+ );
1256
+ }
1257
+ return name;
1258
+ }
1259
+ async function currentHostBranch(repo) {
1260
+ const r = await execa("git", ["-C", repo, "rev-parse", "--abbrev-ref", "HEAD"], {
1261
+ reject: false
1262
+ });
1263
+ if (r.exitCode !== 0) return void 0;
1264
+ const branch = r.stdout.trim();
1265
+ if (!branch || branch === "HEAD") return void 0;
1266
+ return branch;
1267
+ }
1268
+ async function resolveBranchSelection(opts) {
1269
+ if (opts.useBranch && opts.fromBranch) {
1270
+ throw new UseBranchError(
1271
+ "--use-branch and --from-branch are mutually exclusive: --use-branch reuses an existing branch, --from-branch forks a new box branch from a base ref. Pass only one."
1272
+ );
1273
+ }
1274
+ if (opts.useBranch) {
1275
+ return { useBranch: await resolveUseBranch(opts.useBranch, { repo: opts.repo }) };
1276
+ }
1277
+ if (opts.fromBranch) {
1278
+ return { fromBranch: await resolveFromBranch(opts.fromBranch, { repo: opts.repo }) };
1279
+ }
1280
+ if (opts.providerName !== "docker" && opts.cloudUseCurrentBranch) {
1281
+ const current = await currentHostBranch(opts.repo);
1282
+ if (current) {
1283
+ opts.log?.(`cloud.useCurrentBranch: starting box on host branch "${current}"`);
1284
+ return { useBranch: current };
1285
+ }
1286
+ opts.log?.(
1287
+ "cloud.useCurrentBranch is set but host HEAD is detached; forking a fresh branch instead"
1288
+ );
1289
+ }
1290
+ return {};
1291
+ }
1208
1292
 
1209
1293
  // src/session-teleport/claude.ts
1210
1294
  import { mkdir, mkdtemp, readdir, readFile as readFile2, stat as stat3, writeFile } from "fs/promises";
@@ -1784,7 +1868,7 @@ async function maybeRunSetupWizard(args) {
1784
1868
  }
1785
1869
  }
1786
1870
  const go = await confirm2({
1787
- message: "New project detected, run setup wizard?",
1871
+ message: "New project: run setup wizard? Will install dependencies and setup agentbox.yaml",
1788
1872
  initialValue: true
1789
1873
  });
1790
1874
  if (isCancel3(go) || !go) return { action: "proceed", envFilesToImport };
@@ -1830,14 +1914,6 @@ function passthroughFlags(opts) {
1830
1914
  function reattachRef(r) {
1831
1915
  return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
1832
1916
  }
1833
- function parseMaxRunningOption(raw) {
1834
- if (raw === void 0) return void 0;
1835
- const n = Number(raw);
1836
- if (!Number.isInteger(n) || n <= 0) {
1837
- throw new Error(`--max-running: expected a positive integer, got "${raw}"`);
1838
- }
1839
- return n;
1840
- }
1841
1917
  function pickCreateOpts(opts) {
1842
1918
  return {
1843
1919
  workspace: opts.workspace,
@@ -1865,6 +1941,8 @@ function logPrune(rebuild) {
1865
1941
  }
1866
1942
  var RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
1867
1943
  async function attachClaudeWrapped(box, sessionName, reattach, onError, openIn) {
1944
+ const provider = await providerForBox(box);
1945
+ const canPaste = await clipboardCaptureAvailable();
1868
1946
  const code = await runWrappedAttach({
1869
1947
  container: box.container,
1870
1948
  dockerArgv: buildClaudeAttachArgv(box.container, sessionName),
@@ -1875,7 +1953,8 @@ async function attachClaudeWrapped(box, sessionName, reattach, onError, openIn)
1875
1953
  mode: "claude",
1876
1954
  detachNotice: formatDetachNotice(reattach),
1877
1955
  onError,
1878
- openIn
1956
+ openIn,
1957
+ onPasteImage: canPaste ? () => pasteHostClipboardImage(provider, box) : void 0
1879
1958
  });
1880
1959
  process.exit(code);
1881
1960
  }
@@ -1967,15 +2046,21 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
1967
2046
  ).option(
1968
2047
  "--from-branch <ref>",
1969
2048
  "base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first."
2049
+ ).option(
2050
+ "-b, --use-branch <name>",
2051
+ "reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch."
1970
2052
  ).option(
1971
2053
  "-v, --verbose",
1972
2054
  "bypass the spinner and stream raw provider output (docker build / Daytona snapshot create) to stderr. The same content always lands in ~/.agentbox/logs/claude.log."
1973
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
2055
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
1974
2056
  "-i, --initial-prompt <text>",
1975
2057
  "seed the claude session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent). NOTE: this is NOT claude's own `-p` headless print mode \u2014 for that, pass `-- -p ...`."
1976
2058
  ).option(
1977
2059
  "--max-running <n>",
1978
2060
  "per-invocation override of queue.maxConcurrent; only honored when `-i` is set"
2061
+ ).option(
2062
+ "--max-working <n>",
2063
+ "per-invocation override of queue.maxWorking; only honored when `-i` is set"
1979
2064
  ).option(
1980
2065
  "-c, --continue",
1981
2066
  "teleport the most recent host Claude Code session for this cwd into the box and resume from it"
@@ -2046,7 +2131,8 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2046
2131
  }
2047
2132
  throw err;
2048
2133
  }
2049
- const maxRunningOverride = parseMaxRunningOption(opts.maxRunning);
2134
+ const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
2135
+ const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
2050
2136
  const result = await submitQueueJob({
2051
2137
  agent: "claude-code",
2052
2138
  boxName: opts.name ?? "",
@@ -2054,7 +2140,8 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2054
2140
  prompt: opts.initialPrompt,
2055
2141
  agentArgs: claudeArgs,
2056
2142
  createOpts: pickCreateOpts(opts),
2057
- maxRunningOverride
2143
+ maxRunningOverride,
2144
+ maxWorkingOverride
2058
2145
  });
2059
2146
  outro2(
2060
2147
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -2112,10 +2199,18 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2112
2199
  effectiveClaudeArgs = buildPromptArgs("claude-code", wiz.initialPrompt, claudeArgs);
2113
2200
  }
2114
2201
  let fromBranch;
2202
+ let useBranch;
2115
2203
  try {
2116
- fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
2204
+ ({ fromBranch, useBranch } = await resolveBranchSelection({
2205
+ useBranch: opts.useBranch,
2206
+ fromBranch: opts.fromBranch,
2207
+ repo: opts.workspace,
2208
+ providerName,
2209
+ cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
2210
+ log: (m) => cmdLog.write(m)
2211
+ }));
2117
2212
  } catch (err) {
2118
- if (err instanceof FromBranchError) {
2213
+ if (err instanceof FromBranchError || err instanceof UseBranchError) {
2119
2214
  log9.error(err.message);
2120
2215
  cmdLog.close();
2121
2216
  process.exit(2);
@@ -2139,6 +2234,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2139
2234
  vnc: { enabled: cfg.effective.box.vnc },
2140
2235
  limits: resolveLimits(cfg.effective.box, opts),
2141
2236
  fromBranch,
2237
+ useBranch,
2142
2238
  projectRoot
2143
2239
  },
2144
2240
  binary: "claude",
@@ -2182,6 +2278,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2182
2278
  useSnapshot,
2183
2279
  checkpointRef,
2184
2280
  fromBranch,
2281
+ useBranch,
2185
2282
  image: cfg.effective.box.image,
2186
2283
  claudeConfig: { isolate: cfg.effective.box.isolateClaudeConfig },
2187
2284
  claudeEnv: resolved.env,
@@ -2438,7 +2535,7 @@ var claudeStartCommand = new Command2("start").description(
2438
2535
  ).option("--session-name <name>", "tmux session name (default from config; built-in: claude)").option(
2439
2536
  "--no-sync-config",
2440
2537
  "skip rsyncing the host's ~/.claude into the box's volume before starting (faster; use existing in-box state)"
2441
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
2538
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
2442
2539
  "-c, --continue",
2443
2540
  "teleport the most recent host Claude Code session for this cwd into the box and resume"
2444
2541
  ).option(
@@ -2554,6 +2651,17 @@ claudeCommand.addCommand(claudeLoginCommand);
2554
2651
  // src/commands/checkpoint.ts
2555
2652
  import { confirm as confirm4, isCancel as isCancel5, log as log10 } from "@clack/prompts";
2556
2653
  import { Command as Command3 } from "commander";
2654
+ var CLOUD_BACKENDS = ["daytona", "hetzner", "vercel"];
2655
+ async function cloudProviderFor(backend) {
2656
+ switch (backend) {
2657
+ case "daytona":
2658
+ return (await import("./dist-CX5CGVEB.js")).daytonaProvider;
2659
+ case "hetzner":
2660
+ return (await import("./dist-GDHP34ZK.js")).hetznerProvider;
2661
+ case "vercel":
2662
+ return (await import("./dist-XML54CNB.js")).vercelProvider;
2663
+ }
2664
+ }
2557
2665
  var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
2558
2666
  var CHECKPOINT_NOTICE_TTL_MS = 66e4;
2559
2667
  async function projectRootFor(cwd, recordRoot) {
@@ -2639,10 +2747,16 @@ var lsSub = new Command3("ls").description("List this project's checkpoints (bot
2639
2747
  const projectRoot = (await findProjectRoot(process.cwd())).root;
2640
2748
  const cfg = await loadEffectiveConfig(projectRoot);
2641
2749
  const defDocker = resolveDefaultCheckpoint(cfg.effective, "docker");
2642
- const defDaytona = resolveDefaultCheckpoint(cfg.effective, "daytona");
2643
2750
  const dockerList = await listCheckpoints(projectRoot);
2644
- const daytonaList = await listCloudCheckpoints(projectRoot, "daytona");
2645
- if (dockerList.length === 0 && daytonaList.length === 0) {
2751
+ const cloudLists = await Promise.all(
2752
+ CLOUD_BACKENDS.map(async (backend) => ({
2753
+ backend,
2754
+ def: resolveDefaultCheckpoint(cfg.effective, backend),
2755
+ items: await listCloudCheckpoints(projectRoot, backend)
2756
+ }))
2757
+ );
2758
+ const totalCloud = cloudLists.reduce((n, c) => n + c.items.length, 0);
2759
+ if (dockerList.length === 0 && totalCloud === 0) {
2646
2760
  process.stdout.write(`no checkpoints for ${projectRoot}
2647
2761
  `);
2648
2762
  return;
@@ -2654,12 +2768,14 @@ var lsSub = new Command3("ls").description("List this project's checkpoints (bot
2654
2768
  `
2655
2769
  );
2656
2770
  }
2657
- for (const c of daytonaList) {
2658
- const flag = c.name === defDaytona ? " *default" : "";
2659
- process.stdout.write(
2660
- `${c.name} daytona (snapshot) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
2771
+ for (const { backend, def, items } of cloudLists) {
2772
+ for (const c of items) {
2773
+ const flag = c.name === def ? " *default" : "";
2774
+ process.stdout.write(
2775
+ `${c.name} ${backend} (snapshot) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
2661
2776
  `
2662
- );
2777
+ );
2778
+ }
2663
2779
  }
2664
2780
  } catch (err) {
2665
2781
  handleLifecycleError(err);
@@ -2667,13 +2783,16 @@ var lsSub = new Command3("ls").description("List this project's checkpoints (bot
2667
2783
  });
2668
2784
  var setDefaultSub = new Command3("set-default").description("Pin a checkpoint as the project default (box.defaultCheckpoint)").argument("[ref]", "checkpoint name (omit with --clear)").option("--clear", "unset the project default instead of setting one").option(
2669
2785
  "--provider <name>",
2670
- "set the default for only this provider (docker|daytona); without it, sets the cross-provider fallback"
2786
+ "set the default for only this provider (docker|daytona|hetzner|vercel); without it, sets the cross-provider fallback"
2671
2787
  ).action(async (ref, opts) => {
2672
2788
  try {
2673
2789
  const projectRoot = (await findProjectRoot(process.cwd())).root;
2674
2790
  const providerArg = opts.provider;
2675
- if (providerArg !== void 0 && providerArg !== "docker" && providerArg !== "daytona") {
2676
- throw new Error(`unknown provider '${opts.provider ?? ""}' (known: docker, daytona)`);
2791
+ const knownProviders = ["docker", ...CLOUD_BACKENDS];
2792
+ if (providerArg !== void 0 && !knownProviders.includes(providerArg)) {
2793
+ throw new Error(
2794
+ `unknown provider '${opts.provider ?? ""}' (known: ${knownProviders.join(", ")})`
2795
+ );
2677
2796
  }
2678
2797
  const configKey = defaultCheckpointConfigKey(providerArg);
2679
2798
  const label = providerArg ? `${providerArg} default checkpoint` : "project default checkpoint";
@@ -2692,11 +2811,16 @@ var setDefaultSub = new Command3("set-default").description("Pin a checkpoint as
2692
2811
  if (ref === void 0) {
2693
2812
  throw new Error("missing <ref> (or pass --clear to unset the default)");
2694
2813
  }
2695
- const checkDocker = providerArg === void 0 || providerArg === "docker";
2696
- const checkDaytona = providerArg === void 0 || providerArg === "daytona";
2697
- const dockerList = checkDocker ? await listCheckpoints(projectRoot) : [];
2698
- const daytonaList = checkDaytona ? await listCloudCheckpoints(projectRoot, "daytona") : [];
2699
- if (!dockerList.some((c) => c.name === ref) && !daytonaList.some((c) => c.name === ref)) {
2814
+ const dockerHit = (providerArg === void 0 || providerArg === "docker") && (await listCheckpoints(projectRoot)).some((c) => c.name === ref);
2815
+ let cloudHit = false;
2816
+ for (const backend of CLOUD_BACKENDS) {
2817
+ if (providerArg !== void 0 && providerArg !== backend) continue;
2818
+ if (await resolveCloudCheckpoint(projectRoot, backend, ref)) {
2819
+ cloudHit = true;
2820
+ break;
2821
+ }
2822
+ }
2823
+ if (!dockerHit && !cloudHit) {
2700
2824
  throw new Error(`checkpoint not found: ${ref} (see \`agentbox checkpoint ls\`)`);
2701
2825
  }
2702
2826
  const r = await setConfigValue("project", configKey, ref, projectRoot);
@@ -2709,8 +2833,12 @@ var setDefaultSub = new Command3("set-default").description("Pin a checkpoint as
2709
2833
  var rmSub = new Command3("rm").description("Delete a checkpoint (any provider that has it)").argument("<ref>", "checkpoint name").option("-y, --yes", "skip the confirmation prompt").option("--provider <name>", "delete only from this provider's store (default: all)").action(async (ref, opts) => {
2710
2834
  try {
2711
2835
  const projectRoot = (await findProjectRoot(process.cwd())).root;
2712
- const dockerInfo = !opts.provider || opts.provider === "docker";
2713
- const daytonaInfo = (!opts.provider || opts.provider === "daytona") && await resolveCloudCheckpoint(projectRoot, "daytona", ref);
2836
+ const wantDocker = !opts.provider || opts.provider === "docker";
2837
+ const cloudHits = [];
2838
+ for (const backend of CLOUD_BACKENDS) {
2839
+ if (opts.provider && opts.provider !== backend) continue;
2840
+ if (await resolveCloudCheckpoint(projectRoot, backend, ref)) cloudHits.push(backend);
2841
+ }
2714
2842
  if (!opts.yes) {
2715
2843
  const ok = await confirm4({ message: `Delete checkpoint ${ref}?`, initialValue: false });
2716
2844
  if (isCancel5(ok) || !ok) {
@@ -2719,7 +2847,7 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
2719
2847
  }
2720
2848
  }
2721
2849
  let any = false;
2722
- if (dockerInfo) {
2850
+ if (wantDocker) {
2723
2851
  const removed = await removeCheckpoint(projectRoot, ref);
2724
2852
  if (removed) {
2725
2853
  any = true;
@@ -2727,16 +2855,16 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
2727
2855
  `);
2728
2856
  }
2729
2857
  }
2730
- if (daytonaInfo) {
2731
- const { daytonaProvider } = await import("./dist-L4LCG5SJ.js");
2858
+ for (const backend of cloudHits) {
2732
2859
  try {
2733
- await daytonaProvider.checkpoint?.remove(projectRoot, ref);
2860
+ const provider = await cloudProviderFor(backend);
2861
+ await provider.checkpoint?.remove(projectRoot, ref);
2734
2862
  any = true;
2735
- process.stdout.write(`removed daytona checkpoint ${ref}
2863
+ process.stdout.write(`removed ${backend} checkpoint ${ref}
2736
2864
  `);
2737
2865
  } catch (err) {
2738
2866
  log10.warn(
2739
- `daytona checkpoint remove failed: ${err instanceof Error ? err.message : String(err)}`
2867
+ `${backend} checkpoint remove failed: ${err instanceof Error ? err.message : String(err)}`
2740
2868
  );
2741
2869
  }
2742
2870
  }
@@ -2747,7 +2875,8 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
2747
2875
  ["box.defaultCheckpoint", projectBox?.defaultCheckpoint, cfg.effective.box.defaultCheckpoint],
2748
2876
  ["box.defaultCheckpointDocker", projectBox?.defaultCheckpointDocker, cfg.effective.box.defaultCheckpointDocker],
2749
2877
  ["box.defaultCheckpointDaytona", projectBox?.defaultCheckpointDaytona, cfg.effective.box.defaultCheckpointDaytona],
2750
- ["box.defaultCheckpointHetzner", projectBox?.defaultCheckpointHetzner, cfg.effective.box.defaultCheckpointHetzner]
2878
+ ["box.defaultCheckpointHetzner", projectBox?.defaultCheckpointHetzner, cfg.effective.box.defaultCheckpointHetzner],
2879
+ ["box.defaultCheckpointVercel", projectBox?.defaultCheckpointVercel, cfg.effective.box.defaultCheckpointVercel]
2751
2880
  ];
2752
2881
  for (const [key, projectValue, effectiveValue] of defKeys) {
2753
2882
  if (projectValue === ref) {
@@ -2790,6 +2919,16 @@ async function runCloudCheckpointCreate(box, opts) {
2790
2919
  CHECKPOINT_NOTICE_TTL_MS
2791
2920
  );
2792
2921
  try {
2922
+ if (opts.setDefault && provider.extractAgentCredentials) {
2923
+ try {
2924
+ const saved = await provider.extractAgentCredentials(box);
2925
+ if (saved.length > 0) {
2926
+ log10.info(`saved ${saved.join(", ")} login to ~/.agentbox for future boxes`);
2927
+ }
2928
+ } catch (err) {
2929
+ log10.warn(`agent credential extract skipped: ${err instanceof Error ? err.message : String(err)}`);
2930
+ }
2931
+ }
2793
2932
  log10.info(`capturing cloud snapshot '${name}' (this may take a few minutes)`);
2794
2933
  const result = await provider.checkpoint.create(box, name);
2795
2934
  log10.success(`checkpoint ${result.ref} (daytona snapshot) captured`);
@@ -3124,14 +3263,6 @@ import { Command as Command5 } from "commander";
3124
3263
  function reattachRef2(r) {
3125
3264
  return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
3126
3265
  }
3127
- function parseMaxRunningOption2(raw) {
3128
- if (raw === void 0) return void 0;
3129
- const n = Number(raw);
3130
- if (!Number.isInteger(n) || n <= 0) {
3131
- throw new Error(`--max-running: expected a positive integer, got "${raw}"`);
3132
- }
3133
- return n;
3134
- }
3135
3266
  function pickCodexCreateOpts(opts) {
3136
3267
  return {
3137
3268
  workspace: opts.workspace,
@@ -3245,15 +3376,21 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3245
3376
  ).option(
3246
3377
  "--from-branch <ref>",
3247
3378
  "base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first."
3379
+ ).option(
3380
+ "-b, --use-branch <name>",
3381
+ "reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch."
3248
3382
  ).option(
3249
3383
  "-v, --verbose",
3250
3384
  "bypass the spinner and stream raw provider output to stderr. The same content always lands in ~/.agentbox/logs/codex.log."
3251
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
3385
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
3252
3386
  "-i, --initial-prompt <text>",
3253
3387
  "seed the codex session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent)."
3254
3388
  ).option(
3255
3389
  "--max-running <n>",
3256
3390
  "per-invocation override of queue.maxConcurrent; only honored when `-i` is set"
3391
+ ).option(
3392
+ "--max-working <n>",
3393
+ "per-invocation override of queue.maxWorking; only honored when `-i` is set"
3257
3394
  ).option(
3258
3395
  "-c, --continue",
3259
3396
  "teleport the most recent host Codex session for this cwd into the box and resume from it"
@@ -3326,7 +3463,8 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3326
3463
  }
3327
3464
  throw err;
3328
3465
  }
3329
- const maxRunningOverride = parseMaxRunningOption2(opts.maxRunning);
3466
+ const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
3467
+ const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
3330
3468
  const result = await submitQueueJob({
3331
3469
  agent: "codex",
3332
3470
  boxName: opts.name ?? "",
@@ -3334,7 +3472,8 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3334
3472
  prompt: opts.initialPrompt,
3335
3473
  agentArgs: codexArgs,
3336
3474
  createOpts: pickCodexCreateOpts(opts),
3337
- maxRunningOverride
3475
+ maxRunningOverride,
3476
+ maxWorkingOverride
3338
3477
  });
3339
3478
  outro3(
3340
3479
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -3363,10 +3502,18 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3363
3502
  process.exit(1);
3364
3503
  }
3365
3504
  let fromBranch;
3505
+ let useBranch;
3366
3506
  try {
3367
- fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
3507
+ ({ fromBranch, useBranch } = await resolveBranchSelection({
3508
+ useBranch: opts.useBranch,
3509
+ fromBranch: opts.fromBranch,
3510
+ repo: opts.workspace,
3511
+ providerName,
3512
+ cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
3513
+ log: (m) => cmdLog.write(m)
3514
+ }));
3368
3515
  } catch (err) {
3369
- if (err instanceof FromBranchError) {
3516
+ if (err instanceof FromBranchError || err instanceof UseBranchError) {
3370
3517
  log12.error(err.message);
3371
3518
  cmdLog.close();
3372
3519
  process.exit(2);
@@ -3389,6 +3536,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3389
3536
  vnc: { enabled: cfg.effective.box.vnc },
3390
3537
  limits: resolveLimits(cfg.effective.box, opts),
3391
3538
  fromBranch,
3539
+ useBranch,
3392
3540
  projectRoot
3393
3541
  },
3394
3542
  binary: "codex",
@@ -3439,6 +3587,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3439
3587
  useSnapshot,
3440
3588
  checkpointRef,
3441
3589
  fromBranch,
3590
+ useBranch,
3442
3591
  image: cfg.effective.box.image,
3443
3592
  codexConfig: { isolate: cfg.effective.box.isolateCodexConfig },
3444
3593
  withPlaywright,
@@ -3661,7 +3810,7 @@ var codexStartCommand = new Command5("start").description(
3661
3810
  ).option("--session-name <name>", "tmux session name (default from config; built-in: codex)").option(
3662
3811
  "--no-sync-config",
3663
3812
  "skip rsyncing the host's ~/.codex into the box's volume before starting (faster; use existing in-box state)"
3664
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
3813
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
3665
3814
  "-c, --continue",
3666
3815
  "teleport the most recent host Codex session for this cwd into the box and resume"
3667
3816
  ).option(
@@ -3782,14 +3931,6 @@ import { Command as Command6 } from "commander";
3782
3931
  function reattachRef3(r) {
3783
3932
  return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
3784
3933
  }
3785
- function parseMaxRunningOption3(raw) {
3786
- if (raw === void 0) return void 0;
3787
- const n = Number(raw);
3788
- if (!Number.isInteger(n) || n <= 0) {
3789
- throw new Error(`--max-running: expected a positive integer, got "${raw}"`);
3790
- }
3791
- return n;
3792
- }
3793
3934
  function pickOpencodeCreateOpts(opts) {
3794
3935
  return {
3795
3936
  workspace: opts.workspace,
@@ -3906,15 +4047,21 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
3906
4047
  ).option(
3907
4048
  "--from-branch <ref>",
3908
4049
  "base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first."
4050
+ ).option(
4051
+ "-b, --use-branch <name>",
4052
+ "reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch."
3909
4053
  ).option(
3910
4054
  "-v, --verbose",
3911
4055
  "bypass the spinner and stream raw provider output to stderr. The same content always lands in ~/.agentbox/logs/opencode.log."
3912
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
4056
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("--inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
3913
4057
  "-i, --initial-prompt <text>",
3914
4058
  "seed the opencode session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent)."
3915
4059
  ).option(
3916
4060
  "--max-running <n>",
3917
4061
  "per-invocation override of queue.maxConcurrent; only honored when `-i` is set"
4062
+ ).option(
4063
+ "--max-working <n>",
4064
+ "per-invocation override of queue.maxWorking; only honored when `-i` is set"
3918
4065
  ).option(
3919
4066
  "-c, --continue",
3920
4067
  "session teleport (not yet supported for opencode in v1; emits a friendly error)"
@@ -3972,7 +4119,8 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
3972
4119
  }
3973
4120
  throw err;
3974
4121
  }
3975
- const maxRunningOverride = parseMaxRunningOption3(opts.maxRunning);
4122
+ const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
4123
+ const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
3976
4124
  const result = await submitQueueJob({
3977
4125
  agent: "opencode",
3978
4126
  boxName: opts.name ?? "",
@@ -3980,7 +4128,8 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
3980
4128
  prompt: opts.initialPrompt,
3981
4129
  agentArgs: opencodeArgs,
3982
4130
  createOpts: pickOpencodeCreateOpts(opts),
3983
- maxRunningOverride
4131
+ maxRunningOverride,
4132
+ maxWorkingOverride
3984
4133
  });
3985
4134
  outro4(
3986
4135
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -4009,10 +4158,18 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4009
4158
  process.exit(1);
4010
4159
  }
4011
4160
  let fromBranch;
4161
+ let useBranch;
4012
4162
  try {
4013
- fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
4163
+ ({ fromBranch, useBranch } = await resolveBranchSelection({
4164
+ useBranch: opts.useBranch,
4165
+ fromBranch: opts.fromBranch,
4166
+ repo: opts.workspace,
4167
+ providerName,
4168
+ cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
4169
+ log: (m) => cmdLog.write(m)
4170
+ }));
4014
4171
  } catch (err) {
4015
- if (err instanceof FromBranchError) {
4172
+ if (err instanceof FromBranchError || err instanceof UseBranchError) {
4016
4173
  log13.error(err.message);
4017
4174
  cmdLog.close();
4018
4175
  process.exit(2);
@@ -4035,6 +4192,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4035
4192
  vnc: { enabled: cfg.effective.box.vnc },
4036
4193
  limits: resolveLimits(cfg.effective.box, opts),
4037
4194
  fromBranch,
4195
+ useBranch,
4038
4196
  projectRoot
4039
4197
  },
4040
4198
  binary: "opencode",
@@ -4067,6 +4225,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4067
4225
  useSnapshot,
4068
4226
  checkpointRef,
4069
4227
  fromBranch,
4228
+ useBranch,
4070
4229
  image: cfg.effective.box.image,
4071
4230
  opencodeConfig: { isolate: cfg.effective.box.isolateOpencodeConfig },
4072
4231
  withPlaywright,
@@ -4231,7 +4390,7 @@ var opencodeStartCommand = new Command6("start").description(
4231
4390
  ).option("--session-name <name>", "tmux session name (default from config; built-in: opencode)").option(
4232
4391
  "--no-sync-config",
4233
4392
  "skip rsyncing the host's OpenCode config into the box's volume before starting (faster; use existing in-box state)"
4234
- ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-b, --no-attach", NO_ATTACH_HELP).option(
4393
+ ).option("--attach-in <mode>", ATTACH_IN_HELP).option("-i, --inline", INLINE_HELP).option("-d, --no-attach", NO_ATTACH_HELP).option(
4235
4394
  "-c, --continue",
4236
4395
  "session teleport (not yet supported for opencode in v1; emits a friendly error)"
4237
4396
  ).option(
@@ -4769,6 +4928,9 @@ var createCommand = new Command9("create").description("Create and start a new a
4769
4928
  ).option(
4770
4929
  "--from-branch <ref>",
4771
4930
  "base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first."
4931
+ ).option(
4932
+ "-b, --use-branch <name>",
4933
+ "reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch."
4772
4934
  ).option("-y, --yes", "skip prompts, accept defaults").option(
4773
4935
  "--carry-yes",
4774
4936
  "auto-approve agentbox.yaml's `carry:` block (also AGENTBOX_CARRY_YES=1). Required for non-TTY use of `-y` when carry: is non-empty."
@@ -4857,11 +5019,22 @@ var createCommand = new Command9("create").description("Create and start a new a
4857
5019
  const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
4858
5020
  const provider = await providerForCreate({ flag: opts.provider, config: cfg.effective });
4859
5021
  let fromBranch;
5022
+ let useBranch;
4860
5023
  try {
4861
- fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
5024
+ ({ fromBranch, useBranch } = await resolveBranchSelection({
5025
+ useBranch: opts.useBranch,
5026
+ fromBranch: opts.fromBranch,
5027
+ repo: opts.workspace,
5028
+ providerName: provider.name,
5029
+ cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
5030
+ log: (m2) => {
5031
+ s.message(m2);
5032
+ cmdLog.write(m2);
5033
+ }
5034
+ }));
4862
5035
  } catch (err) {
4863
- if (err instanceof FromBranchError) {
4864
- s.stop("aborting: invalid --from-branch");
5036
+ if (err instanceof FromBranchError || err instanceof UseBranchError) {
5037
+ s.stop("aborting: invalid branch selection");
4865
5038
  log15.error(err.message);
4866
5039
  cmdLog.close();
4867
5040
  process.exit(2);
@@ -4881,6 +5054,7 @@ var createCommand = new Command9("create").description("Create and start a new a
4881
5054
  limits: resolveLimits(cfg.effective.box, opts),
4882
5055
  bundleDepth: cfg.effective.box.bundleDepth,
4883
5056
  fromBranch,
5057
+ useBranch,
4884
5058
  projectRoot,
4885
5059
  onLog: (line) => {
4886
5060
  s.message(line);
@@ -4890,7 +5064,14 @@ var createCommand = new Command9("create").description("Create and start a new a
4890
5064
  useSnapshot,
4891
5065
  sharedCache: cfg.effective.box.dockerCacheShared,
4892
5066
  portless: portlessEnabled,
4893
- portlessStateDir: cfg.effective.portless.stateDir || void 0
5067
+ portlessStateDir: cfg.effective.portless.stateDir || void 0,
5068
+ // Vercel-only sizing (box.vercelVcpus / vercelTimeoutMs). The cloud
5069
+ // scaffold reads these as overrides; other providers ignore them.
5070
+ ...provider.name === "vercel" ? {
5071
+ vcpus: cfg.effective.box.vercelVcpus,
5072
+ timeoutMs: cfg.effective.box.vercelTimeoutMs,
5073
+ networkPolicy: cfg.effective.box.vercelNetworkPolicy
5074
+ } : {}
4894
5075
  }
4895
5076
  });
4896
5077
  s.stop(`box ${result.record.container} ready`);
@@ -4945,7 +5126,7 @@ var createCommand = new Command9("create").description("Create and start a new a
4945
5126
  }
4946
5127
  outro5("done");
4947
5128
  if (attachClaudeAfter) {
4948
- const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-T727ZPRV.js");
5129
+ const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-ZXBCNWJX.js");
4949
5130
  await cloudAgentAttach2({
4950
5131
  box: result.record,
4951
5132
  binary: "claude",
@@ -5501,6 +5682,9 @@ var Compositor = class {
5501
5682
  * the poll respawn so it can't interrupt the transition). */
5502
5683
  busy = false;
5503
5684
  layout;
5685
+ /** Last host terminal/tab title we emitted, to dedupe OSC writes across the
5686
+ * frequent (spinner-driven) drawChrome calls. */
5687
+ lastTitle = null;
5504
5688
  prevRows = null;
5505
5689
  renderTimer = null;
5506
5690
  pollTimer = null;
@@ -5522,6 +5706,7 @@ var Compositor = class {
5522
5706
  };
5523
5707
  async run() {
5524
5708
  this.out.write("\x1B[?1049h\x1B[?25l\x1B[2J" + MOUSE_ENABLE_SEQ + EXT_KEYS_ENABLE_SEQ);
5709
+ pushTerminalTitle(this.out);
5525
5710
  if (this.inp.isTTY) this.inp.setRawMode(true);
5526
5711
  this.inp.resume();
5527
5712
  this.inp.on("data", this.onData);
@@ -6116,7 +6301,20 @@ var Compositor = class {
6116
6301
  this.out.write(s + SYNC_END);
6117
6302
  }
6118
6303
  }
6304
+ /** Drive the host terminal/tab title from the selected box:
6305
+ * `AgentBox: <session title | box name>`, or just `AgentBox` for the
6306
+ * synthetic "+ New box" entry / no selection. Deduped via {@link lastTitle}. */
6307
+ updateTitle() {
6308
+ if (this.tornDown) return;
6309
+ const box = this.selectedBox();
6310
+ const inner = box && box.id !== NEW_BOX_ID ? box.state === "running" && box.sessionTitle ? stripTitleGlyph(box.sessionTitle) : box.name : void 0;
6311
+ const title = inner ? `AgentBox: ${inner}` : "AgentBox";
6312
+ if (title === this.lastTitle) return;
6313
+ this.lastTitle = title;
6314
+ setTerminalTitle(title, this.out);
6315
+ }
6119
6316
  drawChrome() {
6317
+ this.updateTitle();
6120
6318
  if (this.tornDown || this.layout.tooSmall) return;
6121
6319
  const { sidebar, sepX, statusY } = this.layout;
6122
6320
  const decorate = this.activePrompts.size > 0 || this.activeNotices.size > 0;
@@ -6215,6 +6413,7 @@ var Compositor = class {
6215
6413
  if (this.inp.isTTY) this.inp.setRawMode(false);
6216
6414
  this.inp.pause();
6217
6415
  this.out.write(EXT_KEYS_DISABLE_SEQ + MOUSE_DISABLE_SEQ + "\x1B[?25h\x1B[0m\x1B[?1049l");
6416
+ popTerminalTitle(this.out);
6218
6417
  this.resolveDone?.();
6219
6418
  }
6220
6419
  };
@@ -6933,30 +7132,106 @@ var hetznerCommand = new Command13("hetzner").description(
6933
7132
  "Hetzner Cloud VPS provider \u2014 credentials, firewall, plus sugar for `--provider hetzner` (e.g. `agentbox hetzner create|claude|codex|opencode`)"
6934
7133
  ).addCommand(loginSub2, { isDefault: true }).addCommand(firewallSub);
6935
7134
 
6936
- // src/commands/destroy.ts
6937
- import { confirm as confirm7, isCancel as isCancel8, log as log19 } from "@clack/prompts";
7135
+ // ../../packages/sandbox-vercel/dist/cli.js
7136
+ import { log as log19 } from "@clack/prompts";
6938
7137
  import { Command as Command14 } from "commander";
6939
- var destroyCommand = new Command14("destroy").alias("rm").description("Destroy a box and discard its container writable layer (where /workspace lived)").argument(
7138
+ function reportError3(err) {
7139
+ const message = err instanceof Error ? err.message : String(err);
7140
+ log19.error(message);
7141
+ process.exitCode = 1;
7142
+ }
7143
+ function relativeExpiry(expiresAt) {
7144
+ const deltaMs = expiresAt * 1e3 - Date.now();
7145
+ if (deltaMs <= 0) return "expired";
7146
+ const mins = Math.round(deltaMs / 6e4);
7147
+ if (mins < 60) return `expires in ${mins}m`;
7148
+ return `expires in ${Math.round(mins / 60)}h`;
7149
+ }
7150
+ async function printStatus3() {
7151
+ const s = readVercelCredStatus();
7152
+ if (s.auth === "none") {
7153
+ process.stdout.write(
7154
+ "vercel: not configured\n run `agentbox vercel login` to set up credentials\n"
7155
+ );
7156
+ return;
7157
+ }
7158
+ const lines = ["vercel: configured"];
7159
+ if (s.auth === "cli") {
7160
+ const det = await detectSbx();
7161
+ lines.push(" auth: Vercel CLI (sandbox) login");
7162
+ lines.push(` cli: ${det.installed ? `installed${det.version ? ` ${det.version}` : ""}` : `not installed \u2014 run \`${installSbxHint()}\``}`);
7163
+ if (s.cli) {
7164
+ if (!s.cli.loggedIn) {
7165
+ lines.push(" session: logged out \u2014 run `agentbox vercel login`");
7166
+ } else {
7167
+ let tokenLine = ` token: ${s.token ? maskKey3(s.token) : "(live, from CLI store)"}`;
7168
+ if (s.cli.expiresAt) tokenLine += ` (${relativeExpiry(s.cli.expiresAt)})`;
7169
+ else tokenLine += " (no expiry recorded \u2014 will refresh on use)";
7170
+ lines.push(tokenLine);
7171
+ }
7172
+ lines.push(` store: ${s.cli.authPath}`);
7173
+ }
7174
+ } else if (s.auth === "oidc") {
7175
+ lines.push(" auth: OIDC token (VERCEL_OIDC_TOKEN)");
7176
+ } else {
7177
+ lines.push(" auth: access token");
7178
+ if (s.token) lines.push(` token: ${maskKey3(s.token)}`);
7179
+ }
7180
+ lines.push(` source: ${s.source}`);
7181
+ if (s.teamId) lines.push(` team: ${s.teamId}`);
7182
+ if (s.projectId) lines.push(` project: ${s.projectId}`);
7183
+ if (s.source === "secrets.env" || s.source === "cli-store") {
7184
+ lines.push(` file: ${secretsPath3()}`);
7185
+ }
7186
+ process.stdout.write(lines.join("\n") + "\n");
7187
+ }
7188
+ var loginSub3 = new Command14("login").description("Set up (or rotate) Vercel credentials for sandbox boxes").option("--status", "show what is currently configured (masked) and exit").action(async (opts) => {
7189
+ try {
7190
+ if (opts.status) {
7191
+ await printStatus3();
7192
+ return;
7193
+ }
7194
+ if (!process.stdin.isTTY) {
7195
+ process.stderr.write(
7196
+ "vercel login needs an interactive terminal \u2014 set the VERCEL_TOKEN trio (or VERCEL_OIDC_TOKEN) in the environment or in ~/.agentbox/secrets.env for non-interactive use.\n"
7197
+ );
7198
+ process.exitCode = 1;
7199
+ return;
7200
+ }
7201
+ await ensureVercelCredentials({ force: true });
7202
+ } catch (err) {
7203
+ reportError3(err);
7204
+ }
7205
+ });
7206
+ var vercelCommand = new Command14("vercel").description(
7207
+ "Vercel Sandbox provider \u2014 credentials, plus sugar for `--provider vercel` (e.g. `agentbox vercel create|claude|codex|opencode`)"
7208
+ ).addCommand(loginSub3, { isDefault: true });
7209
+
7210
+ // src/commands/destroy.ts
7211
+ import { confirm as confirm7, isCancel as isCancel8, log as log20 } from "@clack/prompts";
7212
+ import { Command as Command15 } from "commander";
7213
+ var destroyCommand = new Command15("destroy").alias("rm").description("Destroy a box and discard its container writable layer (where /workspace lived)").argument(
6940
7214
  "[box]",
6941
7215
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
6942
7216
  ).option("-y, --yes", "skip the confirmation prompt").option("--keep-snapshot", "don't delete the snapshot dir under ~/.agentbox/snapshots/").action(async (idOrName, opts) => {
6943
7217
  try {
6944
7218
  const box = await resolveBoxOrExit(idOrName);
6945
7219
  if (!opts.yes) {
6946
- log19.warn(
6947
- "This will wipe the container writable layer \u2014 /workspace contents and agent work-in-progress are lost."
6948
- );
6949
- log19.info(`id: ${box.id}`);
6950
- log19.info(`container: ${box.container}`);
7220
+ log20.warn("Will also wipe the box volume and agent work-in-progress");
7221
+ const rootBranch = box.gitWorktrees?.find((w) => w.kind === "root")?.branch;
7222
+ const lines = [box.name];
7223
+ if (rootBranch) lines.push(`branch: ${rootBranch}`);
7224
+ lines.push(`project: ${box.workspacePath}`);
6951
7225
  if (box.snapshotDir) {
6952
- log19.info(`snapshot: ${box.snapshotDir}${opts.keepSnapshot ? " (will be kept)" : ""}`);
7226
+ lines.push(`snapshot: ${box.snapshotDir}${opts.keepSnapshot ? " (will be kept)" : ""}`);
6953
7227
  }
7228
+ log20.info(lines.join("\n"));
6954
7229
  const ok = await confirm7({
6955
7230
  message: "Destroy this box?",
6956
7231
  initialValue: false
6957
7232
  });
6958
7233
  if (isCancel8(ok) || !ok) {
6959
- log19.info("cancelled");
7234
+ log20.info("cancelled");
6960
7235
  return;
6961
7236
  }
6962
7237
  }
@@ -6989,17 +7264,17 @@ var destroyCommand = new Command14("destroy").alias("rm").description("Destroy a
6989
7264
  });
6990
7265
 
6991
7266
  // src/commands/download.ts
6992
- import { confirm as confirm13, isCancel as isCancel14, log as log25 } from "@clack/prompts";
6993
- import { Command as Command20 } from "commander";
7267
+ import { confirm as confirm13, isCancel as isCancel14, log as log26 } from "@clack/prompts";
7268
+ import { Command as Command21 } from "commander";
6994
7269
 
6995
7270
  // src/commands/download-claude.ts
6996
- import { confirm as confirm8, isCancel as isCancel9, log as log20 } from "@clack/prompts";
6997
- import { Command as Command15 } from "commander";
7271
+ import { confirm as confirm8, isCancel as isCancel9, log as log21 } from "@clack/prompts";
7272
+ import { Command as Command16 } from "commander";
6998
7273
  function tag(item) {
6999
7274
  const noun = item.category === "plugins" ? "plugin" : item.category.replace(/s$/, "");
7000
7275
  return ` ${item.category}/${item.name} (new ${noun})`;
7001
7276
  }
7002
- var downloadClaudeCommand = new Command15("claude").description(
7277
+ var downloadClaudeCommand = new Command16("claude").description(
7003
7278
  "Download box-installed Claude skills/plugins/agents/commands back to host ~/.claude (additive)"
7004
7279
  ).argument(
7005
7280
  "[box]",
@@ -7009,7 +7284,7 @@ var downloadClaudeCommand = new Command15("claude").description(
7009
7284
  const box = await resolveBoxOrExit(idOrName);
7010
7285
  const volume = box.claudeConfigVolume ?? resolveClaudeVolume({ isolate: false, boxId: box.id }).volume;
7011
7286
  if (volume === SHARED_CLAUDE_VOLUME) {
7012
- log20.warn(
7287
+ log21.warn(
7013
7288
  `Reading the shared ${SHARED_CLAUDE_VOLUME} volume \u2014 it aggregates Claude extensions installed in ANY box, not just ${box.name}.`
7014
7289
  );
7015
7290
  }
@@ -7039,7 +7314,7 @@ var downloadClaudeCommand = new Command15("claude").description(
7039
7314
  initialValue: false
7040
7315
  });
7041
7316
  if (isCancel9(ok) || !ok) {
7042
- log20.info("cancelled");
7317
+ log21.info("cancelled");
7043
7318
  return;
7044
7319
  }
7045
7320
  }
@@ -7054,9 +7329,9 @@ var downloadClaudeCommand = new Command15("claude").description(
7054
7329
  });
7055
7330
 
7056
7331
  // src/commands/download-codex.ts
7057
- import { confirm as confirm9, isCancel as isCancel10, log as log21 } from "@clack/prompts";
7058
- import { Command as Command16 } from "commander";
7059
- var downloadCodexCommand = new Command16("codex").description(
7332
+ import { confirm as confirm9, isCancel as isCancel10, log as log22 } from "@clack/prompts";
7333
+ import { Command as Command17 } from "commander";
7334
+ var downloadCodexCommand = new Command17("codex").description(
7060
7335
  "Download box-side Codex config/auth (config.toml, auth.json, prompts) back to host ~/.codex (additive)"
7061
7336
  ).argument(
7062
7337
  "[box]",
@@ -7066,7 +7341,7 @@ var downloadCodexCommand = new Command16("codex").description(
7066
7341
  const box = await resolveBoxOrExit(idOrName);
7067
7342
  const volume = box.codexConfigVolume ?? resolveCodexVolume({ isolate: false, boxId: box.id }).volume;
7068
7343
  if (volume === SHARED_CODEX_VOLUME) {
7069
- log21.warn(
7344
+ log22.warn(
7070
7345
  `Reading the shared ${SHARED_CODEX_VOLUME} volume \u2014 it aggregates Codex config from ANY box, not just ${box.name}.`
7071
7346
  );
7072
7347
  }
@@ -7092,7 +7367,7 @@ var downloadCodexCommand = new Command16("codex").description(
7092
7367
  initialValue: false
7093
7368
  });
7094
7369
  if (isCancel10(ok) || !ok) {
7095
- log21.info("cancelled");
7370
+ log22.info("cancelled");
7096
7371
  return;
7097
7372
  }
7098
7373
  }
@@ -7105,9 +7380,9 @@ var downloadCodexCommand = new Command16("codex").description(
7105
7380
  });
7106
7381
 
7107
7382
  // src/commands/download-opencode.ts
7108
- import { confirm as confirm10, isCancel as isCancel11, log as log22 } from "@clack/prompts";
7109
- import { Command as Command17 } from "commander";
7110
- var downloadOpencodeCommand = new Command17("opencode").description(
7383
+ import { confirm as confirm10, isCancel as isCancel11, log as log23 } from "@clack/prompts";
7384
+ import { Command as Command18 } from "commander";
7385
+ var downloadOpencodeCommand = new Command18("opencode").description(
7111
7386
  "Download box-side OpenCode config/auth (auth.json, opencode.json, agents, commands, themes) back to host ~/.config + ~/.local/share opencode (additive)"
7112
7387
  ).argument(
7113
7388
  "[box]",
@@ -7117,7 +7392,7 @@ var downloadOpencodeCommand = new Command17("opencode").description(
7117
7392
  const box = await resolveBoxOrExit(idOrName);
7118
7393
  const volume = box.opencodeConfigVolume ?? resolveOpencodeVolume({ isolate: false, boxId: box.id }).volume;
7119
7394
  if (volume === SHARED_OPENCODE_VOLUME) {
7120
- log22.warn(
7395
+ log23.warn(
7121
7396
  `Reading the shared ${SHARED_OPENCODE_VOLUME} volume \u2014 it aggregates OpenCode config from ANY box, not just ${box.name}.`
7122
7397
  );
7123
7398
  }
@@ -7143,7 +7418,7 @@ var downloadOpencodeCommand = new Command17("opencode").description(
7143
7418
  initialValue: false
7144
7419
  });
7145
7420
  if (isCancel11(ok) || !ok) {
7146
- log22.info("cancelled");
7421
+ log23.info("cancelled");
7147
7422
  return;
7148
7423
  }
7149
7424
  }
@@ -7156,8 +7431,8 @@ var downloadOpencodeCommand = new Command17("opencode").description(
7156
7431
  });
7157
7432
 
7158
7433
  // src/commands/download-config.ts
7159
- import { confirm as confirm11, isCancel as isCancel12, log as log23 } from "@clack/prompts";
7160
- import { Command as Command18 } from "commander";
7434
+ import { confirm as confirm11, isCancel as isCancel12, log as log24 } from "@clack/prompts";
7435
+ import { Command as Command19 } from "commander";
7161
7436
  function tagChange(line) {
7162
7437
  const sp = line.indexOf(" ");
7163
7438
  const code = sp === -1 ? line : line.slice(0, sp);
@@ -7166,7 +7441,7 @@ function tagChange(line) {
7166
7441
  return ` ${path} ${isNew ? "(new)" : "(overwrites host)"}`;
7167
7442
  }
7168
7443
  var CONFIG_PATTERNS = ["agentbox.yaml"];
7169
- var downloadConfigCommand = new Command18("config").description("Download agentbox.yaml box -> host").argument(
7444
+ var downloadConfigCommand = new Command19("config").description("Download agentbox.yaml box -> host").argument(
7170
7445
  "[box]",
7171
7446
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
7172
7447
  ).option("-y, --yes", "skip the confirmation prompt").option("--dry-run", "list matched files and exit; don't write").option("--no-refresh", "skip the box->scratch-dir rsync step").action(async (idOrName, opts) => {
@@ -7174,15 +7449,15 @@ var downloadConfigCommand = new Command18("config").description("Download agentb
7174
7449
  const box = await resolveBoxOrExit(idOrName);
7175
7450
  const insp = await inspectBox(box.id);
7176
7451
  if (insp.state === "paused") {
7177
- log23.info("box is paused; unpausing");
7452
+ log24.info("box is paused; unpausing");
7178
7453
  await unpauseBox(box.id);
7179
7454
  } else if (insp.state === "stopped") {
7180
- log23.info("box is stopped; starting");
7455
+ log24.info("box is stopped; starting");
7181
7456
  await startBox(box.id);
7182
7457
  } else if (insp.state === "missing") {
7183
7458
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
7184
7459
  }
7185
- log23.info(`agentbox.yaml bypasses gitignore and copies directly into ${box.workspacePath}`);
7460
+ log24.info(`agentbox.yaml bypasses gitignore and copies directly into ${box.workspacePath}`);
7186
7461
  const preview = await pullToHost(box, {
7187
7462
  dryRun: true,
7188
7463
  respectGitignore: false,
@@ -7210,7 +7485,7 @@ var downloadConfigCommand = new Command18("config").description("Download agentb
7210
7485
  initialValue: false
7211
7486
  });
7212
7487
  if (isCancel12(ok) || !ok) {
7213
- log23.info("cancelled");
7488
+ log24.info("cancelled");
7214
7489
  return;
7215
7490
  }
7216
7491
  }
@@ -7232,8 +7507,8 @@ var downloadConfigCommand = new Command18("config").description("Download agentb
7232
7507
  });
7233
7508
 
7234
7509
  // src/commands/download-env.ts
7235
- import { confirm as confirm12, isCancel as isCancel13, log as log24 } from "@clack/prompts";
7236
- import { Command as Command19 } from "commander";
7510
+ import { confirm as confirm12, isCancel as isCancel13, log as log25 } from "@clack/prompts";
7511
+ import { Command as Command20 } from "commander";
7237
7512
  function tagChange2(line) {
7238
7513
  const sp = line.indexOf(" ");
7239
7514
  const code = sp === -1 ? line : line.slice(0, sp);
@@ -7241,7 +7516,7 @@ function tagChange2(line) {
7241
7516
  const isNew = /^>f\++$/.test(code);
7242
7517
  return ` ${path} ${isNew ? "(new)" : "(overwrites host)"}`;
7243
7518
  }
7244
- var downloadEnvCommand = new Command19("env").description(
7519
+ var downloadEnvCommand = new Command20("env").description(
7245
7520
  "Download gitignored env/config files (.env*, .envrc, secrets.toml, agentbox.yaml, ...) box -> host"
7246
7521
  ).argument(
7247
7522
  "[box]",
@@ -7256,15 +7531,15 @@ var downloadEnvCommand = new Command19("env").description(
7256
7531
  const box = await resolveBoxOrExit(idOrName);
7257
7532
  const insp = await inspectBox(box.id);
7258
7533
  if (insp.state === "paused") {
7259
- log24.info("box is paused; unpausing");
7534
+ log25.info("box is paused; unpausing");
7260
7535
  await unpauseBox(box.id);
7261
7536
  } else if (insp.state === "stopped") {
7262
- log24.info("box is stopped; starting");
7537
+ log25.info("box is stopped; starting");
7263
7538
  await startBox(box.id);
7264
7539
  } else if (insp.state === "missing") {
7265
7540
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
7266
7541
  }
7267
- log24.info(
7542
+ log25.info(
7268
7543
  `env/config files bypass gitignore and copy directly into ${box.workspacePath}`
7269
7544
  );
7270
7545
  const patterns = [...DEFAULT_ENV_PATTERNS, ...opts.pattern];
@@ -7295,7 +7570,7 @@ var downloadEnvCommand = new Command19("env").description(
7295
7570
  initialValue: false
7296
7571
  });
7297
7572
  if (isCancel13(ok) || !ok) {
7298
- log24.info("cancelled");
7573
+ log25.info("cancelled");
7299
7574
  return;
7300
7575
  }
7301
7576
  }
@@ -7317,7 +7592,7 @@ var downloadEnvCommand = new Command19("env").description(
7317
7592
  });
7318
7593
 
7319
7594
  // src/commands/download.ts
7320
- var downloadCommand = new Command20("download").enablePositionalOptions().description("Download a box's /workspace back into your host workspace dir (gitignore-aware)").argument(
7595
+ var downloadCommand = new Command21("download").enablePositionalOptions().description("Download a box's /workspace back into your host workspace dir (gitignore-aware)").argument(
7321
7596
  "[box]",
7322
7597
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
7323
7598
  ).option("-y, --yes", "skip the confirmation prompt").option("--dry-run", "print the change list and exit; don't write").option(
@@ -7343,7 +7618,7 @@ var downloadCommand = new Command20("download").enablePositionalOptions().descri
7343
7618
  throw new Error("cloud download does not yet support --dry-run; omit to bulk-pull /workspace.");
7344
7619
  }
7345
7620
  if (!opts.respectGitignore || opts.includeNodeModules || opts.withEnv || opts.pattern.length > 0) {
7346
- log25.warn(
7621
+ log26.warn(
7347
7622
  "cloud download ignores gitignore/--with-env/--pattern filters in v1 \u2014 pulling the whole /workspace tree (Phase 6 polish)."
7348
7623
  );
7349
7624
  }
@@ -7353,7 +7628,7 @@ var downloadCommand = new Command20("download").enablePositionalOptions().descri
7353
7628
  initialValue: false
7354
7629
  });
7355
7630
  if (isCancel14(ok) || !ok) {
7356
- log25.info("cancelled");
7631
+ log26.info("cancelled");
7357
7632
  return;
7358
7633
  }
7359
7634
  }
@@ -7372,17 +7647,17 @@ var downloadCommand = new Command20("download").enablePositionalOptions().descri
7372
7647
  }
7373
7648
  const insp = await inspectBox(box.id);
7374
7649
  if (insp.state === "paused") {
7375
- log25.info("box is paused; unpausing");
7650
+ log26.info("box is paused; unpausing");
7376
7651
  await unpauseBox(box.id);
7377
7652
  } else if (insp.state === "stopped") {
7378
- log25.info("box is stopped; starting");
7653
+ log26.info("box is stopped; starting");
7379
7654
  await startBox(box.id);
7380
7655
  } else if (insp.state === "missing") {
7381
7656
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
7382
7657
  }
7383
7658
  const rootWorktree = box.gitWorktrees?.find((w) => w.kind === "root");
7384
7659
  if (rootWorktree) {
7385
- log25.warn(
7660
+ log26.warn(
7386
7661
  `This box has been committing to branch \`${rootWorktree.branch}\` in a separate worktree.
7387
7662
  For a git-aware merge instead of a file copy, run from your checkout:
7388
7663
  git merge ${rootWorktree.branch}
@@ -7418,7 +7693,7 @@ Continuing with rsync into ${box.workspacePath}`
7418
7693
  initialValue: false
7419
7694
  });
7420
7695
  if (isCancel14(ok) || !ok) {
7421
- log25.info("cancelled");
7696
+ log26.info("cancelled");
7422
7697
  return;
7423
7698
  }
7424
7699
  }
@@ -7446,8 +7721,8 @@ downloadCommand.addCommand(downloadOpencodeCommand);
7446
7721
  downloadCommand.addCommand(downloadConfigCommand);
7447
7722
 
7448
7723
  // src/commands/drive.ts
7449
- import { log as log26 } from "@clack/prompts";
7450
- import { Command as Command21 } from "commander";
7724
+ import { log as log27 } from "@clack/prompts";
7725
+ import { Command as Command22 } from "commander";
7451
7726
 
7452
7727
  // src/lib/drive/keys.ts
7453
7728
  var NAMED = {
@@ -7629,11 +7904,11 @@ var SessionNotFoundError = class extends Error {
7629
7904
  // src/commands/drive.ts
7630
7905
  var PROMPT_ENTER_DELAY_MS = 200;
7631
7906
  var POLL_INTERVAL_MS2 = 250;
7632
- var driveCommand = new Command21("drive").description(
7907
+ var driveCommand = new Command22("drive").description(
7633
7908
  "Drive a running tmux session inside a box: snapshot the screen, send keystrokes, type text, or wait for output. Targets the agent session by default (claude \u2192 codex \u2192 opencode)."
7634
7909
  );
7635
7910
  var sessionOption = ["--session <name>", "tmux session to target (default: first running agent session)"];
7636
- var driveSnapshotCommand = new Command21("snapshot").description("Print the rendered terminal contents of the box's active tmux session.").argument(
7911
+ var driveSnapshotCommand = new Command22("snapshot").description("Print the rendered terminal contents of the box's active tmux session.").argument(
7637
7912
  "[box]",
7638
7913
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
7639
7914
  ).option(sessionOption[0], sessionOption[1]).option("--ansi", "preserve ANSI color/style escape sequences (default: plain text)").option("--with-cursor", "include cursor coordinates and pane size (implies --json)").option("--rows <range>", 'inclusive row range "FROM:TO" (negative numbers walk into scrollback)').option("--json", "emit a JSON envelope { session, cols, rows, cursor?, screen }").action(async (boxRef, opts) => {
@@ -7662,7 +7937,7 @@ var driveSnapshotCommand = new Command21("snapshot").description("Print the rend
7662
7937
  handleDriveError(err);
7663
7938
  }
7664
7939
  });
7665
- var driveKeypressCommand = new Command21("keypress").description('Send keystrokes parsed via the DSL (e.g. "<C-a>q", "ls<Enter>"). Each arg is concatenated with no spaces.').argument(
7940
+ var driveKeypressCommand = new Command22("keypress").description('Send keystrokes parsed via the DSL (e.g. "<C-a>q", "ls<Enter>"). Each arg is concatenated with no spaces.').argument(
7666
7941
  "<box>",
7667
7942
  "box ref: project index, id, id prefix, name, or container"
7668
7943
  ).argument("<keys...>", "one or more DSL tokens / literal text; `<<` escapes a literal `<`").option(sessionOption[0], sessionOption[1]).action(async (boxRef, keys, opts) => {
@@ -7676,7 +7951,7 @@ var driveKeypressCommand = new Command21("keypress").description('Send keystroke
7676
7951
  handleDriveError(err);
7677
7952
  }
7678
7953
  });
7679
- var driveSendTextCommand = new Command21("send-text").description("Type literal text into the session (no DSL parsing, no trailing Enter).").argument("<box>", "box ref").argument("<text>", "literal text to type").option(sessionOption[0], sessionOption[1]).action(async (boxRef, text, opts) => {
7954
+ var driveSendTextCommand = new Command22("send-text").description("Type literal text into the session (no DSL parsing, no trailing Enter).").argument("<box>", "box ref").argument("<text>", "literal text to type").option(sessionOption[0], sessionOption[1]).action(async (boxRef, text, opts) => {
7680
7955
  try {
7681
7956
  const box = await resolveBoxOrExit(boxRef);
7682
7957
  const provider = await providerForBox(box);
@@ -7686,7 +7961,7 @@ var driveSendTextCommand = new Command21("send-text").description("Type literal
7686
7961
  handleDriveError(err);
7687
7962
  }
7688
7963
  });
7689
- var drivePromptCommand = new Command21("prompt").description('Type text into the agent session and press Enter \u2014 convenience for "send a message to the running agent".').argument("<box>", "box ref").argument("<text>", "prompt text to send (literal; no DSL parsing)").option(sessionOption[0], sessionOption[1]).option("--delay <ms>", `milliseconds to wait between text and Enter (default: ${String(PROMPT_ENTER_DELAY_MS)})`).action(async (boxRef, text, opts) => {
7964
+ var drivePromptCommand = new Command22("prompt").description('Type text into the agent session and press Enter \u2014 convenience for "send a message to the running agent".').argument("<box>", "box ref").argument("<text>", "prompt text to send (literal; no DSL parsing)").option(sessionOption[0], sessionOption[1]).option("--delay <ms>", `milliseconds to wait between text and Enter (default: ${String(PROMPT_ENTER_DELAY_MS)})`).action(async (boxRef, text, opts) => {
7690
7965
  try {
7691
7966
  const box = await resolveBoxOrExit(boxRef);
7692
7967
  const provider = await providerForBox(box);
@@ -7699,7 +7974,7 @@ var drivePromptCommand = new Command21("prompt").description('Type text into the
7699
7974
  handleDriveError(err);
7700
7975
  }
7701
7976
  });
7702
- var driveWaitCommand = new Command21("wait").description("Block until --text appears in the session's rendered screen, or exit non-zero on timeout.").argument("<box>", "box ref").requiredOption("--text <str>", "substring to wait for").option("--timeout <ms>", "wall-clock cap in milliseconds (default: 5000)").option(sessionOption[0], sessionOption[1]).option("--json", "emit a JSON envelope { matched, elapsedMs, session, screen? }").action(async (boxRef, opts) => {
7977
+ var driveWaitCommand = new Command22("wait").description("Block until --text appears in the session's rendered screen, or exit non-zero on timeout.").argument("<box>", "box ref").requiredOption("--text <str>", "substring to wait for").option("--timeout <ms>", "wall-clock cap in milliseconds (default: 5000)").option(sessionOption[0], sessionOption[1]).option("--json", "emit a JSON envelope { matched, elapsedMs, session, screen? }").action(async (boxRef, opts) => {
7703
7978
  try {
7704
7979
  const box = await resolveBoxOrExit(boxRef);
7705
7980
  const provider = await providerForBox(box);
@@ -7732,14 +8007,14 @@ var driveWaitCommand = new Command21("wait").description("Block until --text app
7732
8007
  }) + "\n"
7733
8008
  );
7734
8009
  } else {
7735
- log26.error(`text not found within ${String(timeoutMs)}ms: ${opts.text}`);
8010
+ log27.error(`text not found within ${String(timeoutMs)}ms: ${opts.text}`);
7736
8011
  }
7737
8012
  process.exit(1);
7738
8013
  } catch (err) {
7739
8014
  handleDriveError(err);
7740
8015
  }
7741
8016
  });
7742
- var driveResizeCommand = new Command21("resize").description("Resize the tmux window to <cols> x <rows>.").argument("<box>", "box ref").argument("<cols>", "columns (positive int)").argument("<rows>", "rows (positive int)").option(sessionOption[0], sessionOption[1]).action(async (boxRef, colsStr, rowsStr, opts) => {
8017
+ var driveResizeCommand = new Command22("resize").description("Resize the tmux window to <cols> x <rows>.").argument("<box>", "box ref").argument("<cols>", "columns (positive int)").argument("<rows>", "rows (positive int)").option(sessionOption[0], sessionOption[1]).action(async (boxRef, colsStr, rowsStr, opts) => {
7743
8018
  try {
7744
8019
  const box = await resolveBoxOrExit(boxRef);
7745
8020
  const provider = await providerForBox(box);
@@ -7759,8 +8034,8 @@ driveCommand.addCommand(driveWaitCommand);
7759
8034
  driveCommand.addCommand(driveResizeCommand);
7760
8035
  function handleDriveError(err) {
7761
8036
  if (err instanceof SessionNotFoundError) {
7762
- log26.error(err.message);
7763
- log26.info("start an agent first (e.g. `agentbox claude <box>`) or pass --session.");
8037
+ log27.error(err.message);
8038
+ log27.info("start an agent first (e.g. `agentbox claude <box>`) or pass --session.");
7764
8039
  process.exit(2);
7765
8040
  }
7766
8041
  handleLifecycleError(err);
@@ -7784,8 +8059,8 @@ function sleep2(ms) {
7784
8059
  }
7785
8060
 
7786
8061
  // src/commands/fork.ts
7787
- import { log as log27 } from "@clack/prompts";
7788
- import { Command as Command22 } from "commander";
8062
+ import { log as log28 } from "@clack/prompts";
8063
+ import { Command as Command23 } from "commander";
7789
8064
  import { existsSync as existsSync4, readdirSync, statSync } from "fs";
7790
8065
  import { homedir as homedir8 } from "os";
7791
8066
  import { join as join10 } from "path";
@@ -7837,7 +8112,7 @@ function resolveAttachArgs(attachIn) {
7837
8112
  if (attachIn === "same") return ["--attach-in", "same"];
7838
8113
  return detectHostTerminal() === "unknown" ? ["--no-attach"] : ["--attach-in", attachIn];
7839
8114
  }
7840
- var forkCommand = new Command22("fork").description(
8115
+ var forkCommand = new Command23("fork").description(
7841
8116
  "Fork the current host agent session into a new box and resume it there. Opens the box in a new terminal tab under iTerm/tmux; otherwise starts it in the background."
7842
8117
  ).option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option(
7843
8118
  "--agent <name>",
@@ -7853,19 +8128,19 @@ var forkCommand = new Command22("fork").description(
7853
8128
  "auto-approve agentbox.yaml's carry: block (fork skips carry by default \u2014 it does not silently re-copy host files into the new box)"
7854
8129
  ).action(async (opts) => {
7855
8130
  if ((process.env.AGENTBOX_RELAY_URL ?? "").trim().length > 0) {
7856
- log27.error(
8131
+ log28.error(
7857
8132
  "agentbox fork runs on the host only: it teleports a host agent session into a new box. You appear to be inside a box (AGENTBOX_RELAY_URL is set) \u2014 box\u2192box fork is not supported yet."
7858
8133
  );
7859
8134
  process.exit(2);
7860
8135
  }
7861
8136
  const agent = opts.agent?.trim() || "claude";
7862
8137
  if (!FORK_AGENTS.includes(agent)) {
7863
- log27.error(`--agent: expected one of ${FORK_AGENTS.join(", ")}, got "${opts.agent ?? ""}"`);
8138
+ log28.error(`--agent: expected one of ${FORK_AGENTS.join(", ")}, got "${opts.agent ?? ""}"`);
7864
8139
  process.exit(2);
7865
8140
  }
7866
8141
  const attachIn = opts.attachIn ?? "tab";
7867
8142
  if (!FORK_ATTACH_VALUES.includes(attachIn)) {
7868
- log27.error(`--attach-in: expected one of ${FORK_ATTACH_VALUES.join(", ")}, got "${attachIn}"`);
8143
+ log28.error(`--attach-in: expected one of ${FORK_ATTACH_VALUES.join(", ")}, got "${attachIn}"`);
7869
8144
  process.exit(2);
7870
8145
  }
7871
8146
  const provider = opts.provider?.trim();
@@ -7873,7 +8148,7 @@ var forkCommand = new Command22("fork").description(
7873
8148
  try {
7874
8149
  sessionArgs = resolveSessionArgs(agent, opts);
7875
8150
  } catch (err) {
7876
- log27.error(err instanceof Error ? err.message : String(err));
8151
+ log28.error(err instanceof Error ? err.message : String(err));
7877
8152
  process.exit(2);
7878
8153
  }
7879
8154
  const subArgv = [
@@ -7891,8 +8166,8 @@ var forkCommand = new Command22("fork").description(
7891
8166
  });
7892
8167
 
7893
8168
  // src/commands/install.ts
7894
- import { intro as intro5, log as log28, outro as outro6 } from "@clack/prompts";
7895
- import { Command as Command23 } from "commander";
8169
+ import { intro as intro5, log as log29, outro as outro6 } from "@clack/prompts";
8170
+ import { Command as Command24 } from "commander";
7896
8171
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync, writeFileSync as writeFileSync3 } from "fs";
7897
8172
  import { homedir as homedir9 } from "os";
7898
8173
  import { dirname, join as join11, resolve as resolve2 } from "path";
@@ -7941,7 +8216,7 @@ function writableReason(target, force) {
7941
8216
  }
7942
8217
  return force ? "forced" : "skip";
7943
8218
  }
7944
- var installCommand = new Command23("install").description(
8219
+ var installCommand = new Command24("install").description(
7945
8220
  "Install AgentBox's host-side /agentbox fork command into Claude (~/.claude/skills), and \u2014 when detected \u2014 into Codex (~/.codex/prompts) and OpenCode (~/.config/opencode/commands). Idempotent."
7946
8221
  ).option("--force", "overwrite existing files even if not AgentBox-managed").option("--dry-run", "print what would be written without changing anything").action((opts) => {
7947
8222
  intro5("Installing AgentBox host commands...");
@@ -7951,7 +8226,7 @@ var installCommand = new Command23("install").description(
7951
8226
  try {
7952
8227
  srcDir = resolveHostSkillsDir();
7953
8228
  } catch (err) {
7954
- log28.error(err instanceof Error ? err.message : String(err));
8229
+ log29.error(err instanceof Error ? err.message : String(err));
7955
8230
  process.exit(1);
7956
8231
  }
7957
8232
  const written = [];
@@ -7959,19 +8234,19 @@ var installCommand = new Command23("install").description(
7959
8234
  for (const t of installTargets()) {
7960
8235
  const src = join11(srcDir, t.src);
7961
8236
  if (!existsSync5(src)) {
7962
- log28.warn(`bundled file missing (skipped): ${src}`);
8237
+ log29.warn(`bundled file missing (skipped): ${src}`);
7963
8238
  skipped++;
7964
8239
  continue;
7965
8240
  }
7966
8241
  if (t.gateDir && !existsSync5(t.gateDir)) continue;
7967
8242
  const reason = writableReason(t.dest, force);
7968
8243
  if (reason === "skip") {
7969
- log28.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
8244
+ log29.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
7970
8245
  skipped++;
7971
8246
  continue;
7972
8247
  }
7973
8248
  if (dryRun) {
7974
- log28.info(`would write ${t.dest} (${reason})`);
8249
+ log29.info(`would write ${t.dest} (${reason})`);
7975
8250
  written.push(t.dest);
7976
8251
  continue;
7977
8252
  }
@@ -7991,7 +8266,7 @@ var installCommand = new Command23("install").description(
7991
8266
  });
7992
8267
 
7993
8268
  // src/commands/git.ts
7994
- import { Command as Command24 } from "commander";
8269
+ import { Command as Command25 } from "commander";
7995
8270
  var WORKSPACE = "/workspace";
7996
8271
  var TOKEN_TTL_MS = 12e4;
7997
8272
  async function runInBox(box, argv) {
@@ -8023,7 +8298,7 @@ function buildPredictedGhPrParams(ghArgs) {
8023
8298
  async function exitWith(code) {
8024
8299
  process.exit(code);
8025
8300
  }
8026
- var pushCommand = new Command24("push").description("Push the box's branch via the host relay (host creds, no prompt)").argument("<box>", "box ref: project index, id, id prefix, name, or container").argument("[args...]", "extra flags forwarded to `agentbox-ctl git push` (e.g. --force-with-lease, --tags)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
8301
+ var pushCommand = new Command25("push").description("Push the box's branch via the host relay (host creds, no prompt)").argument("<box>", "box ref: project index, id, id prefix, name, or container").argument("[args...]", "extra flags forwarded to `agentbox-ctl git push` (e.g. --force-with-lease, --tags)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
8027
8302
  try {
8028
8303
  const box = await resolveBoxOrExit(boxRef);
8029
8304
  const predicted = buildPredictedGitParams(opts.remote, args);
@@ -8036,7 +8311,7 @@ var pushCommand = new Command24("push").description("Push the box's branch via t
8036
8311
  handleLifecycleError(err);
8037
8312
  }
8038
8313
  });
8039
- var fetchCommand = new Command24("fetch").description("Fetch via the host relay (refs land in the shared .git)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `agentbox-ctl git fetch` (e.g. --prune)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
8314
+ var fetchCommand = new Command25("fetch").description("Fetch via the host relay (refs land in the shared .git)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `agentbox-ctl git fetch` (e.g. --prune)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
8040
8315
  try {
8041
8316
  const box = await resolveBoxOrExit(boxRef);
8042
8317
  const predicted = buildPredictedGitParams(opts.remote, args);
@@ -8049,7 +8324,7 @@ var fetchCommand = new Command24("fetch").description("Fetch via the host relay
8049
8324
  handleLifecycleError(err);
8050
8325
  }
8051
8326
  });
8052
- var pullCommand = new Command24("pull").description(
8327
+ var pullCommand = new Command25("pull").description(
8053
8328
  "Fetch via the relay then merge in /workspace. With <branch>: first `git checkout <branch>` so the box switches base branch and pulls latest \u2014 useful for reusing a box on a new task."
8054
8329
  ).argument("<box>", "box ref").argument("[branch]", "optional branch to switch to before pulling (e.g. main)").argument("[args...]", "extra flags forwarded to `agentbox-ctl git pull`").option("--remote <name>", "remote name (default: origin)").option("--ff-only", "pass --ff-only to the in-box merge").allowExcessArguments(true).allowUnknownOption(true).action(
8055
8330
  async (boxRef, branch, args, opts) => {
@@ -8071,7 +8346,7 @@ var pullCommand = new Command24("pull").description(
8071
8346
  }
8072
8347
  }
8073
8348
  );
8074
- var checkoutCommand = new Command24("checkout").description("Change the box's working branch (runs `git checkout <branch>` in /workspace)").argument("<box>", "box ref").argument("<branch>", "branch to check out inside the box").argument("[args...]", "extra flags forwarded to `git checkout`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, branch, args) => {
8349
+ var checkoutCommand = new Command25("checkout").description("Change the box's working branch (runs `git checkout <branch>` in /workspace)").argument("<box>", "box ref").argument("<branch>", "branch to check out inside the box").argument("[args...]", "extra flags forwarded to `git checkout`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, branch, args) => {
8075
8350
  try {
8076
8351
  const box = await resolveBoxOrExit(boxRef);
8077
8352
  await exitWith(await runAndStream(box, ["git", "checkout", branch, ...args]));
@@ -8079,7 +8354,7 @@ var checkoutCommand = new Command24("checkout").description("Change the box's wo
8079
8354
  handleLifecycleError(err);
8080
8355
  }
8081
8356
  });
8082
- var statusCommand = new Command24("status").description("Run `git status` in the box's /workspace (read-only, no relay)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `git status`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
8357
+ var statusCommand = new Command25("status").description("Run `git status` in the box's /workspace (read-only, no relay)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `git status`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
8083
8358
  try {
8084
8359
  const box = await resolveBoxOrExit(boxRef);
8085
8360
  await exitWith(await runAndStream(box, ["git", "status", ...args]));
@@ -8098,21 +8373,18 @@ var PR_OP_DESCRIPTIONS = {
8098
8373
  close: "Close a PR.",
8099
8374
  reopen: "Reopen a PR."
8100
8375
  };
8101
- function injectPrCreateHead(op, box, args) {
8102
- if (op !== "create") return args;
8103
- if (args.some((a) => a === "--head" || a.startsWith("--head="))) return args;
8376
+ function injectPrCreateHead2(op, box, args) {
8104
8377
  const rootWt = (box.gitWorktrees ?? []).find((w) => w.kind === "root");
8105
- if (!rootWt) return args;
8106
- return ["--head", rootWt.branch, ...args];
8378
+ return injectPrCreateHead(op, rootWt?.branch, args);
8107
8379
  }
8108
8380
  function buildPrSubcommand(op) {
8109
- return new Command24(op).description(PR_OP_DESCRIPTIONS[op]).argument("<box>", "box ref").argument(
8381
+ return new Command25(op).description(PR_OP_DESCRIPTIONS[op]).argument("<box>", "box ref").argument(
8110
8382
  "[args...]",
8111
8383
  "extra flags forwarded to `gh pr <op>` (e.g. --title, --body, --label, --draft, --json)"
8112
8384
  ).allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
8113
8385
  try {
8114
8386
  const box = await resolveBoxOrExit(boxRef);
8115
- const ghArgs = injectPrCreateHead(op, box, args);
8387
+ const ghArgs = injectPrCreateHead2(op, box, args);
8116
8388
  const predicted = buildPredictedGhPrParams(ghArgs);
8117
8389
  const tokenArgs = await hostInitiatedArgs(box.id, `gh.pr.${op}`, predicted);
8118
8390
  const argv = ["agentbox-ctl", "gh", "pr", op, ...tokenArgs, ...ghArgs];
@@ -8122,18 +8394,18 @@ function buildPrSubcommand(op) {
8122
8394
  }
8123
8395
  });
8124
8396
  }
8125
- var prCommand = new Command24("pr").description(
8397
+ var prCommand = new Command25("pr").description(
8126
8398
  "PR operations against a box's branch via the host `gh` CLI"
8127
8399
  );
8128
8400
  for (const op of GH_PR_OPS) {
8129
8401
  const sub = buildPrSubcommand(op);
8130
8402
  prCommand.addCommand(sub, op === "create" ? { isDefault: true } : void 0);
8131
8403
  }
8132
- var gitCommand = new Command24("git").description("Run git / gh pr operations against a box from the host").addCommand(pushCommand).addCommand(fetchCommand).addCommand(pullCommand).addCommand(checkoutCommand).addCommand(statusCommand).addCommand(prCommand);
8404
+ var gitCommand = new Command25("git").description("Run git / gh pr operations against a box from the host").addCommand(pushCommand).addCommand(fetchCommand).addCommand(pullCommand).addCommand(checkoutCommand).addCommand(statusCommand).addCommand(prCommand);
8133
8405
 
8134
8406
  // src/commands/list.ts
8135
- import { log as log29 } from "@clack/prompts";
8136
- import { Command as Command25 } from "commander";
8407
+ import { log as log30 } from "@clack/prompts";
8408
+ import { Command as Command26 } from "commander";
8137
8409
  import { pathToFileURL } from "url";
8138
8410
 
8139
8411
  // src/hyperlink.ts
@@ -8303,10 +8575,10 @@ async function buildListText(all) {
8303
8575
  ${table}`;
8304
8576
  }
8305
8577
  var listCommand2 = withWatchOptions(
8306
- new Command25("list").alias("ls").description("List agent boxes in the current project (-g for all)").option("-j, --json", "machine-readable JSON output").option("-g, --global", "include boxes from all projects")
8578
+ new Command26("list").alias("ls").description("List agent boxes in the current project (-g for all)").option("-j, --json", "machine-readable JSON output").option("-g, --global", "include boxes from all projects")
8307
8579
  ).action(async (opts) => {
8308
8580
  if (opts.json && opts.watch) {
8309
- log29.error("cannot combine --json with --watch");
8581
+ log30.error("cannot combine --json with --watch");
8310
8582
  process.exit(2);
8311
8583
  }
8312
8584
  const all = opts.global ?? false;
@@ -8323,11 +8595,11 @@ var listCommand2 = withWatchOptions(
8323
8595
  });
8324
8596
 
8325
8597
  // src/commands/logs.ts
8326
- import { log as log30 } from "@clack/prompts";
8327
- import { Command as Command26 } from "commander";
8598
+ import { log as log31 } from "@clack/prompts";
8599
+ import { Command as Command27 } from "commander";
8328
8600
  import { spawn as spawn3 } from "child_process";
8329
8601
  var DAEMON_LOG_PATH = "/var/log/agentbox/ctl-daemon.log";
8330
- var logsCommand = new Command26("logs").description("Print recent log lines from a box service; -f to stream").argument(
8602
+ var logsCommand = new Command27("logs").description("Print recent log lines from a box service; -f to stream").argument(
8331
8603
  "[box]",
8332
8604
  "box ref (optional when cwd has exactly 1 box): project index, id, id prefix, name, or container"
8333
8605
  ).argument("[service]", "service name from agentbox.yaml").option("-n, --tail <n>", "how many recent lines to print first", "200").option("-f, --follow", "keep the connection open and stream new lines").option(
@@ -8345,9 +8617,9 @@ var logsCommand = new Command26("logs").description("Print recent log lines from
8345
8617
  service = boxArg;
8346
8618
  }
8347
8619
  if (!service && !opts.daemon) {
8348
- log30.error("missing <service> argument");
8349
- log30.info("usage: agentbox logs [box] <service> [-n N] [-f]");
8350
- log30.info(" agentbox logs [box] --daemon [-n N] [-f]");
8620
+ log31.error("missing <service> argument");
8621
+ log31.info("usage: agentbox logs [box] <service> [-n N] [-f]");
8622
+ log31.info(" agentbox logs [box] --daemon [-n N] [-f]");
8351
8623
  process.exit(2);
8352
8624
  }
8353
8625
  const box = await resolveBoxOrExit(idOrName);
@@ -8358,7 +8630,7 @@ var logsCommand = new Command26("logs").description("Print recent log lines from
8358
8630
  if (!opts.follow) {
8359
8631
  const proc = await provider.exec(box, args, { user: "vscode" });
8360
8632
  if (proc.exitCode !== 0) {
8361
- log30.error(
8633
+ log31.error(
8362
8634
  `${opts.daemon ? "daemon log" : "agentbox-ctl logs"} failed: ${proc.stderr || proc.stdout}`
8363
8635
  );
8364
8636
  process.exit(1);
@@ -8392,7 +8664,10 @@ var logsCommand = new Command26("logs").description("Print recent log lines from
8392
8664
  });
8393
8665
  const [argv0, ...rest] = spec.argv;
8394
8666
  if (!argv0) throw new Error("provider.buildAttach returned an empty argv");
8395
- const child = spawn3(argv0, rest, { stdio: ["ignore", "inherit", "inherit"] });
8667
+ const child = spawn3(argv0, rest, {
8668
+ stdio: ["ignore", "inherit", "inherit"],
8669
+ env: spec.env ? { ...process.env, ...spec.env } : process.env
8670
+ });
8396
8671
  const cleanup = async () => {
8397
8672
  if (spec.cleanup) await spec.cleanup();
8398
8673
  };
@@ -8411,12 +8686,12 @@ var logsCommand = new Command26("logs").description("Print recent log lines from
8411
8686
  });
8412
8687
 
8413
8688
  // src/commands/open.ts
8414
- import { log as log31 } from "@clack/prompts";
8689
+ import { log as log32 } from "@clack/prompts";
8415
8690
  import { execa as execa2 } from "execa";
8416
8691
  import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
8417
8692
  import { homedir as homedir10 } from "os";
8418
8693
  import { join as join12 } from "path";
8419
- import { Command as Command27 } from "commander";
8694
+ import { Command as Command28 } from "commander";
8420
8695
 
8421
8696
  // src/commands/path.ts
8422
8697
  async function runPath(box, opts) {
@@ -8438,7 +8713,7 @@ async function runPath(box, opts) {
8438
8713
  }
8439
8714
 
8440
8715
  // src/commands/open.ts
8441
- var openCommand = new Command27("open").description("Open a box's /workspace in Finder (docker: rsync'd snapshot; cloud: sshfs mount)").argument(
8716
+ var openCommand = new Command28("open").description("Open a box's /workspace in Finder (docker: rsync'd snapshot; cloud: sshfs mount)").argument(
8442
8717
  "[box]",
8443
8718
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
8444
8719
  ).option("--no-refresh", "skip the rsync; open whatever's already on disk (docker only)").option(
@@ -8517,10 +8792,10 @@ async function runCloudOpen(box, provider, opts) {
8517
8792
  if (!existsSync6(mountRoot)) {
8518
8793
  mkdirSync4(mountRoot, { recursive: true, mode: 493 });
8519
8794
  } else if (await isMounted(mountRoot)) {
8520
- log31.info(`re-mounting (stale mount detected at ${mountRoot})`);
8795
+ log32.info(`re-mounting (stale mount detected at ${mountRoot})`);
8521
8796
  await tryUnmount(mountRoot);
8522
8797
  }
8523
- log31.info(`mounting ${alias}:/workspace at ${mountRoot}`);
8798
+ log32.info(`mounting ${alias}:/workspace at ${mountRoot}`);
8524
8799
  const mount = await execa2(
8525
8800
  sshfsBin,
8526
8801
  [
@@ -8567,8 +8842,8 @@ async function tryUnmount(path) {
8567
8842
  }
8568
8843
 
8569
8844
  // src/commands/pause.ts
8570
- import { Command as Command28 } from "commander";
8571
- var pauseCommand = new Command28("pause").description(
8845
+ import { Command as Command29 } from "commander";
8846
+ var pauseCommand = new Command29("pause").description(
8572
8847
  "Pause a box. Docker: `docker pause` (cgroup freeze \u2014 sub-second resume). Cloud: backend.pause (Daytona archive \u2014 cold storage; resume is slower but uses no quota while archived)."
8573
8848
  ).argument(
8574
8849
  "[box]",
@@ -8591,8 +8866,8 @@ var pauseCommand = new Command28("pause").description(
8591
8866
  });
8592
8867
 
8593
8868
  // src/commands/prepare.ts
8594
- import { intro as intro6, log as log32, spinner as spinner7 } from "@clack/prompts";
8595
- import { Command as Command29 } from "commander";
8869
+ import { intro as intro6, log as log33, spinner as spinner7 } from "@clack/prompts";
8870
+ import { Command as Command30 } from "commander";
8596
8871
  async function dockerStatus() {
8597
8872
  let img;
8598
8873
  try {
@@ -8650,7 +8925,7 @@ async function renderDocker(status) {
8650
8925
  }
8651
8926
  async function daytonaStatus() {
8652
8927
  try {
8653
- const mod = await import("./dist-L4LCG5SJ.js");
8928
+ const mod = await import("./dist-CX5CGVEB.js");
8654
8929
  return await mod.getDaytonaStatus();
8655
8930
  } catch (err) {
8656
8931
  return { configured: false, reason: err instanceof Error ? err.message.split("\n")[0] : String(err) };
@@ -8709,11 +8984,11 @@ async function showStatus(opts) {
8709
8984
  }
8710
8985
  process.stdout.write(lines.join("\n") + "\n");
8711
8986
  }
8712
- var prepareCommand = new Command29("prepare").description(
8987
+ var prepareCommand = new Command30("prepare").description(
8713
8988
  "Build base sandbox images / snapshots, or show what is already prepared across providers."
8714
8989
  ).option(
8715
8990
  "-p, --provider <name>",
8716
- "provider to prepare (docker | daytona | hetzner). Omit for status-only."
8991
+ "provider to prepare (docker | daytona | hetzner | vercel). Omit for status-only."
8717
8992
  ).option(
8718
8993
  "-n, --name <name>",
8719
8994
  "snapshot name (Daytona only; default: agentbox-base-<timestamp>)"
@@ -8738,7 +9013,7 @@ var prepareCommand = new Command29("prepare").description(
8738
9013
  }
8739
9014
  const provider = await getProvider(providerName);
8740
9015
  if (typeof provider.prepare !== "function") {
8741
- log32.error(`provider '${providerName}' does not implement prepare`);
9016
+ log33.error(`provider '${providerName}' does not implement prepare`);
8742
9017
  process.exit(1);
8743
9018
  }
8744
9019
  const sp = spinner7();
@@ -8759,10 +9034,10 @@ var prepareCommand = new Command29("prepare").description(
8759
9034
  result.snapshotName,
8760
9035
  process.cwd()
8761
9036
  );
8762
- log32.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
9037
+ log33.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
8763
9038
  } catch (err) {
8764
9039
  const msg = err instanceof Error ? err.message : String(err);
8765
- log32.warn(
9040
+ log33.warn(
8766
9041
  `prepared snapshot '${result.snapshotName}', but failed to pin it into the project config: ${msg}
8767
9042
  Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.`
8768
9043
  );
@@ -8772,7 +9047,7 @@ Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.
8772
9047
  }
8773
9048
  process.stdout.write("\n");
8774
9049
  await showStatus({ onlyProvider: providerName });
8775
- log32.info(
9050
+ log33.info(
8776
9051
  "tip: install the agentbox host skill so Claude Code on this machine can drive AgentBox for you:\n npx skills add https://github.com/madarco/agentbox --skill agentbox"
8777
9052
  );
8778
9053
  } catch (err) {
@@ -8802,8 +9077,8 @@ function describeError(err) {
8802
9077
  }
8803
9078
 
8804
9079
  // src/commands/prune.ts
8805
- import { confirm as confirm14, isCancel as isCancel15, log as log33 } from "@clack/prompts";
8806
- import { Command as Command30 } from "commander";
9080
+ import { confirm as confirm14, isCancel as isCancel15, log as log34 } from "@clack/prompts";
9081
+ import { Command as Command31 } from "commander";
8807
9082
  function totalRemovals(r, projectConfigs) {
8808
9083
  return r.removedRecords.length + r.removedContainers.length + r.removedVolumes.length + r.removedSnapshotDirs.length + r.removedBoxDirs.length + projectConfigs.length;
8809
9084
  }
@@ -8849,20 +9124,20 @@ async function liveProjectRoots() {
8849
9124
  return [];
8850
9125
  }
8851
9126
  }
8852
- var pruneCommand = new Command30("prune").description("Clean up orphan state.json records (and with --all, orphan docker resources)").option("--dry-run", "show what would be removed, don't change anything").option(
9127
+ var pruneCommand = new Command31("prune").description("Clean up orphan state.json records (and with --all, orphan docker resources)").option("--dry-run", "show what would be removed, don't change anything").option(
8853
9128
  "--all",
8854
9129
  "also remove orphan agentbox-* containers, volumes, snapshot dirs, and orphan per-project config dirs"
8855
9130
  ).option("-y, --yes", "skip the confirmation prompt").option(
8856
9131
  "--provider <name>",
8857
- "restrict prune to a specific provider (docker | daytona). For daytona, lists cloud sandboxes that are not in this CLI's state.json and offers to delete them."
9132
+ "restrict prune to a specific provider (docker | daytona | hetzner | vercel). For cloud providers, lists sandboxes that are not in this CLI's state.json and offers to delete them."
8858
9133
  ).action(async (opts) => {
8859
9134
  try {
8860
- if (opts.provider === "daytona") {
8861
- await pruneDaytona(opts);
9135
+ if (opts.provider !== void 0 && isCloudPruneProvider(opts.provider)) {
9136
+ await pruneCloud(opts.provider, opts);
8862
9137
  return;
8863
9138
  }
8864
9139
  if (opts.provider !== void 0 && opts.provider !== "docker") {
8865
- log33.error(`unknown provider '${opts.provider}'; expected docker or daytona`);
9140
+ log34.error(`unknown provider '${opts.provider}'; expected docker, daytona, hetzner, or vercel`);
8866
9141
  process.exit(2);
8867
9142
  }
8868
9143
  const dryRun = opts.dryRun ?? false;
@@ -8875,13 +9150,13 @@ var pruneCommand = new Command30("prune").description("Clean up orphan state.jso
8875
9150
  process.stdout.write("nothing to prune\n");
8876
9151
  return;
8877
9152
  }
8878
- log33.info(`would remove:
9153
+ log34.info(`would remove:
8879
9154
  ${summary(preview, previewProjects)}`);
8880
9155
  if (dryRun) return;
8881
9156
  if (!opts.yes) {
8882
9157
  const ok = await confirm14({ message: "Proceed with prune?", initialValue: true });
8883
9158
  if (isCancel15(ok) || !ok) {
8884
- log33.info("cancelled");
9159
+ log34.info("cancelled");
8885
9160
  return;
8886
9161
  }
8887
9162
  }
@@ -8894,17 +9169,31 @@ ${summary(result, removedProjects)}
8894
9169
  handleLifecycleError(err);
8895
9170
  }
8896
9171
  });
8897
- async function pruneDaytona(opts) {
9172
+ var CLOUD_PRUNE_PROVIDERS = ["daytona", "hetzner", "vercel"];
9173
+ function isCloudPruneProvider(name) {
9174
+ return CLOUD_PRUNE_PROVIDERS.includes(name);
9175
+ }
9176
+ async function cloudBackendFor(provider) {
9177
+ switch (provider) {
9178
+ case "daytona":
9179
+ return (await import("./dist-CX5CGVEB.js")).daytonaBackend;
9180
+ case "hetzner":
9181
+ return (await import("./dist-GDHP34ZK.js")).hetznerBackend;
9182
+ case "vercel":
9183
+ return (await import("./dist-XML54CNB.js")).vercelBackend;
9184
+ }
9185
+ }
9186
+ async function pruneCloud(provider, opts) {
8898
9187
  const dryRun = opts.dryRun ?? false;
8899
- const { daytonaBackend: daytonaBackend2 } = await import("./dist-L4LCG5SJ.js");
8900
- if (!daytonaBackend2.list) {
8901
- log33.error("daytona backend doesn't expose `list()`; cannot enumerate sandboxes for prune");
9188
+ const backend = await cloudBackendFor(provider);
9189
+ if (!backend.list) {
9190
+ log34.error(`${provider} backend doesn't expose \`list()\`; cannot enumerate sandboxes for prune`);
8902
9191
  process.exit(2);
8903
9192
  }
8904
- const [remote, state] = await Promise.all([daytonaBackend2.list(), readState()]);
9193
+ const [remote, state] = await Promise.all([backend.list(), readState()]);
8905
9194
  const knownIds = /* @__PURE__ */ new Set();
8906
9195
  for (const b of state.boxes) {
8907
- if ((b.provider ?? "docker") === "daytona" && b.cloud?.sandboxId) {
9196
+ if ((b.provider ?? "docker") === provider && b.cloud?.sandboxId) {
8908
9197
  knownIds.add(b.cloud.sandboxId);
8909
9198
  }
8910
9199
  }
@@ -8914,10 +9203,11 @@ async function pruneDaytona(opts) {
8914
9203
  return friendly.length > 0;
8915
9204
  });
8916
9205
  if (orphans.length === 0) {
8917
- process.stdout.write("no daytona orphans found\n");
9206
+ process.stdout.write(`no ${provider} orphans found
9207
+ `);
8918
9208
  return;
8919
9209
  }
8920
- log33.info(`found ${String(orphans.length)} daytona sandbox(es) not in this CLI's state:`);
9210
+ log34.info(`found ${String(orphans.length)} ${provider} sandbox(es) not in this CLI's state:`);
8921
9211
  for (const sb of orphans) {
8922
9212
  const parts = [sb.sandboxId];
8923
9213
  if (sb.name) parts.push(sb.name);
@@ -8933,7 +9223,7 @@ async function pruneDaytona(opts) {
8933
9223
  initialValue: false
8934
9224
  });
8935
9225
  if (isCancel15(ok) || !ok) {
8936
- log33.info("cancelled");
9226
+ log34.info("cancelled");
8937
9227
  return;
8938
9228
  }
8939
9229
  }
@@ -8941,34 +9231,34 @@ async function pruneDaytona(opts) {
8941
9231
  let failed = 0;
8942
9232
  for (const sb of orphans) {
8943
9233
  try {
8944
- await daytonaBackend2.destroy({ sandboxId: sb.sandboxId });
9234
+ await backend.destroy({ sandboxId: sb.sandboxId });
8945
9235
  deleted++;
8946
9236
  } catch (err) {
8947
9237
  failed++;
8948
- log33.warn(
9238
+ log34.warn(
8949
9239
  `delete ${sb.sandboxId} failed: ${err instanceof Error ? err.message : String(err)}`
8950
9240
  );
8951
9241
  }
8952
9242
  }
8953
9243
  process.stdout.write(
8954
- `daytona prune: deleted ${String(deleted)}, failed ${String(failed)}
9244
+ `${provider} prune: deleted ${String(deleted)}, failed ${String(failed)}
8955
9245
  `
8956
9246
  );
8957
9247
  }
8958
9248
 
8959
9249
  // src/commands/queue.ts
8960
9250
  import { readFile as readFile4, stat as stat5 } from "fs/promises";
8961
- import { intro as intro7, log as log34, outro as outro7 } from "@clack/prompts";
8962
- import { Command as Command31 } from "commander";
9251
+ import { intro as intro7, log as log35, outro as outro7 } from "@clack/prompts";
9252
+ import { Command as Command32 } from "commander";
8963
9253
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
8964
- var queueCommand = new Command31("queue").description("Inspect and manage background `agentbox claude|codex|opencode -i` jobs");
8965
- var queueListCommand = new Command31("list").description("List queued, running, and (with --all) terminal background jobs").option("--all", "include done/failed/cancelled jobs (default: hide terminal)").action(async (opts) => {
9254
+ var queueCommand = new Command32("queue").description("Inspect and manage background `agentbox claude|codex|opencode -i` jobs");
9255
+ var queueListCommand = new Command32("list").description("List queued, running, and (with --all) terminal background jobs").option("--all", "include done/failed/cancelled jobs (default: hide terminal)").action(async (opts) => {
8966
9256
  const jobs = await loadQueue();
8967
9257
  const cfg = await loadQueueConfig();
8968
9258
  const visible = opts.all === true ? jobs : jobs.filter((j) => !TERMINAL_STATUSES.has(j.status));
8969
9259
  if (visible.length === 0) {
8970
- log34.info(opts.all ? "no queued jobs." : "no active queued jobs (--all to see terminal).");
8971
- log34.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
9260
+ log35.info(opts.all ? "no queued jobs." : "no active queued jobs (--all to see terminal).");
9261
+ log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
8972
9262
  return;
8973
9263
  }
8974
9264
  const rows = visible.map((j) => ({
@@ -8993,12 +9283,12 @@ var queueListCommand = new Command31("list").description("List queued, running,
8993
9283
  headers.map((h, i) => pad3(String(r[h]), widths[i])).join(" ") + "\n"
8994
9284
  );
8995
9285
  }
8996
- log34.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
9286
+ log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
8997
9287
  });
8998
- var queueShowCommand = new Command31("show").description("Dump a job manifest and tail its log").argument("<id>", "queue job id (from `agentbox queue list`)").option("--tail <n>", "lines of log to print (default: 50)", "50").action(async (id, opts) => {
9288
+ var queueShowCommand = new Command32("show").description("Dump a job manifest and tail its log").argument("<id>", "queue job id (from `agentbox queue list`)").option("--tail <n>", "lines of log to print (default: 50)", "50").action(async (id, opts) => {
8999
9289
  const job = await readJob(id);
9000
9290
  if (!job) {
9001
- log34.error(`no job with id ${id}`);
9291
+ log35.error(`no job with id ${id}`);
9002
9292
  process.exit(1);
9003
9293
  }
9004
9294
  process.stdout.write(JSON.stringify(job, null, 2) + "\n");
@@ -9014,18 +9304,18 @@ var queueShowCommand = new Command31("show").description("Dump a job manifest an
9014
9304
  process.stdout.write(slice.join("\n"));
9015
9305
  if (!slice.join("\n").endsWith("\n")) process.stdout.write("\n");
9016
9306
  } catch {
9017
- log34.info(`(no log at ${job.logPath} yet)`);
9307
+ log35.info(`(no log at ${job.logPath} yet)`);
9018
9308
  }
9019
9309
  });
9020
- var queueCancelCommand = new Command31("cancel").description("Cancel a queued job; running jobs are NOT killed \u2014 use `agentbox destroy` instead").argument("<id>", "queue job id (from `agentbox queue list`)").action(async (id) => {
9310
+ var queueCancelCommand = new Command32("cancel").description("Cancel a queued job; running jobs are NOT killed \u2014 use `agentbox destroy` instead").argument("<id>", "queue job id (from `agentbox queue list`)").action(async (id) => {
9021
9311
  intro7(`Cancelling queue job ${id}...`);
9022
9312
  const job = await readJob(id);
9023
9313
  if (!job) {
9024
- log34.error(`no job with id ${id}`);
9314
+ log35.error(`no job with id ${id}`);
9025
9315
  process.exit(1);
9026
9316
  }
9027
9317
  if (job.status !== "queued") {
9028
- log34.error(
9318
+ log35.error(
9029
9319
  `job ${id} is ${job.status}; cancel only flips 'queued' \u2192 'cancelled'.` + (job.status === "running" ? ` Use 'agentbox destroy ${job.boxName || id}' to stop the box.` : "")
9030
9320
  );
9031
9321
  process.exit(1);
@@ -9039,13 +9329,13 @@ var queueCancelCommand = new Command31("cancel").description("Cancel a queued jo
9039
9329
  await writeJob(cancelled);
9040
9330
  outro7(`job ${id} cancelled`);
9041
9331
  });
9042
- var queueClearCommand = new Command31("clear").description("Sweep terminal-state manifests from ~/.agentbox/queue/").option("--done", "remove done jobs").option("--failed", "remove failed jobs").option("--cancelled", "remove cancelled jobs").option("--all", "remove every terminal-state job (done + failed + cancelled)").action(async (opts) => {
9332
+ var queueClearCommand = new Command32("clear").description("Sweep terminal-state manifests from ~/.agentbox/queue/").option("--done", "remove done jobs").option("--failed", "remove failed jobs").option("--cancelled", "remove cancelled jobs").option("--all", "remove every terminal-state job (done + failed + cancelled)").action(async (opts) => {
9043
9333
  const targets = /* @__PURE__ */ new Set();
9044
9334
  if (opts.all === true || opts.done === true) targets.add("done");
9045
9335
  if (opts.all === true || opts.failed === true) targets.add("failed");
9046
9336
  if (opts.all === true || opts.cancelled === true) targets.add("cancelled");
9047
9337
  if (targets.size === 0) {
9048
- log34.error("pick at least one of: --done, --failed, --cancelled, --all");
9338
+ log35.error("pick at least one of: --done, --failed, --cancelled, --all");
9049
9339
  process.exit(2);
9050
9340
  }
9051
9341
  const jobs = await loadQueue();
@@ -9055,7 +9345,7 @@ var queueClearCommand = new Command31("clear").description("Sweep terminal-state
9055
9345
  await deleteJob(j.id);
9056
9346
  removed += 1;
9057
9347
  }
9058
- log34.success(`removed ${String(removed)} manifest${removed === 1 ? "" : "s"}`);
9348
+ log35.success(`removed ${String(removed)} manifest${removed === 1 ? "" : "s"}`);
9059
9349
  });
9060
9350
  var QUEUE_WAIT_EVENTS = [
9061
9351
  "new-box",
@@ -9068,11 +9358,11 @@ var QUEUE_WAIT_EVENTS = [
9068
9358
  var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
9069
9359
  var DEFAULT_QUEUE_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
9070
9360
  var QUEUE_POLL_INTERVAL_MS = 500;
9071
- var queueWaitForCommand = new Command31("wait-for").description(
9361
+ var queueWaitForCommand = new Command32("wait-for").description(
9072
9362
  `Block until a queue / box event fires. <event> one of: ${QUEUE_WAIT_EVENTS.join(" | ")}.`
9073
9363
  ).argument("<event>", `target event: ${QUEUE_WAIT_EVENTS.join(" | ")}`).option("--box <ref>", "box ref (required for box-paused / box-running / box-stopped)").option("--job <id>", "queue job id (required for job-done)").option("--timeout <ms>", `wall-clock cap (default: ${String(DEFAULT_QUEUE_WAIT_TIMEOUT_MS)})`).option("--json", "emit a JSON envelope { matched, elapsedMs, ... }").action(async (eventRaw, opts) => {
9074
9364
  if (!QUEUE_WAIT_EVENTS.includes(eventRaw)) {
9075
- log34.error(`unknown event '${eventRaw}' (one of: ${QUEUE_WAIT_EVENTS.join(", ")})`);
9365
+ log35.error(`unknown event '${eventRaw}' (one of: ${QUEUE_WAIT_EVENTS.join(", ")})`);
9076
9366
  process.exit(2);
9077
9367
  }
9078
9368
  const event = eventRaw;
@@ -9092,11 +9382,11 @@ var queueWaitForCommand = new Command31("wait-for").description(
9092
9382
  if (opts.json === true) {
9093
9383
  process.stdout.write(JSON.stringify({ matched: false, event, elapsedMs }) + "\n");
9094
9384
  } else {
9095
- log34.error(`'${event}' did not occur within ${String(timeoutMs)}ms`);
9385
+ log35.error(`'${event}' did not occur within ${String(timeoutMs)}ms`);
9096
9386
  }
9097
9387
  process.exit(1);
9098
9388
  }
9099
- log34.error(err instanceof Error ? err.message : String(err));
9389
+ log35.error(err instanceof Error ? err.message : String(err));
9100
9390
  process.exit(1);
9101
9391
  }
9102
9392
  });
@@ -9193,8 +9483,8 @@ function truncate(s, max) {
9193
9483
  }
9194
9484
 
9195
9485
  // src/commands/relay.ts
9196
- import { log as log35, spinner as spinner8 } from "@clack/prompts";
9197
- import { Command as Command32 } from "commander";
9486
+ import { log as log36, spinner as spinner8 } from "@clack/prompts";
9487
+ import { Command as Command33 } from "commander";
9198
9488
  async function rehydrateFromState() {
9199
9489
  const state = await readState();
9200
9490
  await rehydrateRelayRegistry(
@@ -9234,7 +9524,7 @@ function renderStatus(s) {
9234
9524
  }
9235
9525
  return ["relay: not running", ` log: ${s.logFile}`].join("\n");
9236
9526
  }
9237
- var statusSub = new Command32("status").description("Show whether the host relay is running, with pid / port / box count").option("--json", "emit RelayStatus as JSON").action(async (opts) => {
9527
+ var statusSub = new Command33("status").description("Show whether the host relay is running, with pid / port / box count").option("--json", "emit RelayStatus as JSON").action(async (opts) => {
9238
9528
  try {
9239
9529
  const s = await getRelayStatus();
9240
9530
  if (opts.json) {
@@ -9246,7 +9536,7 @@ var statusSub = new Command32("status").description("Show whether the host relay
9246
9536
  handleLifecycleError(err);
9247
9537
  }
9248
9538
  });
9249
- var stopSub = new Command32("stop").description("Stop the host relay process (idempotent)").action(async () => {
9539
+ var stopSub = new Command33("stop").description("Stop the host relay process (idempotent)").action(async () => {
9250
9540
  try {
9251
9541
  const s = spinner8();
9252
9542
  s.start("stopping relay");
@@ -9258,7 +9548,7 @@ var stopSub = new Command32("stop").description("Stop the host relay process (id
9258
9548
  handleLifecycleError(err);
9259
9549
  }
9260
9550
  });
9261
- var startSub = new Command32("start").description("Start the host relay if not already running (idempotent)").action(async () => {
9551
+ var startSub = new Command33("start").description("Start the host relay if not already running (idempotent)").action(async () => {
9262
9552
  try {
9263
9553
  const s = spinner8();
9264
9554
  s.start("starting relay");
@@ -9269,7 +9559,7 @@ var startSub = new Command32("start").description("Start the host relay if not a
9269
9559
  handleLifecycleError(err);
9270
9560
  }
9271
9561
  });
9272
- var restartSub = new Command32("restart").description("Stop then start the host relay").action(async () => {
9562
+ var restartSub = new Command33("restart").description("Stop then start the host relay").action(async () => {
9273
9563
  try {
9274
9564
  const s = spinner8();
9275
9565
  s.start("stopping relay");
@@ -9285,29 +9575,31 @@ var restartSub = new Command32("restart").description("Stop then start the host
9285
9575
  s2.stop(`relay running on ${ep.hostUrl}`);
9286
9576
  } catch (err) {
9287
9577
  s2.stop("relay start failed");
9288
- log35.warn(err instanceof Error ? err.message : String(err));
9578
+ log36.warn(err instanceof Error ? err.message : String(err));
9289
9579
  throw err;
9290
9580
  }
9291
9581
  } catch (err) {
9292
9582
  handleLifecycleError(err);
9293
9583
  }
9294
9584
  });
9295
- var relayCommand = new Command32("relay").description("Manage the host relay process (status / stop / start / restart)").addCommand(statusSub, { isDefault: true }).addCommand(stopSub).addCommand(startSub).addCommand(restartSub);
9585
+ var relayCommand = new Command33("relay").description("Manage the host relay process (status / stop / start / restart)").addCommand(statusSub, { isDefault: true }).addCommand(stopSub).addCommand(startSub).addCommand(restartSub);
9296
9586
 
9297
9587
  // src/commands/_run-queued-job.ts
9298
- import { Command as Command33 } from "commander";
9299
- var runQueuedJobCommand = new Command33("_run-queued-job").description("internal: run a queued background agent job (do not invoke directly)").argument("<id>", "queue job id (from ~/.agentbox/queue/<id>.json)").action(async (id) => {
9300
- const log44 = openCommandLog(`queue-${id}`);
9301
- log44.write(`worker pid=${String(process.pid)} starting for job ${id}`);
9588
+ import { Command as Command34 } from "commander";
9589
+ var runQueuedJobCommand = new Command34("_run-queued-job").description("internal: run a queued background agent job (do not invoke directly)").argument("<id>", "queue job id (from ~/.agentbox/queue/<id>.json)").action(async (id) => {
9590
+ const log45 = openCommandLog(`queue-${id}`);
9591
+ log45.write(`worker pid=${String(process.pid)} starting for job ${id}`);
9302
9592
  let job = null;
9303
9593
  try {
9304
9594
  job = await readJob(id);
9305
9595
  if (!job) {
9306
- log44.write(`FATAL: no manifest at id=${id}`);
9307
- log44.close();
9596
+ log45.write(`FATAL: no manifest at id=${id}`);
9597
+ log45.close();
9308
9598
  process.exit(64);
9309
9599
  }
9310
- await runDockerJob(job, log44);
9600
+ await runDockerJob(job, log45, (boxId) => {
9601
+ if (job) job = { ...job, boxId };
9602
+ });
9311
9603
  const done = {
9312
9604
  ...job,
9313
9605
  status: "done",
@@ -9315,12 +9607,12 @@ var runQueuedJobCommand = new Command33("_run-queued-job").description("internal
9315
9607
  exitCode: 0
9316
9608
  };
9317
9609
  await writeJob(done);
9318
- log44.write(`done`);
9319
- log44.close();
9610
+ log45.write(`done`);
9611
+ log45.close();
9320
9612
  process.exit(0);
9321
9613
  } catch (err) {
9322
9614
  const msg = err instanceof Error ? err.stack ?? err.message : String(err);
9323
- log44.write(`FAIL: ${msg}`);
9615
+ log45.write(`FAIL: ${msg}`);
9324
9616
  if (job) {
9325
9617
  try {
9326
9618
  const failed = {
@@ -9334,11 +9626,11 @@ var runQueuedJobCommand = new Command33("_run-queued-job").description("internal
9334
9626
  } catch {
9335
9627
  }
9336
9628
  }
9337
- log44.close();
9629
+ log45.close();
9338
9630
  process.exit(1);
9339
9631
  }
9340
9632
  });
9341
- async function runDockerJob(job, log44) {
9633
+ async function runDockerJob(job, log45, onBoxCreated) {
9342
9634
  const opts = job.createOpts;
9343
9635
  const cfg = await loadEffectiveConfig(opts.workspace, {
9344
9636
  cliOverrides: buildOverridesFromJob(job)
@@ -9353,7 +9645,7 @@ async function runDockerJob(job, log44) {
9353
9645
  const useSnapshot = opts.hostSnapshot === false ? false : opts.hostSnapshot === true ? true : cfg.effective.box.hostSnapshot ?? false;
9354
9646
  const resolved = job.agent === "claude-code" ? await resolveClaudeAuth(process.env) : null;
9355
9647
  const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
9356
- log44.write(`creating box for agent=${job.agent}`);
9648
+ log45.write(`creating box for agent=${job.agent}`);
9357
9649
  const result = await createBox({
9358
9650
  workspacePath: opts.workspace,
9359
9651
  name: opts.name && opts.name.length > 0 ? opts.name : void 0,
@@ -9375,17 +9667,19 @@ async function runDockerJob(job, log44) {
9375
9667
  portlessStateDir: cfg.effective.portless.stateDir || void 0,
9376
9668
  limits: resolveLimits(cfg.effective.box, opts),
9377
9669
  projectRoot,
9378
- onLog: (line) => log44.write(line)
9670
+ onLog: (line) => log45.write(line)
9379
9671
  });
9380
- log44.write(`box created: ${result.record.container}`);
9672
+ log45.write(`box created: ${result.record.container}`);
9673
+ onBoxCreated(result.record.id);
9674
+ await writeJob({ ...job, boxId: result.record.id });
9381
9675
  const promptedArgs = buildPromptArgs(job.agent, job.prompt, job.agentArgs);
9382
9676
  if (job.agent === "claude-code") {
9383
- log44.write(`checking plugin native deps`);
9677
+ log45.write(`checking plugin native deps`);
9384
9678
  await rebuildPluginNativeDeps(result.record.container, {
9385
9679
  volume: result.record.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME,
9386
- onProgress: (line) => log44.write(line)
9680
+ onProgress: (line) => log45.write(line)
9387
9681
  });
9388
- log44.write(`starting claude session`);
9682
+ log45.write(`starting claude session`);
9389
9683
  await startClaudeSession({
9390
9684
  container: result.record.container,
9391
9685
  claudeArgs: promptedArgs,
@@ -9393,22 +9687,22 @@ async function runDockerJob(job, log44) {
9393
9687
  boxName: result.record.name
9394
9688
  });
9395
9689
  } else if (job.agent === "codex") {
9396
- log44.write(`checking codex`);
9690
+ log45.write(`checking codex`);
9397
9691
  await ensureCodexInstalled(result.record.container, {
9398
- onProgress: (line) => log44.write(line)
9692
+ onProgress: (line) => log45.write(line)
9399
9693
  });
9400
- log44.write(`starting codex session`);
9694
+ log45.write(`starting codex session`);
9401
9695
  await startCodexSession({
9402
9696
  container: result.record.container,
9403
9697
  codexArgs: promptedArgs,
9404
9698
  sessionName: cfg.effective.codex.sessionName
9405
9699
  });
9406
9700
  } else if (job.agent === "opencode") {
9407
- log44.write(`checking opencode`);
9701
+ log45.write(`checking opencode`);
9408
9702
  await ensureOpencodeInstalled(result.record.container, {
9409
- onProgress: (line) => log44.write(line)
9703
+ onProgress: (line) => log45.write(line)
9410
9704
  });
9411
- log44.write(`starting opencode session`);
9705
+ log45.write(`starting opencode session`);
9412
9706
  await startOpencodeSession({
9413
9707
  container: result.record.container,
9414
9708
  opencodeArgs: promptedArgs,
@@ -9440,8 +9734,8 @@ function buildOverridesFromJob(job) {
9440
9734
 
9441
9735
  // src/commands/screen.ts
9442
9736
  import { spawnSync as spawnSync3 } from "child_process";
9443
- import { log as log36 } from "@clack/prompts";
9444
- import { Command as Command34 } from "commander";
9737
+ import { log as log37 } from "@clack/prompts";
9738
+ import { Command as Command35 } from "commander";
9445
9739
  var SIGNED_URL_TTL_MIN = 1;
9446
9740
  var SIGNED_URL_TTL_MAX = 86400;
9447
9741
  function parseTtlOrExit(raw) {
@@ -9454,7 +9748,7 @@ function parseTtlOrExit(raw) {
9454
9748
  }
9455
9749
  return n;
9456
9750
  }
9457
- var screenCommand = new Command34("screen").description("Open a box's VNC (noVNC) viewer in the browser (auto-unpause/start)").argument(
9751
+ var screenCommand = new Command35("screen").description("Open a box's VNC (noVNC) viewer in the browser (auto-unpause/start)").argument(
9458
9752
  "[box]",
9459
9753
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
9460
9754
  ).option("--print", "print the URL to stdout instead of launching the browser").option("--loopback", "docker only: use the 127.0.0.1 URL instead of the OrbStack .orb.local URL").option(
@@ -9471,10 +9765,10 @@ var screenCommand = new Command34("screen").description("Open a box's VNC (noVNC
9471
9765
  if (provider === "docker") {
9472
9766
  const insp = await inspectBox(box.id);
9473
9767
  if (insp.state === "paused") {
9474
- log36.info("box is paused; unpausing");
9768
+ log37.info("box is paused; unpausing");
9475
9769
  await unpauseBox(box.id);
9476
9770
  } else if (insp.state === "stopped") {
9477
- log36.info("box is stopped; starting");
9771
+ log37.info("box is stopped; starting");
9478
9772
  await startBox(box.id);
9479
9773
  } else if (insp.state === "missing") {
9480
9774
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -9484,13 +9778,13 @@ var screenCommand = new Command34("screen").description("Open a box's VNC (noVNC
9484
9778
  const inBoxUrl = exposePort !== void 0 ? box.portlessUrl ?? `http://localhost:${String(exposePort)}` : "about:blank";
9485
9779
  const br = await ensureBoxBrowser(box.container, void 0, inBoxUrl);
9486
9780
  if (br.up && !br.alreadyRunning) {
9487
- log36.info(
9781
+ log37.info(
9488
9782
  exposePort !== void 0 ? `opened ${inBoxUrl} in the in-box browser (visible in the VNC view)` : "started in-box browser"
9489
9783
  );
9490
9784
  } else if (br.alreadyRunning) {
9491
- log36.info("in-box browser already running; left it untouched");
9785
+ log37.info("in-box browser already running; left it untouched");
9492
9786
  } else {
9493
- log36.warn(`could not start in-box browser: ${br.reason ?? "unknown"}`);
9787
+ log37.warn(`could not start in-box browser: ${br.reason ?? "unknown"}`);
9494
9788
  }
9495
9789
  const engine = await detectEngine();
9496
9790
  const urls = buildVncUrls(box, engine);
@@ -9511,10 +9805,10 @@ var screenCommand = new Command34("screen").description("Open a box's VNC (noVNC
9511
9805
  const p = await providerForBox(box);
9512
9806
  const state = await p.probeState(box);
9513
9807
  if (state === "paused") {
9514
- log36.info("box is paused; resuming");
9808
+ log37.info("box is paused; resuming");
9515
9809
  await p.resume(box);
9516
9810
  } else if (state === "stopped") {
9517
- log36.info("box is stopped; starting");
9811
+ log37.info("box is stopped; starting");
9518
9812
  await p.start(box);
9519
9813
  } else if (state === "missing") {
9520
9814
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -9540,18 +9834,18 @@ var screenCommand = new Command34("screen").description("Open a box's VNC (noVNC
9540
9834
 
9541
9835
  // src/commands/shell.ts
9542
9836
  import { spawnSync as spawnSync4 } from "child_process";
9543
- import { log as log38 } from "@clack/prompts";
9544
- import { Command as Command35 } from "commander";
9837
+ import { log as log39 } from "@clack/prompts";
9838
+ import { Command as Command36 } from "commander";
9545
9839
 
9546
9840
  // src/commands/_provider-guard.ts
9547
- import { log as log37 } from "@clack/prompts";
9841
+ import { log as log38 } from "@clack/prompts";
9548
9842
  function requireDockerProvider(box, commandName) {
9549
9843
  const provider = box.provider ?? "docker";
9550
9844
  if (provider === "docker") return;
9551
- log37.error(
9845
+ log38.error(
9552
9846
  `\`agentbox ${commandName}\` doesn't yet support cloud boxes (this box's provider is '${provider}').`
9553
9847
  );
9554
- log37.info(
9848
+ log38.info(
9555
9849
  "Cloud-provider routing for this command is on the Phase 3 backlog. For now: use `agentbox url` for web access, `agentbox-ctl git push` from inside the sandbox via SSH/web terminal, or fall back to the cloud provider's own console."
9556
9850
  );
9557
9851
  process.exit(2);
@@ -9591,10 +9885,10 @@ function fmtAgo2(iso) {
9591
9885
  async function ensureBoxRunning(box) {
9592
9886
  const insp = await inspectBox(box.id);
9593
9887
  if (insp.state === "paused") {
9594
- log38.info("box is paused; unpausing");
9888
+ log39.info("box is paused; unpausing");
9595
9889
  await unpauseBox(box.id);
9596
9890
  } else if (insp.state === "stopped") {
9597
- log38.info("box is stopped; starting");
9891
+ log39.info("box is stopped; starting");
9598
9892
  await startBox(box.id);
9599
9893
  } else if (insp.state === "missing") {
9600
9894
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -9633,7 +9927,7 @@ async function startOrAttachShell(box, cfg) {
9633
9927
  const label = shellLabel(cfg.sessionName);
9634
9928
  const info = await shellSessionInfo(box.container, cfg.sessionName, cfg.user);
9635
9929
  if (info.running) {
9636
- log38.info(`reattaching to shell "${label}" \u2014 Control+a d to detach`);
9930
+ log39.info(`reattaching to shell "${label}" \u2014 Control+a d to detach`);
9637
9931
  } else {
9638
9932
  await startShellSession({
9639
9933
  container: box.container,
@@ -9641,7 +9935,7 @@ async function startOrAttachShell(box, cfg) {
9641
9935
  user: cfg.user,
9642
9936
  login: cfg.login
9643
9937
  });
9644
- log38.info(`shell "${label}" \u2014 Control+a d to detach, leaves it running`);
9938
+ log39.info(`shell "${label}" \u2014 Control+a d to detach, leaves it running`);
9645
9939
  }
9646
9940
  const code = await runWrappedAttach({
9647
9941
  container: box.container,
@@ -9656,7 +9950,7 @@ async function startOrAttachShell(box, cfg) {
9656
9950
  });
9657
9951
  process.exit(code);
9658
9952
  }
9659
- var shellCommand = new Command35("shell").description(
9953
+ var shellCommand = new Command36("shell").description(
9660
9954
  "Open an interactive shell in a box, in a detachable tmux session (auto-unpause/start)"
9661
9955
  ).argument(
9662
9956
  "[box]",
@@ -9694,6 +9988,7 @@ var shellCommand = new Command35("shell").description(
9694
9988
  container: box.name,
9695
9989
  command: spec.argv[0],
9696
9990
  dockerArgv: spec.argv.slice(1),
9991
+ env: spec.env,
9697
9992
  relayBaseUrl: RELAY_HOST_URL5,
9698
9993
  boxId: box.id,
9699
9994
  boxName: box.name,
@@ -9746,7 +10041,7 @@ var shellCommand = new Command35("shell").description(
9746
10041
  handleLifecycleError(err);
9747
10042
  }
9748
10043
  });
9749
- var shellAttachCommand = new Command35("attach").description(
10044
+ var shellAttachCommand = new Command36("attach").description(
9750
10045
  "Attach to a shell tmux session in a box, starting one if none is running (auto-unpause/start)"
9751
10046
  ).argument(
9752
10047
  "[box]",
@@ -9784,7 +10079,7 @@ function renderShellTable(sessions) {
9784
10079
  for (const r of rows) process.stdout.write(`${fmt(r)}
9785
10080
  `);
9786
10081
  }
9787
- var shellLsCommand = new Command35("ls").description("List the shell tmux sessions running in a box").argument(
10082
+ var shellLsCommand = new Command36("ls").description("List the shell tmux sessions running in a box").argument(
9788
10083
  "[box]",
9789
10084
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
9790
10085
  ).action(async (idOrName) => {
@@ -9793,11 +10088,11 @@ var shellLsCommand = new Command35("ls").description("List the shell tmux sessio
9793
10088
  requireDockerProvider(box, "shell");
9794
10089
  const insp = await inspectBox(box.id);
9795
10090
  if (insp.state !== "running") {
9796
- log38.info(`box ${box.name} is ${insp.state} \u2014 no live shell sessions`);
10091
+ log39.info(`box ${box.name} is ${insp.state} \u2014 no live shell sessions`);
9797
10092
  return;
9798
10093
  }
9799
10094
  if (insp.shellSessions.length === 0) {
9800
- log38.info(
10095
+ log39.info(
9801
10096
  `no shell sessions in ${box.name} \u2014 start one with: agentbox shell ${reattachRef4(box)}`
9802
10097
  );
9803
10098
  return;
@@ -9807,7 +10102,7 @@ var shellLsCommand = new Command35("ls").description("List the shell tmux sessio
9807
10102
  handleLifecycleError(err);
9808
10103
  }
9809
10104
  });
9810
- var shellKillCommand = new Command35("kill").description("Kill a shell tmux session in a box (the shell and anything running in it)").argument(
10105
+ var shellKillCommand = new Command36("kill").description("Kill a shell tmux session in a box (the shell and anything running in it)").argument(
9811
10106
  "[box]",
9812
10107
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
9813
10108
  ).option("-n, --name <label>", "shell label to kill (default: the box default shell)").option("--all", "kill every shell session in the box").action(async function(idOrName) {
@@ -9817,25 +10112,25 @@ var shellKillCommand = new Command35("kill").description("Kill a shell tmux sess
9817
10112
  requireDockerProvider(box, "shell");
9818
10113
  const insp = await inspectBox(box.id);
9819
10114
  if (insp.state !== "running") {
9820
- log38.info(`box ${box.name} is ${insp.state} \u2014 no shell sessions to kill`);
10115
+ log39.info(`box ${box.name} is ${insp.state} \u2014 no shell sessions to kill`);
9821
10116
  return;
9822
10117
  }
9823
10118
  if (opts.all) {
9824
10119
  if (insp.shellSessions.length === 0) {
9825
- log38.info(`no shell sessions in ${box.name}`);
10120
+ log39.info(`no shell sessions in ${box.name}`);
9826
10121
  return;
9827
10122
  }
9828
10123
  let killed = 0;
9829
10124
  for (const s of insp.shellSessions) {
9830
10125
  if (await killShellSession(box.container, s.sessionName)) killed++;
9831
10126
  }
9832
- log38.success(`killed ${String(killed)} shell session${killed === 1 ? "" : "s"} in ${box.name}`);
10127
+ log39.success(`killed ${String(killed)} shell session${killed === 1 ? "" : "s"} in ${box.name}`);
9833
10128
  return;
9834
10129
  }
9835
10130
  const target = shellSessionName(opts.name);
9836
10131
  const ok = await killShellSession(box.container, target);
9837
- if (ok) log38.success(`killed shell "${shellLabel(target)}" in ${box.name}`);
9838
- else log38.warn(`no shell "${shellLabel(target)}" in ${box.name} (already gone?)`);
10132
+ if (ok) log39.success(`killed shell "${shellLabel(target)}" in ${box.name}`);
10133
+ else log39.warn(`no shell "${shellLabel(target)}" in ${box.name} (already gone?)`);
9839
10134
  } catch (err) {
9840
10135
  handleLifecycleError(err);
9841
10136
  }
@@ -9845,8 +10140,8 @@ shellCommand.addCommand(shellLsCommand);
9845
10140
  shellCommand.addCommand(shellKillCommand);
9846
10141
 
9847
10142
  // src/commands/start.ts
9848
- import { Command as Command36 } from "commander";
9849
- var startCommand = new Command36("start").description(
10143
+ import { Command as Command37 } from "commander";
10144
+ var startCommand = new Command37("start").description(
9850
10145
  "Start a stopped box. Docker: docker start + relaunch ctl/dockerd/vnc daemons. Cloud: backend.start, then re-resolve preview URLs/tokens, re-launch in-sandbox ctl/dockerd daemons, and re-register with the host relay (so the CloudBoxPoller resumes)."
9851
10146
  ).argument(
9852
10147
  "[box]",
@@ -9869,8 +10164,8 @@ var startCommand = new Command36("start").description(
9869
10164
  });
9870
10165
 
9871
10166
  // src/commands/status.ts
9872
- import { log as log40 } from "@clack/prompts";
9873
- import { Command as Command37 } from "commander";
10167
+ import { log as log41 } from "@clack/prompts";
10168
+ import { Command as Command38 } from "commander";
9874
10169
 
9875
10170
  // src/endpoints-render.ts
9876
10171
  function renderEndpointLines(endpoints, stream) {
@@ -9900,7 +10195,7 @@ function renderEndpointLines(endpoints, stream) {
9900
10195
  }
9901
10196
 
9902
10197
  // src/commands/inspect.ts
9903
- import { log as log39 } from "@clack/prompts";
10198
+ import { log as log40 } from "@clack/prompts";
9904
10199
  function fmtLimit(n, unit) {
9905
10200
  return n && n > 0 ? `${String(n)}${unit}` : "unlimited";
9906
10201
  }
@@ -10045,7 +10340,7 @@ function renderCodexActivityCloud(persisted) {
10045
10340
  async function runInspect(box, opts) {
10046
10341
  try {
10047
10342
  if (opts.json && opts.watch) {
10048
- log39.error("cannot combine --json with --watch");
10343
+ log40.error("cannot combine --json with --watch");
10049
10344
  process.exit(2);
10050
10345
  }
10051
10346
  const isCloud = (box.provider ?? "docker") !== "docker";
@@ -10082,14 +10377,14 @@ async function runInspect(box, opts) {
10082
10377
 
10083
10378
  // src/commands/status.ts
10084
10379
  var statusCommand2 = withWatchOptions(
10085
- new Command37("status").description("Show service + task status from a box's agentbox-ctl daemon").argument(
10380
+ new Command38("status").description("Show service + task status from a box's agentbox-ctl daemon").argument(
10086
10381
  "[box]",
10087
10382
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
10088
10383
  ).option("-j, --json", "machine-readable JSON output").option("--inspect", "show detailed box info (volumes, limits, paths) instead of service/task status")
10089
10384
  ).action(async (idOrName, opts) => {
10090
10385
  try {
10091
10386
  if (opts.json && opts.watch) {
10092
- log40.error("cannot combine --json with --watch");
10387
+ log41.error("cannot combine --json with --watch");
10093
10388
  process.exit(2);
10094
10389
  }
10095
10390
  const box = await resolveBoxOrExit(idOrName);
@@ -10256,8 +10551,8 @@ function renderPersisted2(s, state) {
10256
10551
  }
10257
10552
 
10258
10553
  // src/commands/stop.ts
10259
- import { Command as Command38 } from "commander";
10260
- var stopCommand = new Command38("stop").description(
10554
+ import { Command as Command39 } from "commander";
10555
+ var stopCommand = new Command39("stop").description(
10261
10556
  "Stop a box (Docker: docker stop; preserves upper + node_modules volumes. Cloud: backend.stop \u2014 sandbox stays in your account, disk preserved)."
10262
10557
  ).argument(
10263
10558
  "[box]",
@@ -10286,7 +10581,7 @@ restart with: agentbox start ${box.name}
10286
10581
  });
10287
10582
 
10288
10583
  // src/commands/top.ts
10289
- import { Command as Command39 } from "commander";
10584
+ import { Command as Command40 } from "commander";
10290
10585
  var COLS = ["BOX", "STATE", "CPU%", "MEM USAGE / LIMIT", "MEM%", "PIDS", "DISK", "NET I/O"];
10291
10586
  function row(name, state, s) {
10292
10587
  const mem = `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memLimitBytes)}`;
@@ -10359,7 +10654,7 @@ async function renderProjectFooters() {
10359
10654
 
10360
10655
  SYSTEM: ${parts.join(" - ")}` : "";
10361
10656
  }
10362
- var topCommand = new Command39("top").description("Live resource monitor (cpu/mem/pids/disk) for a box, the project, or every box").argument(
10657
+ var topCommand = new Command40("top").description("Live resource monitor (cpu/mem/pids/disk) for a box, the project, or every box").argument(
10363
10658
  "[box]",
10364
10659
  "box ref (default: every box on the host; --project narrows to the cwd's project)"
10365
10660
  ).option("-p, --project", "show only boxes in the cwd's project").option("--once", "print a single snapshot instead of watching").option("-j, --json", "machine-readable JSON (implies --once)").option("--interval <seconds>", "refresh interval", "2").action(async (idOrName, opts) => {
@@ -10392,8 +10687,8 @@ var topCommand = new Command39("top").description("Live resource monitor (cpu/me
10392
10687
  });
10393
10688
 
10394
10689
  // src/commands/unpause.ts
10395
- import { Command as Command40 } from "commander";
10396
- var unpauseCommand = new Command40("unpause").description(
10690
+ import { Command as Command41 } from "commander";
10691
+ var unpauseCommand = new Command41("unpause").description(
10397
10692
  "Resume a paused box. Docker: `docker unpause` (sub-second). Cloud: backend.resume (re-hydrates from archive \u2014 slower first time)."
10398
10693
  ).argument(
10399
10694
  "[box]",
@@ -10417,8 +10712,8 @@ var unpauseCommand = new Command40("unpause").description(
10417
10712
 
10418
10713
  // src/commands/update.ts
10419
10714
  import { spawn as spawn4 } from "child_process";
10420
- import { confirm as confirm15, intro as intro8, isCancel as isCancel16, log as log41, outro as outro8, spinner as spinner9 } from "@clack/prompts";
10421
- import { Command as Command41 } from "commander";
10715
+ import { confirm as confirm15, intro as intro8, isCancel as isCancel16, log as log42, outro as outro8, spinner as spinner9 } from "@clack/prompts";
10716
+ import { Command as Command42 } from "commander";
10422
10717
 
10423
10718
  // src/exec-method.ts
10424
10719
  function detectExecutionMethod(input) {
@@ -10462,7 +10757,7 @@ function runInherit(cmd, args) {
10462
10757
  child.on("close", (code) => resolveP(code ?? 0));
10463
10758
  });
10464
10759
  }
10465
- var updateCommand = new Command41("self-update").description(
10760
+ var updateCommand = new Command42("self-update").description(
10466
10761
  "Update agentbox: self-update via npm/pnpm (unless run via npx), wipe the box image so it rebuilds, and reload the relay"
10467
10762
  ).option("-y, --yes", "skip the confirmation prompt").option("--dry-run", "show what would happen, don't change anything").option("--skip-self", "skip the package self-update; only refresh the image + relay").action(async (opts) => {
10468
10763
  try {
@@ -10472,7 +10767,7 @@ var updateCommand = new Command41("self-update").description(
10472
10767
  });
10473
10768
  intro8("agentbox self-update");
10474
10769
  const selfStep = opts.skipSelf ? "self-update: skipped (--skip-self)" : describeSelfUpdate(method);
10475
- log41.info(
10770
+ log42.info(
10476
10771
  [
10477
10772
  "plan:",
10478
10773
  ` ${selfStep}`,
@@ -10487,25 +10782,25 @@ var updateCommand = new Command41("self-update").description(
10487
10782
  if (!opts.yes) {
10488
10783
  const ok = await confirm15({ message: "Proceed with update?", initialValue: true });
10489
10784
  if (isCancel16(ok) || !ok) {
10490
- log41.info("cancelled");
10785
+ log42.info("cancelled");
10491
10786
  return;
10492
10787
  }
10493
10788
  }
10494
10789
  let selfUpdated = false;
10495
10790
  if (opts.skipSelf) {
10496
- log41.info("skipping self-update (--skip-self)");
10791
+ log42.info("skipping self-update (--skip-self)");
10497
10792
  } else {
10498
10793
  const cmd = selfUpdateCommand(method);
10499
10794
  if (cmd === null) {
10500
- log41.info(describeSelfUpdate(method));
10795
+ log42.info(describeSelfUpdate(method));
10501
10796
  } else {
10502
- log41.info(`running: ${cmd.cmd} ${cmd.args.join(" ")}`);
10797
+ log42.info(`running: ${cmd.cmd} ${cmd.args.join(" ")}`);
10503
10798
  const code = await runInherit(cmd.cmd, cmd.args);
10504
10799
  if (code !== 0) {
10505
10800
  throw new Error(`${cmd.cmd} exited with code ${String(code)}`);
10506
10801
  }
10507
10802
  selfUpdated = true;
10508
- log41.success(`updated ${PKG} via ${cmd.cmd}`);
10803
+ log42.success(`updated ${PKG} via ${cmd.cmd}`);
10509
10804
  }
10510
10805
  }
10511
10806
  const s = spinner9();
@@ -10521,7 +10816,7 @@ var updateCommand = new Command41("self-update").description(
10521
10816
  stop.stopped ? `stopped relay (pid ${String(stop.pid)})` : "relay was not running"
10522
10817
  );
10523
10818
  if (selfUpdated) {
10524
- log41.info(
10819
+ log42.info(
10525
10820
  "relay will restart automatically (with the updated build) on your next `agentbox create` / `agentbox claude`"
10526
10821
  );
10527
10822
  } else {
@@ -10532,7 +10827,7 @@ var updateCommand = new Command41("self-update").description(
10532
10827
  sr2.stop(`relay back up on ${ep.hostUrl}`);
10533
10828
  } catch (err) {
10534
10829
  sr2.stop("relay restart failed");
10535
- log41.warn(
10830
+ log42.warn(
10536
10831
  `${err instanceof Error ? err.message : String(err)} \u2014 it will retry on the next box command`
10537
10832
  );
10538
10833
  }
@@ -10545,8 +10840,8 @@ var updateCommand = new Command41("self-update").description(
10545
10840
 
10546
10841
  // src/commands/url.ts
10547
10842
  import { spawnSync as spawnSync5 } from "child_process";
10548
- import { log as log42 } from "@clack/prompts";
10549
- import { Command as Command42 } from "commander";
10843
+ import { log as log43 } from "@clack/prompts";
10844
+ import { Command as Command43 } from "commander";
10550
10845
  var SIGNED_URL_TTL_MIN2 = 1;
10551
10846
  var SIGNED_URL_TTL_MAX2 = 86400;
10552
10847
  function parseTtlOrExit2(raw) {
@@ -10559,7 +10854,7 @@ function parseTtlOrExit2(raw) {
10559
10854
  }
10560
10855
  return n;
10561
10856
  }
10562
- var urlCommand = new Command42("url").description(
10857
+ var urlCommand = new Command43("url").description(
10563
10858
  "Open a box's web app URL in the browser, even when no service declares `expose:` (auto-unpause/start)"
10564
10859
  ).argument(
10565
10860
  "[box]",
@@ -10578,10 +10873,10 @@ var urlCommand = new Command42("url").description(
10578
10873
  if (provider === "docker") {
10579
10874
  const insp = await inspectBox(box.id);
10580
10875
  if (insp.state === "paused") {
10581
- log42.info("box is paused; unpausing");
10876
+ log43.info("box is paused; unpausing");
10582
10877
  await unpauseBox(box.id);
10583
10878
  } else if (insp.state === "stopped") {
10584
- log42.info("box is stopped; starting");
10879
+ log43.info("box is stopped; starting");
10585
10880
  await startBox(box.id);
10586
10881
  } else if (insp.state === "missing") {
10587
10882
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -10610,10 +10905,10 @@ var urlCommand = new Command42("url").description(
10610
10905
  const p = await providerForBox(box);
10611
10906
  const state = await p.probeState(box);
10612
10907
  if (state === "paused") {
10613
- log42.info("box is paused; resuming");
10908
+ log43.info("box is paused; resuming");
10614
10909
  await p.resume(box);
10615
10910
  } else if (state === "stopped") {
10616
- log42.info("box is stopped; starting");
10911
+ log43.info("box is stopped; starting");
10617
10912
  await p.start(box);
10618
10913
  } else if (state === "missing") {
10619
10914
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -10637,9 +10932,9 @@ var urlCommand = new Command42("url").description(
10637
10932
  });
10638
10933
 
10639
10934
  // src/commands/wait.ts
10640
- import { log as log43 } from "@clack/prompts";
10641
- import { Command as Command43 } from "commander";
10642
- var waitCommand = new Command43("wait").description("Block until the box reports all autostart units ready").argument(
10935
+ import { log as log44 } from "@clack/prompts";
10936
+ import { Command as Command44 } from "commander";
10937
+ var waitCommand = new Command44("wait").description("Block until the box reports all autostart units ready").argument(
10643
10938
  "[box]",
10644
10939
  "box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
10645
10940
  ).option("--timeout <ms>", "overall timeout in milliseconds", "120000").option("--units <names...>", "restrict to the named units").option("-j, --json", "machine-readable JSON output").action(async (idOrName, opts) => {
@@ -10655,7 +10950,7 @@ var waitCommand = new Command43("wait").description("Block until the box reports
10655
10950
  try {
10656
10951
  parsed = JSON.parse(proc.stdout);
10657
10952
  } catch {
10658
- log43.error(`agentbox-ctl wait-ready failed: ${proc.stderr || proc.stdout}`);
10953
+ log44.error(`agentbox-ctl wait-ready failed: ${proc.stderr || proc.stdout}`);
10659
10954
  process.exit(1);
10660
10955
  }
10661
10956
  if (opts.json) {
@@ -10694,7 +10989,7 @@ function rewriteProviderPrefix(argv) {
10694
10989
  process.env.DOCKER_CLI_HINTS ??= "false";
10695
10990
  process.env.AGENTBOX_CLI_VERSION = AGENTBOX_VERSION;
10696
10991
  process.env.AGENTBOX_CLI_COMMIT = AGENTBOX_COMMIT;
10697
- var program = new Command44();
10992
+ var program = new Command45();
10698
10993
  program.name("agentbox").description("Launch coding agents in isolated sandboxes").version(AGENTBOX_VERSION);
10699
10994
  program.enablePositionalOptions();
10700
10995
  program.addCommand(createCommand);
@@ -10732,6 +11027,7 @@ program.addCommand(relayCommand);
10732
11027
  program.addCommand(runQueuedJobCommand, { hidden: true });
10733
11028
  program.addCommand(daytonaCommand);
10734
11029
  program.addCommand(hetznerCommand);
11030
+ program.addCommand(vercelCommand);
10735
11031
  program.addCommand(dockerCommand);
10736
11032
  program.addCommand(updateCommand);
10737
11033
  program.addCommand(installCommand);