@madarco/agentbox 0.11.2 → 0.12.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 (35) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-XKO4SHR3.js} +3 -3
  3. package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
  4. package/dist/chunk-2LF5YILI.js.map +1 -0
  5. package/dist/{chunk-MXXXKJYS.js → chunk-DHJ7OMIP.js} +234 -83
  6. package/dist/chunk-DHJ7OMIP.js.map +1 -0
  7. package/dist/{chunk-GYJ62GFL.js → chunk-HFV6THYG.js} +6 -6
  8. package/dist/{chunk-ZJXTIH6C.js → chunk-IZXPJPPV.js} +1347 -852
  9. package/dist/chunk-IZXPJPPV.js.map +1 -0
  10. package/dist/{dist-RAZP76VX.js → dist-24PY2ZMO.js} +3 -3
  11. package/dist/{dist-PTJ6CEQY.js → dist-47LVLYUV.js} +4 -4
  12. package/dist/{dist-ASLPRUQR.js → dist-RZZSSUNB.js} +28 -2
  13. package/dist/{dist-WMQDMTWS.js → dist-SWUOU34W.js} +8 -5
  14. package/dist/dist-SWUOU34W.js.map +1 -0
  15. package/dist/index.js +1351 -731
  16. package/dist/index.js.map +1 -1
  17. package/package.json +6 -6
  18. package/runtime/docker/packages/ctl/dist/bin.cjs +335 -4
  19. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
  20. package/runtime/hetzner/ctl.cjs +335 -4
  21. package/runtime/hetzner/gh-shim +86 -5
  22. package/runtime/relay/bin.cjs +285 -2
  23. package/runtime/vercel/ctl.cjs +335 -4
  24. package/runtime/vercel/gh-shim +86 -5
  25. package/share/host-skills/agentbox/SKILL.md +16 -5
  26. package/share/host-skills/agentbox-info/SKILL.md +29 -7
  27. package/dist/chunk-MXXXKJYS.js.map +0 -1
  28. package/dist/chunk-ZGVMN54V.js.map +0 -1
  29. package/dist/chunk-ZJXTIH6C.js.map +0 -1
  30. package/dist/dist-WMQDMTWS.js.map +0 -1
  31. /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-XKO4SHR3.js.map} +0 -0
  32. /package/dist/{chunk-GYJ62GFL.js.map → chunk-HFV6THYG.js.map} +0 -0
  33. /package/dist/{dist-RAZP76VX.js.map → dist-24PY2ZMO.js.map} +0 -0
  34. /package/dist/{dist-PTJ6CEQY.js.map → dist-47LVLYUV.js.map} +0 -0
  35. /package/dist/{dist-ASLPRUQR.js.map → dist-RZZSSUNB.js.map} +0 -0
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  maskKey,
6
6
  readDaytonaCredStatus,
7
7
  secretsPath
8
- } from "./chunk-ZGVMN54V.js";
8
+ } from "./chunk-2LF5YILI.js";
9
9
  import {
10
10
  detectEgressIp,
11
11
  ensureHetznerCredentials,
@@ -26,12 +26,14 @@ import {
26
26
  } from "./chunk-ECLLV5JH.js";
27
27
  import {
28
28
  agentSpecsForCloud,
29
+ currentCloudBaseFingerprint,
29
30
  ensureAgentVolumesForCloud,
31
+ listAllCloudCheckpoints,
30
32
  listCloudCheckpoints,
31
33
  probeCloudCheckpoint,
32
34
  resolveCloudCheckpoint,
33
35
  seedAgentVolumesIfFresh
34
- } from "./chunk-MXXXKJYS.js";
36
+ } from "./chunk-DHJ7OMIP.js";
35
37
  import {
36
38
  ADVANCED_HINT_GROUPS,
37
39
  ALERT_BAND_ROWS,
@@ -62,7 +64,7 @@ import {
62
64
  statusLine,
63
65
  stripTitleGlyph,
64
66
  subscribePrompts
65
- } from "./chunk-GYJ62GFL.js";
67
+ } from "./chunk-HFV6THYG.js";
66
68
  import {
67
69
  AmbiguousBoxError,
68
70
  BOX_STATUS_EVENT,
@@ -88,6 +90,7 @@ import {
88
90
  allCheckpointImagesBytes,
89
91
  allocateShellSessionName,
90
92
  attachedContainerUri,
93
+ boxImageConfigKey,
91
94
  boxResourceStats,
92
95
  buildClaudeAttachArgv,
93
96
  buildClaudeLoginRunArgv,
@@ -99,10 +102,12 @@ import {
99
102
  buildShellSessionAttachArgv,
100
103
  buildVncUrls,
101
104
  bumpProjectGcCounter,
105
+ carrySourceHash,
102
106
  claudeSessionInfo,
103
107
  clearRelayNotice,
104
108
  codexSessionInfo,
105
109
  configPathFor,
110
+ copyCarryPathsToBox,
106
111
  createBox,
107
112
  createCheckpoint,
108
113
  defaultCheckpointConfigKey,
@@ -136,6 +141,7 @@ import {
136
141
  inspectBox,
137
142
  installPortless,
138
143
  killShellSession,
144
+ listAllCheckpoints,
139
145
  listBoxes,
140
146
  listCheckpoints,
141
147
  listProjectsConfigured,
@@ -173,11 +179,14 @@ import {
173
179
  renderTaskTable,
174
180
  resetPortlessCache,
175
181
  resolveAgentLauncher,
182
+ resolveBoxImage,
183
+ resolveBoxSize,
176
184
  resolveCheckpoint,
177
185
  resolveClaudeVolume,
178
186
  resolveCodexVolume,
179
187
  resolveDefaultCheckpoint,
180
188
  resolveOpencodeVolume,
189
+ resyncBox,
181
190
  runInteractiveClaudeLogin,
182
191
  runInteractiveCodexLogin,
183
192
  runInteractiveOpencodeLogin,
@@ -208,21 +217,25 @@ import {
208
217
  waitForTmuxPaneContent,
209
218
  warmUpClaudeCredentials,
210
219
  writeJob
211
- } from "./chunk-ZJXTIH6C.js";
220
+ } from "./chunk-IZXPJPPV.js";
212
221
  import {
213
222
  DEFAULT_BOX_IMAGE,
214
223
  STATE_DIR,
224
+ computeDockerContextFingerprint,
215
225
  ensureImage,
216
226
  hostOpenCommand,
227
+ imageExists,
217
228
  imageInfo,
229
+ readPreparedDockerState,
218
230
  readState,
231
+ recordBox,
219
232
  resolveBoxRef
220
233
  } from "./chunk-SNTHHWKY.js";
221
234
  import "./chunk-G3H2L3O2.js";
222
235
 
223
236
  // src/version.ts
224
- var AGENTBOX_VERSION = true ? "0.11.2" : "0.0.0-dev";
225
- var AGENTBOX_COMMIT = true ? "3683063c" : "dev";
237
+ var AGENTBOX_VERSION = true ? "0.12.0" : "0.0.0-dev";
238
+ var AGENTBOX_COMMIT = true ? "9df04087" : "dev";
226
239
 
227
240
  // src/index.ts
228
241
  import { Command as Command46 } from "commander";
@@ -254,13 +267,16 @@ var HELP_GROUPS = [
254
267
  "prepare",
255
268
  "wait",
256
269
  "prune",
270
+ "doctor",
257
271
  "self-update",
258
272
  "install",
259
273
  "config",
274
+ "git",
260
275
  "relay",
261
276
  "docker",
262
277
  "daytona",
263
- "hetzner"
278
+ "hetzner",
279
+ "vercel"
264
280
  ]
265
281
  }
266
282
  ];
@@ -593,7 +609,7 @@ function parsePositiveInt(raw, label) {
593
609
  }
594
610
 
595
611
  // src/commands/claude.ts
596
- import { confirm as confirm3, intro, isCancel as isCancel4, log as log8, outro, spinner as spinner3 } from "@clack/prompts";
612
+ import { confirm as confirm3, intro, isCancel as isCancel4, log as log9, outro, spinner as spinner3 } from "@clack/prompts";
597
613
  import { Command as Command2 } from "commander";
598
614
 
599
615
  // src/auth.ts
@@ -691,6 +707,293 @@ function buildPromptArgs(agentKind, prompt, userArgs) {
691
707
  return resolveAgentLauncher(agentKind).buildArgs(prompt, userArgs);
692
708
  }
693
709
 
710
+ // src/lib/carry-resync.ts
711
+ import { join as join4 } from "path";
712
+
713
+ // src/lib/carry-resolve.ts
714
+ import { realpath, stat as stat2 } from "fs/promises";
715
+ import { homedir as homedir2 } from "os";
716
+ import { isAbsolute, join as join3, normalize, resolve } from "path";
717
+ var DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
718
+ var DENYLIST_DEST_PREFIXES = ["/proc", "/sys", "/dev"];
719
+ var DENYLIST_DEST_EXACT = /* @__PURE__ */ new Set(["/etc/passwd", "/etc/shadow"]);
720
+ async function resolveCarry(items, opts) {
721
+ const home = opts.homeDir ?? homedir2();
722
+ const cap = opts.maxBytes ?? readMaxBytesFromEnv() ?? DEFAULT_MAX_BYTES;
723
+ const projectRoot = opts.projectRoot;
724
+ const entries = [];
725
+ const errors = [];
726
+ for (const [i, item] of items.entries()) {
727
+ const where = `carry[${String(i)}]`;
728
+ try {
729
+ const entry = await resolveOne(item, { projectRoot, home, cap, where });
730
+ entries.push(entry);
731
+ } catch (err) {
732
+ errors.push(err instanceof Error ? err.message : String(err));
733
+ }
734
+ }
735
+ return { entries, errors };
736
+ }
737
+ function readMaxBytesFromEnv() {
738
+ const raw = process.env.AGENTBOX_CARRY_MAX_BYTES;
739
+ if (!raw) return void 0;
740
+ const n = Number(raw);
741
+ if (!Number.isFinite(n) || n <= 0) return void 0;
742
+ return Math.floor(n);
743
+ }
744
+ async function resolveOne(item, ctx) {
745
+ const absSrc = expandHostPath(item.src, ctx);
746
+ if (containsDotDot(absSrc)) {
747
+ throw new Error(`${ctx.where}: resolved src "${absSrc}" contains .. \u2014 refused`);
748
+ }
749
+ validateBoxDest(item.dest, ctx);
750
+ const optional = item.optional;
751
+ const rawSrc = item.src;
752
+ const rawDest = item.dest;
753
+ const absDest = item.dest;
754
+ let st;
755
+ try {
756
+ st = await stat2(absSrc);
757
+ } catch (err) {
758
+ if (err.code === "ENOENT") {
759
+ if (optional) {
760
+ return {
761
+ rawSrc,
762
+ rawDest,
763
+ absSrc,
764
+ absDest,
765
+ kind: "missing",
766
+ ...item.mode !== void 0 ? { mode: item.mode } : {},
767
+ ...item.user !== void 0 ? { user: item.user } : {},
768
+ optional: true
769
+ };
770
+ }
771
+ throw new Error(`${ctx.where}: host src "${absSrc}" does not exist (use optional: true to skip)`);
772
+ }
773
+ throw err;
774
+ }
775
+ let symlinkInfo;
776
+ try {
777
+ const real = await realpath(absSrc);
778
+ if (real !== absSrc) {
779
+ const homeReal = await realpathSafe(ctx.home);
780
+ const rootReal = await realpathSafe(ctx.projectRoot);
781
+ if (!isInside(real, homeReal) && !isInside(real, rootReal)) {
782
+ symlinkInfo = "outside-home";
783
+ } else {
784
+ symlinkInfo = "safe";
785
+ }
786
+ }
787
+ } catch {
788
+ }
789
+ if (st.isDirectory()) {
790
+ const bytes = await dirSizeCapped(absSrc, ctx.cap);
791
+ if (bytes > ctx.cap) {
792
+ throw new Error(
793
+ `${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes (set AGENTBOX_CARRY_MAX_BYTES to raise the cap or narrow the path)`
794
+ );
795
+ }
796
+ return {
797
+ rawSrc,
798
+ rawDest,
799
+ absSrc,
800
+ absDest,
801
+ kind: "dir",
802
+ bytes,
803
+ ...item.mode !== void 0 ? { mode: item.mode } : {},
804
+ ...item.user !== void 0 ? { user: item.user } : {},
805
+ optional,
806
+ ...symlinkInfo ? { symlinkInfo } : {}
807
+ };
808
+ }
809
+ if (st.isFile()) {
810
+ if (st.size > ctx.cap) {
811
+ throw new Error(
812
+ `${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (set AGENTBOX_CARRY_MAX_BYTES to raise)`
813
+ );
814
+ }
815
+ return {
816
+ rawSrc,
817
+ rawDest,
818
+ absSrc,
819
+ absDest,
820
+ kind: "file",
821
+ bytes: st.size,
822
+ ...item.mode !== void 0 ? { mode: item.mode } : {},
823
+ ...item.user !== void 0 ? { user: item.user } : {},
824
+ optional,
825
+ ...symlinkInfo ? { symlinkInfo } : {}
826
+ };
827
+ }
828
+ throw new Error(`${ctx.where}: src "${absSrc}" is neither a regular file nor a directory`);
829
+ }
830
+ function expandHostPath(src, ctx) {
831
+ if (src.startsWith("~/")) {
832
+ return resolve(ctx.home, src.slice(2));
833
+ }
834
+ if (src.startsWith("./")) {
835
+ return resolve(ctx.projectRoot, src.slice(2));
836
+ }
837
+ if (isAbsolute(src)) return resolve(src);
838
+ throw new Error(`${ctx.where}: src "${src}" must start with /, ~/, or ./`);
839
+ }
840
+ function containsDotDot(p) {
841
+ const segs = normalize(p).split("/");
842
+ return segs.some((s) => s === "..");
843
+ }
844
+ function validateBoxDest(dest, ctx) {
845
+ if (dest.length === 0) {
846
+ throw new Error(`${ctx.where}.dest must not be empty`);
847
+ }
848
+ if (!dest.startsWith("/") && !dest.startsWith("~/")) {
849
+ throw new Error(`${ctx.where}.dest "${dest}" must start with / or ~/`);
850
+ }
851
+ const rawTail = dest.startsWith("~/") ? dest.slice(2) : dest.slice(1);
852
+ if (rawTail.split("/").some((s) => s === "..")) {
853
+ throw new Error(`${ctx.where}.dest "${dest}" contains .. \u2014 refused`);
854
+ }
855
+ if (DENYLIST_DEST_EXACT.has(dest)) {
856
+ throw new Error(`${ctx.where}.dest "${dest}" is on the denylist`);
857
+ }
858
+ for (const p of DENYLIST_DEST_PREFIXES) {
859
+ if (dest === p || dest.startsWith(`${p}/`)) {
860
+ throw new Error(`${ctx.where}.dest "${dest}" is on the denylist (prefix ${p})`);
861
+ }
862
+ }
863
+ }
864
+ function isInside(child, parent) {
865
+ const c = resolve(child);
866
+ const p = resolve(parent);
867
+ if (c === p) return true;
868
+ return c.startsWith(p + "/");
869
+ }
870
+ async function realpathSafe(p) {
871
+ try {
872
+ return await realpath(p);
873
+ } catch {
874
+ return resolve(p);
875
+ }
876
+ }
877
+ async function dirSizeCapped(dir, cap) {
878
+ let total = 0;
879
+ const seen = /* @__PURE__ */ new Set();
880
+ async function walk(d) {
881
+ if (total > cap) return;
882
+ const { readdir: readdir3 } = await import("fs/promises");
883
+ let names;
884
+ try {
885
+ names = await readdir3(d);
886
+ } catch {
887
+ return;
888
+ }
889
+ for (const name of names) {
890
+ if (total > cap) return;
891
+ const full = join3(d, name);
892
+ let st;
893
+ try {
894
+ st = await stat2(full);
895
+ } catch {
896
+ continue;
897
+ }
898
+ const key = `${String(st.dev)}:${String(st.ino)}`;
899
+ if (seen.has(key)) continue;
900
+ seen.add(key);
901
+ if (st.isDirectory()) {
902
+ await walk(full);
903
+ } else if (st.isFile()) {
904
+ total += st.size;
905
+ if (total > cap) return;
906
+ }
907
+ }
908
+ }
909
+ await walk(dir);
910
+ return total;
911
+ }
912
+
913
+ // src/lib/carry-resync.ts
914
+ async function resyncCarryFiles(args) {
915
+ const log45 = args.onLog ?? (() => {
916
+ });
917
+ const prior = args.box.carry?.entries ?? [];
918
+ if (prior.length === 0) return { recopied: 0, skippedNew: 0 };
919
+ const items = await loadCarrySection(join4(args.projectRoot, "agentbox.yaml"));
920
+ if (items.length === 0) return { recopied: 0, skippedNew: 0 };
921
+ const resolved = await resolveCarry(items, { projectRoot: args.projectRoot });
922
+ if (resolved.errors.length > 0) {
923
+ log45(`carry: resync skipped (resolve errors: ${resolved.errors.length})`);
924
+ return { recopied: 0, skippedNew: 0 };
925
+ }
926
+ const priorByDest = new Map(prior.map((e) => [e.dest, e]));
927
+ const changed = [];
928
+ let skippedNew = 0;
929
+ for (const entry of resolved.entries) {
930
+ if (entry.kind === "missing") continue;
931
+ const existing = priorByDest.get(entry.absDest);
932
+ if (!existing) {
933
+ skippedNew += 1;
934
+ continue;
935
+ }
936
+ const hash = await carrySourceHash(entry);
937
+ if (hash === void 0 || hash !== existing.hash) changed.push(entry);
938
+ }
939
+ if (skippedNew > 0) {
940
+ log45(
941
+ `carry: ${String(skippedNew)} new entry/entries not applied on resync \u2014 recreate the box to approve`
942
+ );
943
+ }
944
+ if (changed.length === 0) return { recopied: 0, skippedNew };
945
+ const result = await copyCarryPathsToBox({
946
+ container: args.box.container,
947
+ entries: changed,
948
+ onLog: log45
949
+ });
950
+ for (const err of result.errors) log45(`carry: ${err}`);
951
+ const updatedByDest = new Map(result.applied.map((e) => [e.dest, e]));
952
+ const mergedEntries = prior.map((e) => updatedByDest.get(e.dest) ?? e);
953
+ await recordBox({
954
+ ...args.box,
955
+ carry: { count: mergedEntries.length, entries: mergedEntries }
956
+ });
957
+ if (result.applied.length > 0) {
958
+ log45(`carry: re-copied ${String(result.applied.length)} changed file(s)`);
959
+ }
960
+ return { recopied: result.applied.length, skippedNew };
961
+ }
962
+
963
+ // src/lib/resync-warning.ts
964
+ function repoRelative(containerPath, file) {
965
+ const base = containerPath.replace(/^\/workspace\/?/, "");
966
+ return base ? `${base}/${file}` : file;
967
+ }
968
+ function buildResyncWarning(r) {
969
+ if (!r.hadConflicts) return null;
970
+ const files = [];
971
+ for (const repo of r.repos) {
972
+ for (const f of repo.mergeConflicts) files.push(repoRelative(repo.containerPath, f));
973
+ for (const f of repo.overlaySkipped) files.push(repoRelative(repo.containerPath, f));
974
+ }
975
+ const unique = [...new Set(files)];
976
+ if (unique.length === 0) return null;
977
+ return "[agentbox] I synced this box with the host's latest workspace (new commits + the host's uncommitted/untracked changes). These files had conflicting host changes that I SKIPPED to keep your box's version \u2014 review them if the host edits matter:\n" + unique.map((f) => ` - ${f}`).join("\n");
978
+ }
979
+ function prependResyncWarning(warning, prompt) {
980
+ if (!warning) return prompt;
981
+ return prompt.length > 0 ? `${warning}
982
+
983
+ ${prompt}` : warning;
984
+ }
985
+
986
+ // src/lib/resync-start.ts
987
+ async function maybeResyncWorkspace(args) {
988
+ if (!args.enabled) return null;
989
+ if ((args.box.provider ?? "docker") !== "docker") return null;
990
+ const onLog = (line) => args.spinner?.message(line);
991
+ args.spinner?.message("resyncing workspace with host");
992
+ const result = await resyncBox(args.box.id, onLog);
993
+ await resyncCarryFiles({ box: args.box, projectRoot: args.projectRoot, onLog });
994
+ return buildResyncWarning(result);
995
+ }
996
+
694
997
  // src/lib/skip-permissions.ts
695
998
  var CLAUDE_SKIP_PERMISSIONS_FLAG = "--dangerously-skip-permissions";
696
999
  var CODEX_SKIP_PERMISSIONS_FLAG = "--dangerously-bypass-approvals-and-sandbox";
@@ -867,7 +1170,7 @@ function makeProgressReporter(verbose) {
867
1170
  }
868
1171
 
869
1172
  // src/lib/launch-recap.ts
870
- import { homedir as homedir2 } from "os";
1173
+ import { homedir as homedir3 } from "os";
871
1174
  import { note } from "@clack/prompts";
872
1175
 
873
1176
  // src/lib/from-branch.ts
@@ -964,7 +1267,7 @@ async function resolveBranchSelection(opts) {
964
1267
 
965
1268
  // src/lib/launch-recap.ts
966
1269
  function homeShorten(p) {
967
- const home = homedir2();
1270
+ const home = homedir3();
968
1271
  return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
969
1272
  }
970
1273
  function whiten(text) {
@@ -1044,7 +1347,8 @@ async function cloudAgentCreate(args) {
1044
1347
  }
1045
1348
 
1046
1349
  // src/lib/carry-gate.ts
1047
- import { join as join4 } from "path";
1350
+ import { join as join5 } from "path";
1351
+ import { log as log5 } from "@clack/prompts";
1048
1352
 
1049
1353
  // src/carry-prompt.ts
1050
1354
  import { isCancel, log as log4, select } from "@clack/prompts";
@@ -1138,211 +1442,11 @@ function pad(s, w) {
1138
1442
  return s + " ".repeat(w - s.length);
1139
1443
  }
1140
1444
 
1141
- // src/lib/carry-resolve.ts
1142
- import { realpath, stat as stat2 } from "fs/promises";
1143
- import { homedir as homedir3 } from "os";
1144
- import { isAbsolute, join as join3, normalize, resolve } from "path";
1145
- var DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
1146
- var DENYLIST_DEST_PREFIXES = ["/proc", "/sys", "/dev"];
1147
- var DENYLIST_DEST_EXACT = /* @__PURE__ */ new Set(["/etc/passwd", "/etc/shadow"]);
1148
- async function resolveCarry(items, opts) {
1149
- const home = opts.homeDir ?? homedir3();
1150
- const cap = opts.maxBytes ?? readMaxBytesFromEnv() ?? DEFAULT_MAX_BYTES;
1151
- const projectRoot = opts.projectRoot;
1152
- const entries = [];
1153
- const errors = [];
1154
- for (const [i, item] of items.entries()) {
1155
- const where = `carry[${String(i)}]`;
1156
- try {
1157
- const entry = await resolveOne(item, { projectRoot, home, cap, where });
1158
- entries.push(entry);
1159
- } catch (err) {
1160
- errors.push(err instanceof Error ? err.message : String(err));
1161
- }
1162
- }
1163
- return { entries, errors };
1164
- }
1165
- function readMaxBytesFromEnv() {
1166
- const raw = process.env.AGENTBOX_CARRY_MAX_BYTES;
1167
- if (!raw) return void 0;
1168
- const n = Number(raw);
1169
- if (!Number.isFinite(n) || n <= 0) return void 0;
1170
- return Math.floor(n);
1171
- }
1172
- async function resolveOne(item, ctx) {
1173
- const absSrc = expandHostPath(item.src, ctx);
1174
- if (containsDotDot(absSrc)) {
1175
- throw new Error(`${ctx.where}: resolved src "${absSrc}" contains .. \u2014 refused`);
1176
- }
1177
- validateBoxDest(item.dest, ctx);
1178
- const optional = item.optional;
1179
- const rawSrc = item.src;
1180
- const rawDest = item.dest;
1181
- const absDest = item.dest;
1182
- let st;
1183
- try {
1184
- st = await stat2(absSrc);
1185
- } catch (err) {
1186
- if (err.code === "ENOENT") {
1187
- if (optional) {
1188
- return {
1189
- rawSrc,
1190
- rawDest,
1191
- absSrc,
1192
- absDest,
1193
- kind: "missing",
1194
- ...item.mode !== void 0 ? { mode: item.mode } : {},
1195
- ...item.user !== void 0 ? { user: item.user } : {},
1196
- optional: true
1197
- };
1198
- }
1199
- throw new Error(`${ctx.where}: host src "${absSrc}" does not exist (use optional: true to skip)`);
1200
- }
1201
- throw err;
1202
- }
1203
- let symlinkInfo;
1204
- try {
1205
- const real = await realpath(absSrc);
1206
- if (real !== absSrc) {
1207
- const homeReal = await realpathSafe(ctx.home);
1208
- const rootReal = await realpathSafe(ctx.projectRoot);
1209
- if (!isInside(real, homeReal) && !isInside(real, rootReal)) {
1210
- symlinkInfo = "outside-home";
1211
- } else {
1212
- symlinkInfo = "safe";
1213
- }
1214
- }
1215
- } catch {
1216
- }
1217
- if (st.isDirectory()) {
1218
- const bytes = await dirSizeCapped(absSrc, ctx.cap);
1219
- if (bytes > ctx.cap) {
1220
- throw new Error(
1221
- `${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes (set AGENTBOX_CARRY_MAX_BYTES to raise the cap or narrow the path)`
1222
- );
1223
- }
1224
- return {
1225
- rawSrc,
1226
- rawDest,
1227
- absSrc,
1228
- absDest,
1229
- kind: "dir",
1230
- bytes,
1231
- ...item.mode !== void 0 ? { mode: item.mode } : {},
1232
- ...item.user !== void 0 ? { user: item.user } : {},
1233
- optional,
1234
- ...symlinkInfo ? { symlinkInfo } : {}
1235
- };
1236
- }
1237
- if (st.isFile()) {
1238
- if (st.size > ctx.cap) {
1239
- throw new Error(
1240
- `${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (set AGENTBOX_CARRY_MAX_BYTES to raise)`
1241
- );
1242
- }
1243
- return {
1244
- rawSrc,
1245
- rawDest,
1246
- absSrc,
1247
- absDest,
1248
- kind: "file",
1249
- bytes: st.size,
1250
- ...item.mode !== void 0 ? { mode: item.mode } : {},
1251
- ...item.user !== void 0 ? { user: item.user } : {},
1252
- optional,
1253
- ...symlinkInfo ? { symlinkInfo } : {}
1254
- };
1255
- }
1256
- throw new Error(`${ctx.where}: src "${absSrc}" is neither a regular file nor a directory`);
1257
- }
1258
- function expandHostPath(src, ctx) {
1259
- if (src.startsWith("~/")) {
1260
- return resolve(ctx.home, src.slice(2));
1261
- }
1262
- if (src.startsWith("./")) {
1263
- return resolve(ctx.projectRoot, src.slice(2));
1264
- }
1265
- if (isAbsolute(src)) return resolve(src);
1266
- throw new Error(`${ctx.where}: src "${src}" must start with /, ~/, or ./`);
1267
- }
1268
- function containsDotDot(p) {
1269
- const segs = normalize(p).split("/");
1270
- return segs.some((s) => s === "..");
1271
- }
1272
- function validateBoxDest(dest, ctx) {
1273
- if (dest.length === 0) {
1274
- throw new Error(`${ctx.where}.dest must not be empty`);
1275
- }
1276
- if (!dest.startsWith("/") && !dest.startsWith("~/")) {
1277
- throw new Error(`${ctx.where}.dest "${dest}" must start with / or ~/`);
1278
- }
1279
- const rawTail = dest.startsWith("~/") ? dest.slice(2) : dest.slice(1);
1280
- if (rawTail.split("/").some((s) => s === "..")) {
1281
- throw new Error(`${ctx.where}.dest "${dest}" contains .. \u2014 refused`);
1282
- }
1283
- if (DENYLIST_DEST_EXACT.has(dest)) {
1284
- throw new Error(`${ctx.where}.dest "${dest}" is on the denylist`);
1285
- }
1286
- for (const p of DENYLIST_DEST_PREFIXES) {
1287
- if (dest === p || dest.startsWith(`${p}/`)) {
1288
- throw new Error(`${ctx.where}.dest "${dest}" is on the denylist (prefix ${p})`);
1289
- }
1290
- }
1291
- }
1292
- function isInside(child, parent) {
1293
- const c = resolve(child);
1294
- const p = resolve(parent);
1295
- if (c === p) return true;
1296
- return c.startsWith(p + "/");
1297
- }
1298
- async function realpathSafe(p) {
1299
- try {
1300
- return await realpath(p);
1301
- } catch {
1302
- return resolve(p);
1303
- }
1304
- }
1305
- async function dirSizeCapped(dir, cap) {
1306
- let total = 0;
1307
- const seen = /* @__PURE__ */ new Set();
1308
- async function walk(d) {
1309
- if (total > cap) return;
1310
- const { readdir: readdir3 } = await import("fs/promises");
1311
- let names;
1312
- try {
1313
- names = await readdir3(d);
1314
- } catch {
1315
- return;
1316
- }
1317
- for (const name of names) {
1318
- if (total > cap) return;
1319
- const full = join3(d, name);
1320
- let st;
1321
- try {
1322
- st = await stat2(full);
1323
- } catch {
1324
- continue;
1325
- }
1326
- const key = `${String(st.dev)}:${String(st.ino)}`;
1327
- if (seen.has(key)) continue;
1328
- seen.add(key);
1329
- if (st.isDirectory()) {
1330
- await walk(full);
1331
- } else if (st.isFile()) {
1332
- total += st.size;
1333
- if (total > cap) return;
1334
- }
1335
- }
1336
- }
1337
- await walk(dir);
1338
- return total;
1339
- }
1340
-
1341
1445
  // src/lib/carry-gate.ts
1342
1446
  async function runCarryGate(args) {
1343
- const log44 = args.onLog ?? (() => {
1447
+ const emit = args.onLog ?? (() => {
1344
1448
  });
1345
- const yamlPath = join4(args.projectRoot, "agentbox.yaml");
1449
+ const yamlPath = join5(args.projectRoot, "agentbox.yaml");
1346
1450
  const items = await loadCarrySection(yamlPath);
1347
1451
  if (items.length === 0) return { decision: "approve", entries: [] };
1348
1452
  const resolved = await resolveCarry(items, { projectRoot: args.projectRoot });
@@ -1362,17 +1466,38 @@ async function runCarryGate(args) {
1362
1466
  });
1363
1467
  if (decision === "cancel") return { decision: "cancel" };
1364
1468
  if (decision === "skip-this-run") {
1365
- log44(`carry: skipped for this box (${String(resolved.entries.length)} entry/entries not copied)`);
1469
+ emit(`carry: skipped for this box (${String(resolved.entries.length)} entry/entries not copied)`);
1366
1470
  return { decision: "skip", entries: [] };
1367
1471
  }
1368
1472
  return { decision: "approve", entries: resolved.entries };
1369
1473
  }
1474
+ async function runQueuedCarryGate(args) {
1475
+ try {
1476
+ const gate = await runCarryGate({
1477
+ projectRoot: args.projectRoot,
1478
+ yes: !!args.opts.yes,
1479
+ carryYesFlag: args.opts.carryYes ? true : void 0,
1480
+ carrySkipFlag: args.opts.carry === "skip" ? true : void 0,
1481
+ onLog: args.onLog
1482
+ });
1483
+ if (gate.decision === "cancel") {
1484
+ log5.warn("carry: cancelled \u2014 not queuing the job");
1485
+ args.onClose?.();
1486
+ process.exit(0);
1487
+ }
1488
+ return gate.decision === "approve" ? gate.entries : [];
1489
+ } catch (err) {
1490
+ log5.error(err instanceof Error ? err.message : String(err));
1491
+ args.onClose?.();
1492
+ process.exit(1);
1493
+ }
1494
+ }
1370
1495
 
1371
1496
  // src/session-teleport/claude.ts
1372
1497
  import { mkdir, mkdtemp, readdir, readFile as readFile2, stat as stat3, writeFile } from "fs/promises";
1373
1498
  import { existsSync } from "fs";
1374
1499
  import { homedir as homedir4, tmpdir } from "os";
1375
- import { join as join5 } from "path";
1500
+ import { join as join6 } from "path";
1376
1501
 
1377
1502
  // src/session-teleport/cwd-encoding.ts
1378
1503
  function encodeClaudeProjectsDir(absPath) {
@@ -1393,7 +1518,7 @@ var TeleportError = class extends Error {
1393
1518
  var BOX_CLAUDE_PROJECTS_DIR = `/home/vscode/.claude/projects/${BOX_WORKSPACE_ENCODED}`;
1394
1519
  async function resolveClaudeTeleport(opts) {
1395
1520
  const hostHome = opts.hostHome ?? homedir4();
1396
- const projectDir = join5(
1521
+ const projectDir = join6(
1397
1522
  hostHome,
1398
1523
  ".claude",
1399
1524
  "projects",
@@ -1406,8 +1531,8 @@ async function resolveClaudeTeleport(opts) {
1406
1531
  }
1407
1532
  const sessionPath = await pickSessionFile(projectDir, opts.mode);
1408
1533
  const sessionId = sessionPath.replace(/\.jsonl$/u, "").split("/").pop() ?? "";
1409
- const stage = await mkdtemp(join5(tmpdir(), "agentbox-teleport-claude-"));
1410
- const stagedFile = join5(stage, `${sessionId}.jsonl`);
1534
+ const stage = await mkdtemp(join6(tmpdir(), "agentbox-teleport-claude-"));
1535
+ const stagedFile = join6(stage, `${sessionId}.jsonl`);
1411
1536
  await rewriteSessionFile(sessionPath, stagedFile, opts.hostCwd);
1412
1537
  opts.log?.(`teleport: claude session ${sessionId} staged for upload`);
1413
1538
  return {
@@ -1421,7 +1546,7 @@ async function resolveClaudeTeleport(opts) {
1421
1546
  }
1422
1547
  async function pickSessionFile(projectDir, mode) {
1423
1548
  if (mode.kind === "resume") {
1424
- const target = join5(projectDir, `${mode.id}.jsonl`);
1549
+ const target = join6(projectDir, `${mode.id}.jsonl`);
1425
1550
  if (!existsSync(target)) {
1426
1551
  throw new TeleportError(
1427
1552
  `Claude session "${mode.id}" not found in ${projectDir}. List available sessions with: ls "${projectDir}"`
@@ -1438,7 +1563,7 @@ async function pickSessionFile(projectDir, mode) {
1438
1563
  }
1439
1564
  const stats = await Promise.all(
1440
1565
  jsonl.map(async (name) => {
1441
- const full = join5(projectDir, name);
1566
+ const full = join6(projectDir, name);
1442
1567
  const s = await stat3(full);
1443
1568
  return { full, mtimeMs: s.mtimeMs };
1444
1569
  })
@@ -1472,7 +1597,7 @@ async function rewriteSessionFile(src, dst, hostCwd) {
1472
1597
  }
1473
1598
  out.push(line);
1474
1599
  }
1475
- await mkdir(join5(dst, ".."), { recursive: true });
1600
+ await mkdir(join6(dst, ".."), { recursive: true });
1476
1601
  await writeFile(dst, out.join("\n"), "utf8");
1477
1602
  }
1478
1603
 
@@ -1480,10 +1605,10 @@ async function rewriteSessionFile(src, dst, hostCwd) {
1480
1605
  import { mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, stat as stat4, writeFile as writeFile2 } from "fs/promises";
1481
1606
  import { existsSync as existsSync2 } from "fs";
1482
1607
  import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
1483
- import { join as join6 } from "path";
1608
+ import { join as join7 } from "path";
1484
1609
  async function resolveCodexTeleport(opts) {
1485
1610
  const hostHome = opts.hostHome ?? homedir5();
1486
- const sessionsRoot = join6(hostHome, ".codex", "sessions");
1611
+ const sessionsRoot = join7(hostHome, ".codex", "sessions");
1487
1612
  if (!existsSync2(sessionsRoot)) {
1488
1613
  throw new TeleportError(
1489
1614
  `no Codex session history found on the host (expected at ${sessionsRoot}). Run \`codex\` at least once before using -c / --resume.`
@@ -1525,8 +1650,8 @@ async function resolveCodexTeleport(opts) {
1525
1650
  matching.sort((a, b) => b.mtimeMs - a.mtimeMs);
1526
1651
  picked = matching[0];
1527
1652
  }
1528
- const stage = await mkdtemp2(join6(tmpdir2(), "agentbox-teleport-codex-"));
1529
- const stagedFile = join6(stage, posixBasename(picked.relPath));
1653
+ const stage = await mkdtemp2(join7(tmpdir2(), "agentbox-teleport-codex-"));
1654
+ const stagedFile = join7(stage, posixBasename(picked.relPath));
1530
1655
  await rewriteCodexSession(picked.hostPath, stagedFile, opts.hostCwd);
1531
1656
  opts.log?.(`teleport: codex session ${picked.uuid} staged for upload`);
1532
1657
  const boxParentDir = `/home/vscode/.codex/sessions/${posixDirname(picked.relPath)}`;
@@ -1552,21 +1677,21 @@ async function listCodexSessions(sessionsRoot) {
1552
1677
  const years = await safeReaddir(sessionsRoot);
1553
1678
  for (const y of years) {
1554
1679
  if (!/^\d{4}$/u.test(y)) continue;
1555
- const yDir = join6(sessionsRoot, y);
1680
+ const yDir = join7(sessionsRoot, y);
1556
1681
  const months = await safeReaddir(yDir);
1557
1682
  for (const m of months) {
1558
1683
  if (!/^\d{2}$/u.test(m)) continue;
1559
- const mDir = join6(yDir, m);
1684
+ const mDir = join7(yDir, m);
1560
1685
  const days = await safeReaddir(mDir);
1561
1686
  for (const d of days) {
1562
1687
  if (!/^\d{2}$/u.test(d)) continue;
1563
- const dDir = join6(mDir, d);
1688
+ const dDir = join7(mDir, d);
1564
1689
  const files = await safeReaddir(dDir);
1565
1690
  for (const name of files) {
1566
1691
  if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
1567
1692
  const uuid = extractCodexUuid(name);
1568
1693
  if (uuid === null) continue;
1569
- const hostPath = join6(dDir, name);
1694
+ const hostPath = join7(dDir, name);
1570
1695
  let mtimeMs = 0;
1571
1696
  try {
1572
1697
  mtimeMs = (await stat4(hostPath)).mtimeMs;
@@ -1688,39 +1813,76 @@ async function uploadTeleport(input) {
1688
1813
  );
1689
1814
  }
1690
1815
 
1816
+ // src/session-teleport/plan.ts
1817
+ import { mkdtemp as mkdtemp3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
1818
+ import { existsSync as existsSync3 } from "fs";
1819
+ import { homedir as homedir6, tmpdir as tmpdir3 } from "os";
1820
+ import { basename, isAbsolute as isAbsolute2, join as join8, resolve as resolve2 } from "path";
1821
+ var BOX_CLAUDE_PLANS_DIR = "/home/vscode/.claude/plans";
1822
+ function expandHome(p, hostHome) {
1823
+ if (p === "~") return hostHome;
1824
+ if (p.startsWith("~/")) return join8(hostHome, p.slice(2));
1825
+ return isAbsolute2(p) ? p : resolve2(p);
1826
+ }
1827
+ async function resolvePlanTeleport(opts) {
1828
+ const hostHome = opts.hostHome ?? homedir6();
1829
+ const planFile = expandHome(opts.planPath, hostHome);
1830
+ if (!existsSync3(planFile)) {
1831
+ throw new TeleportError(
1832
+ `plan file not found on the host: ${planFile}. Pass --plan with the path to a Claude Code plan (e.g. ~/.claude/plans/<slug>.md).`
1833
+ );
1834
+ }
1835
+ const name = basename(planFile);
1836
+ const stage = await mkdtemp3(join8(tmpdir3(), "agentbox-teleport-plan-"));
1837
+ const stagedFile = join8(stage, name);
1838
+ const raw = await readFile4(planFile, "utf8");
1839
+ const absCwd = opts.hostCwd ? resolve2(expandHome(opts.hostCwd, hostHome)) : "";
1840
+ const rewritten = absCwd ? raw.split(absCwd).join(BOX_WORKSPACE) : raw;
1841
+ await writeFile3(stagedFile, rewritten, "utf8");
1842
+ opts.log?.(`teleport: claude plan ${name} staged for upload`);
1843
+ return {
1844
+ agent: "claude",
1845
+ sessionId: name,
1846
+ hostFile: stagedFile,
1847
+ boxPath: `${BOX_CLAUDE_PLANS_DIR}/${name}`,
1848
+ boxParentDir: BOX_CLAUDE_PLANS_DIR,
1849
+ forwardArgs: []
1850
+ };
1851
+ }
1852
+
1691
1853
  // src/lib/install-hint.ts
1692
- import { log as log5 } from "@clack/prompts";
1693
- import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
1694
- import { homedir as homedir6 } from "os";
1695
- import { join as join7 } from "path";
1854
+ import { log as log6 } from "@clack/prompts";
1855
+ import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
1856
+ import { homedir as homedir7 } from "os";
1857
+ import { join as join9 } from "path";
1696
1858
  function maybeShowInstallHint() {
1697
1859
  try {
1698
- const skill = join7(homedir6(), ".claude", "skills", "agentbox", "SKILL.md");
1699
- if (existsSync3(skill)) return;
1700
- const marker = join7(homedir6(), ".agentbox", "install-hint-shown");
1701
- if (existsSync3(marker)) return;
1702
- mkdirSync(join7(homedir6(), ".agentbox"), { recursive: true });
1860
+ const skill = join9(homedir7(), ".claude", "skills", "agentbox", "SKILL.md");
1861
+ if (existsSync4(skill)) return;
1862
+ const marker = join9(homedir7(), ".agentbox", "install-hint-shown");
1863
+ if (existsSync4(marker)) return;
1864
+ mkdirSync(join9(homedir7(), ".agentbox"), { recursive: true });
1703
1865
  writeFileSync(marker, "");
1704
- log5.info("tip: run 'agentbox install' to enable the /agentbox fork command in host Claude");
1866
+ log6.info("tip: run 'agentbox install' to enable the /agentbox fork command in host Claude");
1705
1867
  } catch {
1706
1868
  }
1707
1869
  }
1708
1870
 
1709
1871
  // src/lib/log-file.ts
1710
1872
  import { closeSync, mkdirSync as mkdirSync2, openSync, renameSync, symlinkSync, unlinkSync, writeFileSync as writeFileSync2, writeSync } from "fs";
1711
- import { homedir as homedir7 } from "os";
1712
- import { join as join8 } from "path";
1873
+ import { homedir as homedir8 } from "os";
1874
+ import { join as join10 } from "path";
1713
1875
  function stateDir() {
1714
- return process.env.AGENTBOX_HOME ?? join8(homedir7(), ".agentbox");
1876
+ return process.env.AGENTBOX_HOME ?? join10(homedir8(), ".agentbox");
1715
1877
  }
1716
1878
  function logsDir() {
1717
- return join8(stateDir(), "logs");
1879
+ return join10(stateDir(), "logs");
1718
1880
  }
1719
1881
  function openCommandLog(command) {
1720
1882
  const dir = logsDir();
1721
1883
  mkdirSync2(dir, { recursive: true });
1722
- const path = join8(dir, `${command}.log`);
1723
- const prev = join8(dir, `${command}.log.prev`);
1884
+ const path = join10(dir, `${command}.log`);
1885
+ const prev = join10(dir, `${command}.log.prev`);
1724
1886
  try {
1725
1887
  renameSync(path, prev);
1726
1888
  } catch {
@@ -1780,7 +1942,7 @@ function openCommandLog(command) {
1780
1942
  };
1781
1943
  }
1782
1944
  function updateLatestSymlink(dir, target) {
1783
- const link = join8(dir, "latest.log");
1945
+ const link = join10(dir, "latest.log");
1784
1946
  try {
1785
1947
  unlinkSync(link);
1786
1948
  } catch {
@@ -1835,7 +1997,7 @@ function resolveLimits(box, flags) {
1835
1997
  }
1836
1998
 
1837
1999
  // src/portless-prompt.ts
1838
- import { confirm, isCancel as isCancel2, log as log6, spinner as spinner2 } from "@clack/prompts";
2000
+ import { confirm, isCancel as isCancel2, log as log7, spinner as spinner2 } from "@clack/prompts";
1839
2001
  async function setupPortlessHost() {
1840
2002
  let state = await detectPortless();
1841
2003
  if (!state.installed) {
@@ -1845,13 +2007,13 @@ async function setupPortlessHost() {
1845
2007
  resetPortlessCache();
1846
2008
  s2.stop(ok ? "portless installed" : "portless install failed");
1847
2009
  if (!ok) {
1848
- log6.warn(`Could not install Portless \u2014 run \`${portlessInstallHint()}\` yourself.`);
2010
+ log7.warn(`Could not install Portless \u2014 run \`${portlessInstallHint()}\` yourself.`);
1849
2011
  return;
1850
2012
  }
1851
2013
  state = await detectPortless();
1852
2014
  }
1853
2015
  if (state.proxyRunning) {
1854
- log6.info("Portless proxy already running \u2014 boxes will use it.");
2016
+ log7.info("Portless proxy already running \u2014 boxes will use it.");
1855
2017
  return;
1856
2018
  }
1857
2019
  const s = spinner2();
@@ -1863,7 +2025,7 @@ async function setupPortlessHost() {
1863
2025
  s.stop("portless proxy started");
1864
2026
  } else {
1865
2027
  s.stop("portless proxy did not start");
1866
- log6.warn(`Could not start the Portless proxy \u2014 run \`${portlessStartHint()}\` yourself.`);
2028
+ log7.warn(`Could not start the Portless proxy \u2014 run \`${portlessStartHint()}\` yourself.`);
1867
2029
  }
1868
2030
  }
1869
2031
  async function maybePromptPortless(args) {
@@ -1878,7 +2040,7 @@ async function maybePromptPortless(args) {
1878
2040
  try {
1879
2041
  await setConfigValue("global", "portless.enabled", answer, args.cwd, { raw: false });
1880
2042
  } catch (err) {
1881
- log6.warn(
2043
+ log7.warn(
1882
2044
  `Could not save the Portless preference: ${err instanceof Error ? err.message : String(err)}`
1883
2045
  );
1884
2046
  }
@@ -1887,43 +2049,83 @@ async function maybePromptPortless(args) {
1887
2049
  }
1888
2050
 
1889
2051
  // src/wizard.ts
1890
- import { confirm as confirm2, isCancel as isCancel3, log as log7, multiselect } from "@clack/prompts";
1891
- import { basename } from "path";
2052
+ import { confirm as confirm2, isCancel as isCancel3, log as log8, multiselect } from "@clack/prompts";
2053
+ import { basename as basename2 } from "path";
1892
2054
 
1893
2055
  // src/provider/cloud-backend.ts
1894
2056
  async function cloudBackendForProvider(provider) {
1895
2057
  switch (provider) {
1896
2058
  case "daytona":
1897
- return (await import("./dist-PTJ6CEQY.js")).daytonaBackend;
2059
+ return (await import("./dist-47LVLYUV.js")).daytonaBackend;
1898
2060
  case "hetzner":
1899
- return (await import("./dist-WMQDMTWS.js")).hetznerBackend;
2061
+ return (await import("./dist-SWUOU34W.js")).hetznerBackend;
1900
2062
  case "vercel":
1901
- return (await import("./dist-RAZP76VX.js")).vercelBackend;
2063
+ return (await import("./dist-24PY2ZMO.js")).vercelBackend;
1902
2064
  default:
1903
2065
  return null;
1904
2066
  }
1905
2067
  }
1906
2068
 
1907
2069
  // src/checkpoint-lookup.ts
1908
- async function checkpointExistsForProvider(provider, projectRoot, ref) {
1909
- if (provider === "docker") {
1910
- return await resolveCheckpoint(projectRoot, ref) !== null;
2070
+ function short(sha) {
2071
+ return sha.slice(0, 12);
2072
+ }
2073
+ async function evaluateDockerCheckpoint(projectRoot, ref) {
2074
+ const head = await resolveCheckpoint(projectRoot, ref);
2075
+ if (!head) return { state: "missing" };
2076
+ if (!await imageExists(head.manifest.image)) return { state: "missing" };
2077
+ const fp = head.manifest.baseFingerprint;
2078
+ if (head.manifest.schema === 2 || !fp) {
2079
+ return {
2080
+ state: "stale",
2081
+ reason: "captured before checkpoint versioning; base image unverifiable"
2082
+ };
1911
2083
  }
1912
- if (await resolveCloudCheckpoint(projectRoot, provider, ref) === null) return false;
2084
+ const current = readPreparedDockerState()?.base?.contextSha256 ?? (await computeDockerContextFingerprint())?.contextSha256;
2085
+ if (current && fp !== current) {
2086
+ return {
2087
+ state: "stale",
2088
+ reason: `base image updated since capture (captured ${short(fp)}, current ${short(current)})`
2089
+ };
2090
+ }
2091
+ return { state: "fresh" };
2092
+ }
2093
+ async function evaluateCloudCheckpoint(provider, projectRoot, ref) {
2094
+ const found = await resolveCloudCheckpoint(projectRoot, provider, ref);
2095
+ if (!found) return { state: "missing" };
1913
2096
  try {
1914
2097
  const backend = await cloudBackendForProvider(provider);
1915
- if (!backend) return true;
1916
- const { live } = await probeCloudCheckpoint(backend, projectRoot, ref);
1917
- return live;
2098
+ if (backend) {
2099
+ const { live } = await probeCloudCheckpoint(backend, projectRoot, ref);
2100
+ if (!live) return { state: "missing" };
2101
+ }
1918
2102
  } catch {
1919
- return true;
1920
2103
  }
2104
+ const fp = found.manifest.baseFingerprint;
2105
+ if (found.manifest.schema < 2 || !fp) {
2106
+ return {
2107
+ state: "stale",
2108
+ reason: "captured before checkpoint versioning; base snapshot unverifiable"
2109
+ };
2110
+ }
2111
+ const current = currentCloudBaseFingerprint(provider);
2112
+ if (current && fp !== current) {
2113
+ return {
2114
+ state: "stale",
2115
+ reason: `base snapshot updated since capture (captured ${short(fp)}, current ${short(current)})`
2116
+ };
2117
+ }
2118
+ return { state: "fresh" };
2119
+ }
2120
+ async function evaluateCheckpoint(provider, projectRoot, ref) {
2121
+ if (provider === "docker") return evaluateDockerCheckpoint(projectRoot, ref);
2122
+ return evaluateCloudCheckpoint(provider, projectRoot, ref);
1921
2123
  }
1922
2124
 
1923
2125
  // src/wizard.ts
1924
2126
  var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
1925
2127
  function buildSetupInitialPrompt(workspace) {
1926
- const name = basename(workspace);
2128
+ const name = basename2(workspace);
1927
2129
  return `The user just opened a new agentbox sandbox for "${name}" but the workspace has no agentbox.yaml yet. Please run the /agentbox-setup skill (or read ${IN_BOX_SETUP_GUIDE_PATH} if the skill is not loaded), then explore /workspace and propose an agentbox.yaml. Save the file to /workspace/agentbox.yaml. Then run \`agentbox-ctl reload\` from inside the box so the already-running supervisor applies the new config and immediately runs the declared tasks and autostarts the services (no box restart needed). When done, summarise what services and tasks you declared, and remind the user how to land the file on the host (commit through the bind-mounted .git, or "agentbox download env" on the host).`;
1928
2130
  }
1929
2131
  var WIZARD_AUTOLAUNCH_ENV = "AGENTBOX_WIZARD_AUTOLAUNCH";
@@ -1933,25 +2135,37 @@ async function maybeRunSetupWizard(args) {
1933
2135
  if (process.env[WIZARD_AUTOLAUNCH_ENV] === "1") {
1934
2136
  if (args.command !== "claude") return { action: "proceed" };
1935
2137
  const envFiles = parseEnvFilesFromEnv(process.env[WIZARD_ENV_FILES_ENV]);
1936
- if (args.checkpointRef && await checkpointAppliesHere(args)) {
1937
- return { action: "proceed", envFilesToImport: envFiles };
1938
- }
1939
2138
  const proj2 = await findProjectRoot(args.workspace);
1940
- if (proj2.hasAgentboxYaml) return { action: "proceed", envFilesToImport: envFiles };
1941
- return {
1942
- action: "launch-with-prompt",
1943
- initialPrompt: buildSetupInitialPrompt(proj2.root),
1944
- envFilesToImport: envFiles
1945
- };
2139
+ return nonInteractiveOutcome(args, proj2, await checkpointStatus(args, proj2.root), envFiles);
1946
2140
  }
1947
- if (args.yes) return { action: "proceed" };
1948
- if (!process.stdin.isTTY) return { action: "proceed" };
1949
2141
  const proj = await findProjectRoot(args.workspace);
1950
- if (proj.hasAgentboxYaml) return { action: "proceed" };
1951
- if (args.checkpointRef && await checkpointAppliesHere(args)) {
1952
- log7.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
2142
+ const status = await checkpointStatus(args, proj.root);
2143
+ if (args.yes || !process.stdin.isTTY) {
2144
+ return nonInteractiveOutcome(args, proj, status, void 0);
2145
+ }
2146
+ const fromDefault = args.checkpointFromDefault !== false;
2147
+ if (proj.hasAgentboxYaml) {
2148
+ return { action: "proceed", discardCheckpoint: discardOnMissing(status, fromDefault) };
2149
+ }
2150
+ if (status.state === "fresh" || status.state === "stale" && !fromDefault) {
2151
+ log8.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
1953
2152
  return { action: "proceed" };
1954
2153
  }
2154
+ let discardCheckpoint = false;
2155
+ let recreateChosen = false;
2156
+ if (status.state === "stale" && fromDefault) {
2157
+ const recreate = await confirm2({
2158
+ message: `Snapshot "${args.checkpointRef}" is stale (${status.reason}). Start from base and run Setup Wizard? (No = use it anyway)`,
2159
+ initialValue: true
2160
+ });
2161
+ if (isCancel3(recreate) || !recreate) {
2162
+ return { action: "proceed" };
2163
+ }
2164
+ discardCheckpoint = true;
2165
+ recreateChosen = true;
2166
+ } else if (status.state === "missing") {
2167
+ discardCheckpoint = discardOnMissing(status, fromDefault) ?? false;
2168
+ }
1955
2169
  let envFilesToImport;
1956
2170
  if (!args.withEnv) {
1957
2171
  const found = await scanHostEnvFiles(proj.root, WIZARD_ENV_SCAN_PATTERNS);
@@ -1967,23 +2181,57 @@ async function maybeRunSetupWizard(args) {
1967
2181
  }
1968
2182
  }
1969
2183
  }
1970
- const go = await confirm2({
1971
- message: "New project: run setup wizard? Will install dependencies and setup agentbox.yaml",
1972
- initialValue: true
1973
- });
1974
- if (isCancel3(go) || !go) return { action: "proceed", envFilesToImport };
1975
- if (args.command === "create") return { action: "switch-to-claude", envFilesToImport };
2184
+ if (!recreateChosen) {
2185
+ const go = await confirm2({
2186
+ message: "New project: run setup wizard? Will install dependencies and setup agentbox.yaml",
2187
+ initialValue: true
2188
+ });
2189
+ if (isCancel3(go) || !go) {
2190
+ return {
2191
+ action: "proceed",
2192
+ envFilesToImport,
2193
+ discardCheckpoint: discardCheckpoint || void 0
2194
+ };
2195
+ }
2196
+ }
2197
+ if (args.command === "create") {
2198
+ return {
2199
+ action: "switch-to-claude",
2200
+ envFilesToImport,
2201
+ discardCheckpoint: discardCheckpoint || void 0
2202
+ };
2203
+ }
1976
2204
  return {
1977
2205
  action: "launch-with-prompt",
1978
2206
  initialPrompt: buildSetupInitialPrompt(proj.root),
1979
- envFilesToImport
2207
+ envFilesToImport,
2208
+ discardCheckpoint: discardCheckpoint || void 0
1980
2209
  };
1981
2210
  }
1982
- async function checkpointAppliesHere(args) {
1983
- if (!args.checkpointRef) return false;
1984
- const proj = await findProjectRoot(args.workspace);
2211
+ async function checkpointStatus(args, projectRoot) {
2212
+ if (!args.checkpointRef) return { state: "missing" };
1985
2213
  const provider = args.provider ?? "docker";
1986
- return checkpointExistsForProvider(provider, proj.root, args.checkpointRef);
2214
+ return evaluateCheckpoint(provider, projectRoot, args.checkpointRef);
2215
+ }
2216
+ function discardOnMissing(status, fromDefault) {
2217
+ return status.state === "missing" && fromDefault ? true : void 0;
2218
+ }
2219
+ function nonInteractiveOutcome(args, proj, status, envFilesToImport) {
2220
+ const fromDefault = args.checkpointFromDefault !== false;
2221
+ const usableAsIs = status.state === "fresh" || status.state === "stale" && !fromDefault;
2222
+ const discardCheckpoint = fromDefault && (status.state === "missing" || status.state === "stale" && !proj.hasAgentboxYaml) ? true : void 0;
2223
+ if (usableAsIs || proj.hasAgentboxYaml) {
2224
+ return { action: "proceed", envFilesToImport, discardCheckpoint };
2225
+ }
2226
+ if (args.command === "claude") {
2227
+ return {
2228
+ action: "launch-with-prompt",
2229
+ initialPrompt: buildSetupInitialPrompt(proj.root),
2230
+ envFilesToImport,
2231
+ discardCheckpoint
2232
+ };
2233
+ }
2234
+ return { action: "proceed", envFilesToImport, discardCheckpoint };
1987
2235
  }
1988
2236
  function serializeEnvFilesForEnv(files) {
1989
2237
  if (!files || files.length === 0) return void 0;
@@ -2024,6 +2272,7 @@ function pickCreateOpts(opts) {
2024
2272
  withPlaywright: opts.withPlaywright,
2025
2273
  withEnv: opts.withEnv,
2026
2274
  vnc: opts.vnc,
2275
+ resync: opts.resync,
2027
2276
  sharedDockerCache: opts.sharedDockerCache,
2028
2277
  portless: opts.portless,
2029
2278
  sessionName: opts.sessionName,
@@ -2038,7 +2287,7 @@ function logPrune(rebuild) {
2038
2287
  if (rebuild.prunedBytes <= 0) return;
2039
2288
  const mb = Math.round(rebuild.prunedBytes / 1024 / 1024);
2040
2289
  const n = rebuild.pruned.length;
2041
- log8.info(`pruned ${String(n)} stale plugin cache${n === 1 ? "" : "s"} (${String(mb)} MB freed)`);
2290
+ log9.info(`pruned ${String(n)} stale plugin cache${n === 1 ? "" : "s"} (${String(mb)} MB freed)`);
2042
2291
  }
2043
2292
  var RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
2044
2293
  async function attachClaudeWrapped(box, sessionName, reattach, onError, openIn) {
@@ -2102,7 +2351,7 @@ async function maybeRunClaudeLogin(args) {
2102
2351
  const message = args.authSource === "auth-file" ? "You're on a legacy API token (shows as 'Claude API'). Sign in with your Claude subscription instead?" : "Sign in with your Claude subscription? (saved and reused by every box)";
2103
2352
  const answer = await confirm3({ message, initialValue: true });
2104
2353
  if (isCancel4(answer) || !answer) {
2105
- log8.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
2354
+ log9.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
2106
2355
  return;
2107
2356
  }
2108
2357
  const s = spinner3();
@@ -2116,10 +2365,10 @@ async function maybeRunClaudeLogin(args) {
2116
2365
  s.stop("image ready");
2117
2366
  const exitCode = await runClaudeLoginContainer(args.image, ["--claudeai"]);
2118
2367
  if (exitCode !== 0) {
2119
- log8.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
2368
+ log9.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
2120
2369
  return;
2121
2370
  }
2122
- log8.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
2371
+ log9.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
2123
2372
  }
2124
2373
  async function maybeRunCloudClaudeLogin(args) {
2125
2374
  if (!process.stdin.isTTY || args.yes) return;
@@ -2130,7 +2379,7 @@ async function maybeRunCloudClaudeLogin(args) {
2130
2379
  const message = expired ? "Your saved Claude login looks expired. Sign in again? (saved and reused by every box)" : "Sign in with your Claude subscription? (saved and reused by every box)";
2131
2380
  const answer = await confirm3({ message, initialValue: true });
2132
2381
  if (isCancel4(answer) || !answer) {
2133
- log8.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
2382
+ log9.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
2134
2383
  return;
2135
2384
  }
2136
2385
  const s = spinner3();
@@ -2144,12 +2393,15 @@ async function maybeRunCloudClaudeLogin(args) {
2144
2393
  s.stop("image ready");
2145
2394
  const exitCode = await runClaudeLoginContainer(args.image, ["--claudeai"]);
2146
2395
  if (exitCode !== 0) {
2147
- log8.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
2396
+ log9.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
2148
2397
  return;
2149
2398
  }
2150
- log8.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
2399
+ log9.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
2151
2400
  }
2152
- var claudeCommand = new Command2("claude").description("Create a sandboxed box and launch Claude Code in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
2401
+ var claudeCommand = new Command2("claude").description("Create a sandboxed box and launch Claude Code in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option(
2402
+ "--host-snapshot",
2403
+ "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)"
2404
+ ).option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
2153
2405
  "--snapshot <ref>",
2154
2406
  "start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
2155
2407
  ).option("--image <ref>", "override the box image").option("-y, --yes", "skip prompts, accept defaults").option(
@@ -2172,15 +2424,15 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2172
2424
  "--with-env",
2173
2425
  "copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
2174
2426
  ).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
2427
+ "--no-resync",
2428
+ "do not sync the box with the host on start (default: merge the host's current branch + overlay its uncommitted/untracked changes, keeping the box's version on conflict)"
2429
+ ).option(
2175
2430
  "--shared-docker-cache",
2176
2431
  "use the shared 'agentbox-docker-cache' volume for in-box docker images (preserved on destroy; only one box can run at a time when set)"
2177
2432
  ).option(
2178
2433
  "--portless",
2179
2434
  "map the box web app to https://<name>.localhost via the Portless proxy (Docker Desktop)"
2180
- ).option("--no-portless", "do not register a Portless alias for this box").option("--session-name <name>", "tmux session name (default from config; built-in: claude)").option("--memory <size>", "memory ceiling (e.g. 512m, 2g); unset = unlimited").option("--cpus <n>", "CPU count cap (fractional ok, e.g. 1.5); unset = unlimited").option("--pids-limit <n>", "max process count (PIDs cgroup); unset = unlimited").option("--disk <size>", "best-effort writable-layer size (e.g. 10g); no-op on overlay2/macOS").option(
2181
- "--provider <name>",
2182
- "sandbox backend: 'docker' (default) or 'daytona' for a cloud box"
2183
- ).option(
2435
+ ).option("--no-portless", "do not register a Portless alias for this box").option("--session-name <name>", "tmux session name (default from config; built-in: claude)").option("--memory <size>", "memory ceiling (e.g. 512m, 2g); unset = unlimited").option("--cpus <n>", "CPU count cap (fractional ok, e.g. 1.5); unset = unlimited").option("--pids-limit <n>", "max process count (PIDs cgroup); unset = unlimited").option("--disk <size>", "best-effort writable-layer size (e.g. 10g); no-op on overlay2/macOS").option("--provider <name>", "sandbox backend: 'docker' (default) or 'daytona' for a cloud box").option(
2184
2436
  "--from-branch <ref>",
2185
2437
  "base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first."
2186
2438
  ).option(
@@ -2204,6 +2456,9 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2204
2456
  ).option(
2205
2457
  "--resume <id>",
2206
2458
  "teleport the specified host Claude Code session id into the box and resume from it"
2459
+ ).option(
2460
+ "--plan <path>",
2461
+ 'copy a Claude Code plan file (e.g. ~/.claude/plans/<slug>.md) into the box, launch claude with --permission-mode plan, and seed a "resume the plan" prompt'
2207
2462
  ).argument(
2208
2463
  "[claude-args...]",
2209
2464
  "extra args passed to claude inside the box; place after `--`, e.g. `agentbox claude -- --model sonnet`"
@@ -2214,14 +2469,23 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2214
2469
  intro("Starting Claude in a box...");
2215
2470
  let resumeMode = null;
2216
2471
  if (opts.continue === true && opts.resume) {
2217
- log8.error("only one of -c / --continue / --resume can be passed");
2472
+ log9.error("only one of -c / --continue / --resume can be passed");
2218
2473
  cmdLog.close();
2219
2474
  process.exit(2);
2220
2475
  }
2221
2476
  if (opts.continue === true) resumeMode = { kind: "continue" };
2222
2477
  else if (opts.resume) resumeMode = { kind: "resume", id: opts.resume };
2223
2478
  if (resumeMode && opts.initialPrompt && opts.initialPrompt.length > 0) {
2224
- log8.error("-i / --initial-prompt cannot be combined with -c / --resume (seeding a new turn into a resumed session is not supported).");
2479
+ log9.error(
2480
+ "-i / --initial-prompt cannot be combined with -c / --resume (seeding a new turn into a resumed session is not supported)."
2481
+ );
2482
+ cmdLog.close();
2483
+ process.exit(2);
2484
+ }
2485
+ if (opts.plan && opts.initialPrompt && opts.initialPrompt.length > 0) {
2486
+ log9.error(
2487
+ '--plan cannot be combined with -i / --initial-prompt (--plan already seeds an interactive "resume the plan" turn).'
2488
+ );
2225
2489
  cmdLog.close();
2226
2490
  process.exit(2);
2227
2491
  }
@@ -2236,7 +2500,24 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2236
2500
  });
2237
2501
  } catch (err) {
2238
2502
  if (err instanceof TeleportError) {
2239
- log8.error(err.message);
2503
+ log9.error(err.message);
2504
+ cmdLog.close();
2505
+ process.exit(2);
2506
+ }
2507
+ throw err;
2508
+ }
2509
+ }
2510
+ let planPrepared = null;
2511
+ if (opts.plan) {
2512
+ try {
2513
+ planPrepared = await resolvePlanTeleport({
2514
+ planPath: opts.plan,
2515
+ hostCwd: opts.workspace,
2516
+ log: (line) => cmdLog.write(line)
2517
+ });
2518
+ } catch (err) {
2519
+ if (err instanceof TeleportError) {
2520
+ log9.error(err.message);
2240
2521
  cmdLog.close();
2241
2522
  process.exit(2);
2242
2523
  }
@@ -2257,7 +2538,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2257
2538
  });
2258
2539
  } catch (err) {
2259
2540
  if (err instanceof MissingAgentCredsError) {
2260
- log8.error(err.message);
2541
+ log9.error(err.message);
2261
2542
  cmdLog.close();
2262
2543
  process.exit(2);
2263
2544
  }
@@ -2265,13 +2546,19 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2265
2546
  }
2266
2547
  const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
2267
2548
  const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
2549
+ const carryForQueue = await runQueuedCarryGate({
2550
+ projectRoot,
2551
+ opts,
2552
+ onLog: (line) => cmdLog.write(line),
2553
+ onClose: () => cmdLog.close()
2554
+ });
2268
2555
  const result = await submitQueueJob({
2269
2556
  agent: "claude-code",
2270
2557
  boxName: opts.name ?? "",
2271
2558
  providerName,
2272
2559
  prompt: opts.initialPrompt,
2273
2560
  agentArgs: claudeArgs,
2274
- createOpts: pickCreateOpts(opts),
2561
+ createOpts: { ...pickCreateOpts(opts), carry: carryForQueue },
2275
2562
  maxRunningOverride,
2276
2563
  maxWorkingOverride
2277
2564
  });
@@ -2315,13 +2602,13 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2315
2602
  onLog: (line) => cmdLog.write(line)
2316
2603
  });
2317
2604
  if (gate.decision === "cancel") {
2318
- log8.warn("carry: cancelled \u2014 not creating the box");
2605
+ log9.warn("carry: cancelled \u2014 not creating the box");
2319
2606
  cmdLog.close();
2320
2607
  process.exit(0);
2321
2608
  }
2322
2609
  if (gate.decision === "approve") carryEntries = gate.entries;
2323
2610
  } catch (err) {
2324
- log8.error(err instanceof Error ? err.message : String(err));
2611
+ log9.error(err instanceof Error ? err.message : String(err));
2325
2612
  cmdLog.close();
2326
2613
  process.exit(1);
2327
2614
  }
@@ -2330,11 +2617,22 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2330
2617
  yes: !!opts.yes,
2331
2618
  command: "claude",
2332
2619
  checkpointRef,
2620
+ checkpointFromDefault: !(opts.snapshot && opts.snapshot.length > 0),
2333
2621
  provider: providerName,
2334
2622
  withEnv: cfg.effective.box.withEnv
2335
2623
  });
2624
+ const effectiveCheckpointRef = wiz.discardCheckpoint ? void 0 : checkpointRef;
2336
2625
  let effectiveClaudeArgs = claudeArgs;
2337
- if (wiz.action === "launch-with-prompt" && wiz.initialPrompt) {
2626
+ if (planPrepared) {
2627
+ const hasPermissionMode = effectiveClaudeArgs.some(
2628
+ (a) => a === "--permission-mode" || a.startsWith("--permission-mode=")
2629
+ );
2630
+ if (!hasPermissionMode) {
2631
+ effectiveClaudeArgs = [...effectiveClaudeArgs, "--permission-mode", "plan"];
2632
+ }
2633
+ const planPrompt = `Resume the plan at ~/.claude/plans/${planPrepared.sessionId}`;
2634
+ effectiveClaudeArgs = buildPromptArgs("claude-code", planPrompt, effectiveClaudeArgs);
2635
+ } else if (wiz.action === "launch-with-prompt" && wiz.initialPrompt) {
2338
2636
  effectiveClaudeArgs = buildPromptArgs("claude-code", wiz.initialPrompt, claudeArgs);
2339
2637
  }
2340
2638
  effectiveClaudeArgs = applyClaudeSkipPermissions(effectiveClaudeArgs, cfg.effective);
@@ -2351,7 +2649,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2351
2649
  }));
2352
2650
  } catch (err) {
2353
2651
  if (err instanceof FromBranchError || err instanceof UseBranchError) {
2354
- log8.error(err.message);
2652
+ log9.error(err.message);
2355
2653
  cmdLog.close();
2356
2654
  process.exit(2);
2357
2655
  }
@@ -2365,7 +2663,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2365
2663
  request: {
2366
2664
  workspacePath: opts.workspace,
2367
2665
  name: opts.name,
2368
- checkpointRef,
2666
+ checkpointRef: effectiveCheckpointRef,
2369
2667
  image: cfg.effective.box.image,
2370
2668
  withPlaywright,
2371
2669
  withEnv: cfg.effective.box.withEnv,
@@ -2384,18 +2682,28 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2384
2682
  verbose: opts.verbose === true,
2385
2683
  openIn: cfg.effective.attach.openIn,
2386
2684
  attach: opts.attach !== false,
2387
- beforeStart: resumePrepared ? async (box) => {
2685
+ beforeStart: resumePrepared || planPrepared ? async (box) => {
2388
2686
  try {
2389
- await uploadTeleport({
2390
- box,
2391
- provider,
2392
- resolved: resumePrepared,
2393
- log: (line) => cmdLog.write(line)
2394
- });
2395
- return { agentArgsPrefix: resumePrepared.forwardArgs };
2687
+ if (resumePrepared) {
2688
+ await uploadTeleport({
2689
+ box,
2690
+ provider,
2691
+ resolved: resumePrepared,
2692
+ log: (line) => cmdLog.write(line)
2693
+ });
2694
+ }
2695
+ if (planPrepared) {
2696
+ await uploadTeleport({
2697
+ box,
2698
+ provider,
2699
+ resolved: planPrepared,
2700
+ log: (line) => cmdLog.write(line)
2701
+ });
2702
+ }
2703
+ return { agentArgsPrefix: resumePrepared?.forwardArgs ?? [] };
2396
2704
  } catch (err) {
2397
2705
  if (err instanceof TeleportError) {
2398
- log8.error(err.message);
2706
+ log9.error(err.message);
2399
2707
  cmdLog.close();
2400
2708
  process.exit(2);
2401
2709
  }
@@ -2416,9 +2724,10 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2416
2724
  workspacePath: opts.workspace,
2417
2725
  name: opts.name,
2418
2726
  useSnapshot,
2419
- checkpointRef,
2727
+ checkpointRef: effectiveCheckpointRef,
2420
2728
  fromBranch,
2421
2729
  useBranch,
2730
+ resyncOnStart: opts.resync,
2422
2731
  image: cfg.effective.box.image,
2423
2732
  claudeConfig: { isolate: cfg.effective.box.isolateClaudeConfig },
2424
2733
  claudeEnv: resolved.env,
@@ -2465,8 +2774,35 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2465
2774
  } catch (err) {
2466
2775
  if (err instanceof TeleportError) {
2467
2776
  s.stop("teleport failed");
2468
- log8.error(err.message);
2469
- log8.info(
2777
+ log9.error(err.message);
2778
+ log9.info(
2779
+ `The box ${result.record.container} is up but unused. Destroy it with: agentbox destroy ${result.record.container} -y`
2780
+ );
2781
+ cmdLog.close();
2782
+ process.exit(2);
2783
+ }
2784
+ throw err;
2785
+ }
2786
+ }
2787
+ if (planPrepared) {
2788
+ s.message("uploading plan into box");
2789
+ cmdLog.write("uploading plan into box");
2790
+ try {
2791
+ const provider = await providerForBox(result.record);
2792
+ await uploadTeleport({
2793
+ box: result.record,
2794
+ provider,
2795
+ resolved: planPrepared,
2796
+ log: (line) => {
2797
+ s.message(clampSpinnerLine(line));
2798
+ cmdLog.write(line);
2799
+ }
2800
+ });
2801
+ } catch (err) {
2802
+ if (err instanceof TeleportError) {
2803
+ s.stop("plan upload failed");
2804
+ log9.error(err.message);
2805
+ log9.info(
2470
2806
  `The box ${result.record.container} is up but unused. Destroy it with: agentbox destroy ${result.record.container} -y`
2471
2807
  );
2472
2808
  cmdLog.close();
@@ -2475,6 +2811,18 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2475
2811
  throw err;
2476
2812
  }
2477
2813
  }
2814
+ const createResyncWarning = result.resync ? buildResyncWarning(result.resync) : null;
2815
+ let pendingCreateResyncWarn = null;
2816
+ if (createResyncWarning) {
2817
+ const hasSeed = Boolean(planPrepared) || wiz.action === "launch-with-prompt" && Boolean(wiz.initialPrompt) || Boolean(resumePrepared);
2818
+ if (hasSeed) pendingCreateResyncWarn = createResyncWarning;
2819
+ else
2820
+ effectiveClaudeArgs = buildPromptArgs(
2821
+ "claude-code",
2822
+ createResyncWarning,
2823
+ effectiveClaudeArgs
2824
+ );
2825
+ }
2478
2826
  s.message("starting claude session");
2479
2827
  await startClaudeSession({
2480
2828
  container: result.record.container,
@@ -2482,12 +2830,15 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
2482
2830
  sessionName,
2483
2831
  boxName: result.record.name
2484
2832
  });
2833
+ if (pendingCreateResyncWarn) log9.warn(pendingCreateResyncWarn);
2485
2834
  const nSuffix = typeof result.record.projectIndex === "number" ? ` \xB7 n ${String(result.record.projectIndex)}` : "";
2486
2835
  s.stop(`box ready${nSuffix}`);
2487
2836
  logPrune(rebuild);
2488
2837
  for (const f of rebuild.failed) {
2489
- log8.warn(`plugin install failed for ${f.dir}; claude may still load it. stderr:
2490
- ${f.stderr.trim()}`);
2838
+ log9.warn(
2839
+ `plugin install failed for ${f.dir}; claude may still load it. stderr:
2840
+ ${f.stderr.trim()}`
2841
+ );
2491
2842
  }
2492
2843
  maybeShowInstallHint();
2493
2844
  await printLaunchRecap({
@@ -2497,7 +2848,7 @@ ${f.stderr.trim()}`);
2497
2848
  workspacePath: opts.workspace,
2498
2849
  fromBranch,
2499
2850
  useBranch,
2500
- checkpointRef,
2851
+ checkpointRef: effectiveCheckpointRef,
2501
2852
  attaching: opts.attach !== false
2502
2853
  });
2503
2854
  if (opts.attach === false) {
@@ -2514,10 +2865,10 @@ ${f.stderr.trim()}`);
2514
2865
  s.stop("failed");
2515
2866
  cmdLog.write(`FAIL: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
2516
2867
  if (err instanceof ClaudeSessionError) {
2517
- log8.error(err.message);
2868
+ log9.error(err.message);
2518
2869
  if (containerName) {
2519
- log8.info(`The box ${containerName} is still running. Destroy it with:`);
2520
- log8.info(` agentbox destroy ${containerName} -y`);
2870
+ log9.info(`The box ${containerName} is still running. Destroy it with:`);
2871
+ log9.info(` agentbox destroy ${containerName} -y`);
2521
2872
  }
2522
2873
  cmdLog.close();
2523
2874
  process.exit(1);
@@ -2538,6 +2889,7 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
2538
2889
  };
2539
2890
  }
2540
2891
  if (attachIn !== void 0) cliOverrides.attach = { openIn: attachIn };
2892
+ if (opts.resync !== void 0) cliOverrides.box = { resyncOnStart: opts.resync };
2541
2893
  const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides });
2542
2894
  const sessionName = cfg.effective.claude.sessionName;
2543
2895
  const openIn = cfg.effective.attach.openIn;
@@ -2572,6 +2924,7 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
2572
2924
  });
2573
2925
  const s = spinner3();
2574
2926
  s.start("preparing box");
2927
+ const wasDown = insp.state === "paused" || insp.state === "stopped";
2575
2928
  if (insp.state === "paused") {
2576
2929
  s.message("unpausing box");
2577
2930
  await unpauseBox(box.id);
@@ -2579,6 +2932,12 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
2579
2932
  s.message("starting box");
2580
2933
  await startBox(box.id);
2581
2934
  }
2935
+ const resyncWarning = await maybeResyncWorkspace({
2936
+ box,
2937
+ enabled: cfg.effective.box.resyncOnStart && wasDown,
2938
+ projectRoot: cfg.projectRoot,
2939
+ spinner: s
2940
+ });
2582
2941
  const syncConfig = opts.syncConfig !== false;
2583
2942
  if (syncConfig) {
2584
2943
  s.message("syncing ~/.claude into box volume");
@@ -2618,12 +2977,15 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
2618
2977
  } catch (err) {
2619
2978
  if (err instanceof TeleportError) {
2620
2979
  s.stop("teleport failed");
2621
- log8.error(err.message);
2980
+ log9.error(err.message);
2622
2981
  process.exit(2);
2623
2982
  }
2624
2983
  throw err;
2625
2984
  }
2626
2985
  }
2986
+ if (resyncWarning && !resumePrepared) {
2987
+ effectiveArgs = buildPromptArgs("claude-code", resyncWarning, effectiveArgs);
2988
+ }
2627
2989
  s.message("starting claude session");
2628
2990
  await startClaudeSession({
2629
2991
  container: box.container,
@@ -2632,10 +2994,13 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
2632
2994
  boxName: box.name
2633
2995
  });
2634
2996
  s.stop(`box ${box.container} ready`);
2997
+ if (resyncWarning && resumePrepared) log9.warn(resyncWarning);
2635
2998
  logPrune(rebuild);
2636
2999
  for (const f of rebuild.failed) {
2637
- log8.warn(`plugin install failed for ${f.dir}; claude may still load it. stderr:
2638
- ${f.stderr.trim()}`);
3000
+ log9.warn(
3001
+ `plugin install failed for ${f.dir}; claude may still load it. stderr:
3002
+ ${f.stderr.trim()}`
3003
+ );
2639
3004
  }
2640
3005
  if (!wantAttach) {
2641
3006
  outro(
@@ -2673,7 +3038,7 @@ var claudeAttachCommand = new Command2("attach").description(
2673
3038
  await startOrAttachClaude(box, [], { ...opts, syncConfig: false });
2674
3039
  } catch (err) {
2675
3040
  if (err instanceof ClaudeSessionError) {
2676
- log8.error(err.message);
3041
+ log9.error(err.message);
2677
3042
  process.exit(1);
2678
3043
  }
2679
3044
  handleLifecycleError(err);
@@ -2705,7 +3070,7 @@ var claudeStartCommand = new Command2("start").description(
2705
3070
  let effectiveClaudeArgs = shifted && idOrName ? [idOrName, ...claudeArgs] : claudeArgs;
2706
3071
  let resumeMode = null;
2707
3072
  if (opts.continue === true && opts.resume) {
2708
- log8.error("only one of -c / --continue / --resume can be passed");
3073
+ log9.error("only one of -c / --continue / --resume can be passed");
2709
3074
  process.exit(2);
2710
3075
  }
2711
3076
  if (opts.continue === true) resumeMode = { kind: "continue" };
@@ -2720,7 +3085,7 @@ var claudeStartCommand = new Command2("start").description(
2720
3085
  });
2721
3086
  } catch (err) {
2722
3087
  if (err instanceof TeleportError) {
2723
- log8.error(err.message);
3088
+ log9.error(err.message);
2724
3089
  process.exit(2);
2725
3090
  }
2726
3091
  throw err;
@@ -2747,7 +3112,7 @@ var claudeStartCommand = new Command2("start").description(
2747
3112
  effectiveClaudeArgs = [...resumePrepared.forwardArgs, ...effectiveClaudeArgs];
2748
3113
  } catch (err) {
2749
3114
  if (err instanceof TeleportError) {
2750
- log8.error(err.message);
3115
+ log9.error(err.message);
2751
3116
  process.exit(2);
2752
3117
  }
2753
3118
  throw err;
@@ -2766,7 +3131,7 @@ var claudeStartCommand = new Command2("start").description(
2766
3131
  await startOrAttachClaude(box, effectiveClaudeArgs, opts, resumePrepared);
2767
3132
  } catch (err) {
2768
3133
  if (err instanceof ClaudeSessionError) {
2769
- log8.error(err.message);
3134
+ log9.error(err.message);
2770
3135
  process.exit(1);
2771
3136
  }
2772
3137
  handleLifecycleError(err);
@@ -2780,7 +3145,7 @@ var claudeLoginCommand = new Command2("login").description(
2780
3145
  ).action(async (args) => {
2781
3146
  intro("Signing in to Claude...");
2782
3147
  if (!process.stdin.isTTY) {
2783
- log8.error("`agentbox claude login` needs an interactive terminal.");
3148
+ log9.error("`agentbox claude login` needs an interactive terminal.");
2784
3149
  process.exit(1);
2785
3150
  }
2786
3151
  try {
@@ -2792,7 +3157,7 @@ var claudeLoginCommand = new Command2("login").description(
2792
3157
  s.stop("image ready");
2793
3158
  const exitCode = await runClaudeLoginContainer(image, args);
2794
3159
  if (exitCode !== 0) {
2795
- log8.warn(`\`claude auth login\` exited with code ${String(exitCode)}`);
3160
+ log9.warn(`\`claude auth login\` exited with code ${String(exitCode)}`);
2796
3161
  process.exit(exitCode);
2797
3162
  }
2798
3163
  outro("signed in \u2014 credentials saved for future boxes");
@@ -2805,17 +3170,18 @@ claudeCommand.addCommand(claudeStartCommand);
2805
3170
  claudeCommand.addCommand(claudeLoginCommand);
2806
3171
 
2807
3172
  // src/commands/checkpoint.ts
2808
- import { confirm as confirm4, isCancel as isCancel5, log as log9 } from "@clack/prompts";
3173
+ import { confirm as confirm4, isCancel as isCancel5, log as log10 } from "@clack/prompts";
2809
3174
  import { Command as Command3 } from "commander";
3175
+ import { basename as basename3 } from "path";
2810
3176
  var CLOUD_BACKENDS = ["daytona", "hetzner", "vercel"];
2811
3177
  async function cloudProviderFor(backend) {
2812
3178
  switch (backend) {
2813
3179
  case "daytona":
2814
- return (await import("./dist-PTJ6CEQY.js")).daytonaProvider;
3180
+ return (await import("./dist-47LVLYUV.js")).daytonaProvider;
2815
3181
  case "hetzner":
2816
- return (await import("./dist-WMQDMTWS.js")).hetznerProvider;
3182
+ return (await import("./dist-SWUOU34W.js")).hetznerProvider;
2817
3183
  case "vercel":
2818
- return (await import("./dist-RAZP76VX.js")).vercelProvider;
3184
+ return (await import("./dist-24PY2ZMO.js")).vercelProvider;
2819
3185
  }
2820
3186
  }
2821
3187
  var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
@@ -2839,10 +3205,10 @@ var createSub = new Command3("create").description("Capture a box state as a pro
2839
3205
  }
2840
3206
  const insp = await inspectBox(box.id);
2841
3207
  if (insp.state === "paused") {
2842
- log9.info("box is paused; unpausing");
3208
+ log10.info("box is paused; unpausing");
2843
3209
  await unpauseBox(box.id);
2844
3210
  } else if (insp.state === "stopped") {
2845
- log9.info("box is stopped; starting");
3211
+ log10.info("box is stopped; starting");
2846
3212
  await startBox(box.id);
2847
3213
  } else if (insp.state === "missing") {
2848
3214
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -2877,13 +3243,13 @@ var createSub = new Command3("create").description("Capture a box state as a pro
2877
3243
  setDefault: opts.setDefault === true,
2878
3244
  replace: opts.replace === true,
2879
3245
  maxLayers: cfg.effective.checkpoint.maxLayers,
2880
- onLog: (line) => log9.info(line)
3246
+ onLog: (line) => log10.info(line)
2881
3247
  });
2882
- log9.success(
3248
+ log10.success(
2883
3249
  `checkpoint ${info.name} (${info.manifest.type}) -> ${info.dir}` + (opts.setDefault ? " [project default]" : "")
2884
3250
  );
2885
3251
  if (!opts.setDefault) {
2886
- log9.info(
3252
+ log10.info(
2887
3253
  `make it the default for new boxes: agentbox checkpoint set-default ${info.name}`
2888
3254
  );
2889
3255
  }
@@ -2898,8 +3264,78 @@ var createSub = new Command3("create").description("Capture a box state as a pro
2898
3264
  handleLifecycleError(err);
2899
3265
  }
2900
3266
  });
2901
- var lsSub = new Command3("ls").description("List this project's checkpoints (both docker and cloud)").action(async () => {
3267
+ function dockerRow(c, def) {
3268
+ const flag = c.name === def ? " *default" : "";
3269
+ return `${c.name} docker (${c.manifest.type}) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
3270
+ `;
3271
+ }
3272
+ function cloudRow(c, backend, def) {
3273
+ const flag = c.name === def ? " *default" : "";
3274
+ return `${c.name} ${backend} (snapshot) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
3275
+ `;
3276
+ }
3277
+ async function listAllProjects() {
3278
+ const projects = await listProjectsConfigured();
3279
+ const rootByHash = new Map(projects.map((p) => [p.hash, p.originalPath]));
3280
+ const dockerGroups = await listAllCheckpoints();
3281
+ const cloudGroups = await Promise.all(
3282
+ CLOUD_BACKENDS.map(async (backend) => ({
3283
+ backend,
3284
+ groups: await listAllCloudCheckpoints(backend)
3285
+ }))
3286
+ );
3287
+ const bySegment = /* @__PURE__ */ new Map();
3288
+ const ensure = (segment) => {
3289
+ let m = bySegment.get(segment);
3290
+ if (!m) {
3291
+ m = { projectRoot: rootByHash.get(segment.slice(0, 16)), docker: [], cloud: [] };
3292
+ bySegment.set(segment, m);
3293
+ }
3294
+ return m;
3295
+ };
3296
+ for (const g of dockerGroups) ensure(g.segment).docker = g.items;
3297
+ for (const { backend, groups } of cloudGroups) {
3298
+ for (const g of groups) ensure(g.segment).cloud.push({ backend, items: g.items });
3299
+ }
3300
+ if (bySegment.size === 0) {
3301
+ process.stdout.write("no checkpoints found\n");
3302
+ return;
3303
+ }
3304
+ const entries = [...bySegment.entries()].sort(([sa, a], [sb, b]) => {
3305
+ const la = a.projectRoot ? basename3(a.projectRoot) : sa;
3306
+ const lb = b.projectRoot ? basename3(b.projectRoot) : sb;
3307
+ return la.localeCompare(lb) || sa.localeCompare(sb);
3308
+ });
3309
+ let first = true;
3310
+ for (const [segment, m] of entries) {
3311
+ let defDocker = "";
3312
+ const defCloud = /* @__PURE__ */ new Map();
3313
+ if (m.projectRoot) {
3314
+ const cfg = await loadEffectiveConfig(m.projectRoot).catch(() => null);
3315
+ if (cfg) {
3316
+ defDocker = resolveDefaultCheckpoint(cfg.effective, "docker");
3317
+ for (const { backend } of m.cloud) {
3318
+ defCloud.set(backend, resolveDefaultCheckpoint(cfg.effective, backend));
3319
+ }
3320
+ }
3321
+ }
3322
+ const label = m.projectRoot ? basename3(m.projectRoot) : segment;
3323
+ const loc = m.projectRoot ?? "(project config not found)";
3324
+ process.stdout.write(`${first ? "" : "\n"}${label} (${loc})
3325
+ `);
3326
+ first = false;
3327
+ for (const c of m.docker) process.stdout.write(` ${dockerRow(c, defDocker)}`);
3328
+ for (const { backend, items } of m.cloud) {
3329
+ for (const c of items) process.stdout.write(` ${cloudRow(c, backend, defCloud.get(backend) ?? "")}`);
3330
+ }
3331
+ }
3332
+ }
3333
+ var lsSub = new Command3("ls").description("List this project's checkpoints (both docker and cloud); -g for all projects").option("-g, --global", "include checkpoints from all projects").action(async (opts) => {
2902
3334
  try {
3335
+ if (opts.global) {
3336
+ await listAllProjects();
3337
+ return;
3338
+ }
2903
3339
  const projectRoot = (await findProjectRoot(process.cwd())).root;
2904
3340
  const cfg = await loadEffectiveConfig(projectRoot);
2905
3341
  const defDocker = resolveDefaultCheckpoint(cfg.effective, "docker");
@@ -2918,19 +3354,11 @@ var lsSub = new Command3("ls").description("List this project's checkpoints (bot
2918
3354
  return;
2919
3355
  }
2920
3356
  for (const c of dockerList) {
2921
- const flag = c.name === defDocker ? " *default" : "";
2922
- process.stdout.write(
2923
- `${c.name} docker (${c.manifest.type}) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
2924
- `
2925
- );
3357
+ process.stdout.write(dockerRow(c, defDocker));
2926
3358
  }
2927
3359
  for (const { backend, def, items } of cloudLists) {
2928
3360
  for (const c of items) {
2929
- const flag = c.name === def ? " *default" : "";
2930
- process.stdout.write(
2931
- `${c.name} ${backend} (snapshot) from ${c.manifest.sourceBoxName} ${c.manifest.createdAt}${flag}
2932
- `
2933
- );
3361
+ process.stdout.write(cloudRow(c, backend, def));
2934
3362
  }
2935
3363
  }
2936
3364
  } catch (err) {
@@ -2998,7 +3426,7 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
2998
3426
  if (!opts.yes) {
2999
3427
  const ok = await confirm4({ message: `Delete checkpoint ${ref}?`, initialValue: false });
3000
3428
  if (isCancel5(ok) || !ok) {
3001
- log9.info("cancelled");
3429
+ log10.info("cancelled");
3002
3430
  return;
3003
3431
  }
3004
3432
  }
@@ -3019,7 +3447,7 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
3019
3447
  process.stdout.write(`removed ${backend} checkpoint ${ref}
3020
3448
  `);
3021
3449
  } catch (err) {
3022
- log9.warn(
3450
+ log10.warn(
3023
3451
  `${backend} checkpoint remove failed: ${err instanceof Error ? err.message : String(err)}`
3024
3452
  );
3025
3453
  }
@@ -3037,9 +3465,9 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
3037
3465
  for (const [key, projectValue, effectiveValue] of defKeys) {
3038
3466
  if (projectValue === ref) {
3039
3467
  await unsetConfigValue("project", key, projectRoot);
3040
- log9.info(`cleared project ${key} (was ${ref})`);
3468
+ log10.info(`cleared project ${key} (was ${ref})`);
3041
3469
  } else if (effectiveValue === ref) {
3042
- log9.warn(
3470
+ log10.warn(
3043
3471
  `${key} = ${ref} is set outside the per-project config (global or agentbox.yaml defaults) \u2014 clear it manually`
3044
3472
  );
3045
3473
  }
@@ -3050,17 +3478,17 @@ var rmSub = new Command3("rm").description("Delete a checkpoint (any provider th
3050
3478
  });
3051
3479
  async function runCloudCheckpointCreate(box, opts) {
3052
3480
  if (opts.merged) {
3053
- log9.warn("--merged is Docker-only (cloud snapshots are always flattened); ignoring");
3481
+ log10.warn("--merged is Docker-only (cloud snapshots are always flattened); ignoring");
3054
3482
  }
3055
3483
  const projectRoot = await projectRootFor(box.workspacePath, box.projectRoot);
3056
3484
  const name = opts.name ?? `${box.name}-${String(Date.now()).slice(-6)}`;
3057
3485
  const provider = await providerForBox(box);
3058
3486
  const state = await provider.probeState(box);
3059
3487
  if (state === "paused") {
3060
- log9.info("box is paused; resuming");
3488
+ log10.info("box is paused; resuming");
3061
3489
  await provider.resume(box);
3062
3490
  } else if (state === "stopped") {
3063
- log9.info("box is stopped; starting");
3491
+ log10.info("box is stopped; starting");
3064
3492
  await provider.start(box);
3065
3493
  } else if (state === "missing") {
3066
3494
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -3074,7 +3502,7 @@ async function runCloudCheckpointCreate(box, opts) {
3074
3502
  initialValue: false
3075
3503
  });
3076
3504
  if (isCancel5(ok) || !ok) {
3077
- log9.info("cancelled");
3505
+ log10.info("cancelled");
3078
3506
  return;
3079
3507
  }
3080
3508
  }
@@ -3089,21 +3517,21 @@ async function runCloudCheckpointCreate(box, opts) {
3089
3517
  try {
3090
3518
  const saved = await provider.extractAgentCredentials(box);
3091
3519
  if (saved.length > 0) {
3092
- log9.info(`saved ${saved.join(", ")} login to ~/.agentbox for future boxes`);
3520
+ log10.info(`saved ${saved.join(", ")} login to ~/.agentbox for future boxes`);
3093
3521
  }
3094
3522
  } catch (err) {
3095
- log9.warn(`agent credential extract skipped: ${err instanceof Error ? err.message : String(err)}`);
3523
+ log10.warn(`agent credential extract skipped: ${err instanceof Error ? err.message : String(err)}`);
3096
3524
  }
3097
3525
  }
3098
- log9.info(`capturing cloud snapshot '${name}' (this may take a few minutes)`);
3526
+ log10.info(`capturing cloud snapshot '${name}' (this may take a few minutes)`);
3099
3527
  const result = await provider.checkpoint.create(box, name);
3100
- log9.success(`checkpoint ${result.ref} (daytona snapshot) captured`);
3528
+ log10.success(`checkpoint ${result.ref} (daytona snapshot) captured`);
3101
3529
  if (opts.setDefault) {
3102
3530
  const key = defaultCheckpointConfigKey(box.provider ?? "daytona");
3103
3531
  await setConfigValue("project", key, result.ref, projectRoot);
3104
- log9.info(`set project default checkpoint (${key}) -> ${result.ref}`);
3532
+ log10.info(`set project default checkpoint (${key}) -> ${result.ref}`);
3105
3533
  } else {
3106
- log9.info(
3534
+ log10.info(
3107
3535
  `make it the default for new boxes: agentbox checkpoint set-default --provider ${box.provider ?? "daytona"} ${result.ref}`
3108
3536
  );
3109
3537
  }
@@ -3115,15 +3543,15 @@ var checkpointCommand = new Command3("checkpoint").alias("checkpoints").descript
3115
3543
 
3116
3544
  // src/commands/code.ts
3117
3545
  import { spawn } from "child_process";
3118
- import { log as log10 } from "@clack/prompts";
3546
+ import { log as log11 } from "@clack/prompts";
3119
3547
  import { Command as Command4, InvalidArgumentError } from "commander";
3120
3548
 
3121
3549
  // src/ssh-config.ts
3122
3550
  import { promises as fs } from "fs";
3123
- import { homedir as homedir8 } from "os";
3124
- import { join as join9 } from "path";
3551
+ import { homedir as homedir9 } from "os";
3552
+ import { join as join11 } from "path";
3125
3553
  function sshConfigPath() {
3126
- return join9(homedir8(), ".ssh", "config");
3554
+ return join11(homedir9(), ".ssh", "config");
3127
3555
  }
3128
3556
  function beginMarker(alias) {
3129
3557
  return `# BEGIN agentbox cloud box ${alias}`;
@@ -3174,7 +3602,7 @@ function buildBlock(opts) {
3174
3602
  }
3175
3603
  async function writeAgentboxSshAlias(opts) {
3176
3604
  const path = sshConfigPath();
3177
- await fs.mkdir(join9(homedir8(), ".ssh"), { recursive: true, mode: 448 });
3605
+ await fs.mkdir(join11(homedir9(), ".ssh"), { recursive: true, mode: 448 });
3178
3606
  const existing = await readConfig();
3179
3607
  const stripped = stripBlock(existing, opts.alias);
3180
3608
  const separator = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
@@ -3256,11 +3684,11 @@ var codeCommand = new Command4("code").description("Open a box in VS Code or Cur
3256
3684
  }
3257
3685
  const exit = await launchIde(folderUri, forcedIde);
3258
3686
  if (exit.code !== 0) {
3259
- log10.error(`failed to launch ${exit.flavor ? ideProfile(exit.flavor).displayName : "IDE"} via ${exit.via} (exit ${String(exit.code)})`);
3687
+ log11.error(`failed to launch ${exit.flavor ? ideProfile(exit.flavor).displayName : "IDE"} via ${exit.via} (exit ${String(exit.code)})`);
3260
3688
  process.stdout.write(folderUri + "\n");
3261
3689
  process.exit(1);
3262
3690
  }
3263
- log10.success(
3691
+ log11.success(
3264
3692
  `opening ${box.name} in ${ideProfile(exit.flavor).displayName} (${exit.via})`
3265
3693
  );
3266
3694
  } catch (err) {
@@ -3270,10 +3698,10 @@ var codeCommand = new Command4("code").description("Open a box in VS Code or Cur
3270
3698
  async function prepareDockerAttach(box, opts) {
3271
3699
  const insp = await inspectBox(box.id);
3272
3700
  if (insp.state === "paused") {
3273
- log10.info(`box is paused; unpausing`);
3701
+ log11.info(`box is paused; unpausing`);
3274
3702
  await unpauseBox(box.id);
3275
3703
  } else if (insp.state === "stopped") {
3276
- log10.info(`box is stopped; starting`);
3704
+ log11.info(`box is stopped; starting`);
3277
3705
  await startBox(box.id);
3278
3706
  } else if (insp.state === "missing") {
3279
3707
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -3284,9 +3712,9 @@ async function prepareDockerAttach(box, opts) {
3284
3712
  const lines = [];
3285
3713
  if (reply.timedOut.length > 0) lines.push(`timed out: ${reply.timedOut.join(", ")}`);
3286
3714
  if (reply.failed.length > 0) lines.push(`failed: ${reply.failed.join(", ")}`);
3287
- log10.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
3715
+ log11.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
3288
3716
  } else {
3289
- log10.success("all units ready");
3717
+ log11.success("all units ready");
3290
3718
  }
3291
3719
  }
3292
3720
  if (opts.autoTerminals) {
@@ -3296,14 +3724,14 @@ async function prepareDockerAttach(box, opts) {
3296
3724
  regen: opts.regenTasks
3297
3725
  });
3298
3726
  if (r.status === "wrote") {
3299
- log10.info(`wrote /workspace/.vscode/tasks.json (${String(services.length)} service(s))`);
3727
+ log11.info(`wrote /workspace/.vscode/tasks.json (${String(services.length)} service(s))`);
3300
3728
  } else if (r.status === "skipped-user-owned") {
3301
- log10.warn(
3729
+ log11.warn(
3302
3730
  "user-owned .vscode/tasks.json detected; skipping auto-terminals (pass --regen-tasks to overwrite)"
3303
3731
  );
3304
3732
  }
3305
3733
  } catch (err) {
3306
- log10.warn(
3734
+ log11.warn(
3307
3735
  `auto-terminals failed: ${err instanceof Error ? err.message : String(err)}`
3308
3736
  );
3309
3737
  }
@@ -3315,10 +3743,10 @@ async function prepareCloudAttach(box, opts) {
3315
3743
  const p = await providerForBox(box);
3316
3744
  const state = await p.probeState(box);
3317
3745
  if (state === "paused") {
3318
- log10.info("box is paused; resuming");
3746
+ log11.info("box is paused; resuming");
3319
3747
  await p.resume(box);
3320
3748
  } else if (state === "stopped") {
3321
- log10.info("box is stopped; starting");
3749
+ log11.info("box is stopped; starting");
3322
3750
  await p.start(box);
3323
3751
  } else if (state === "missing") {
3324
3752
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -3331,12 +3759,12 @@ async function prepareCloudAttach(box, opts) {
3331
3759
  const lines = [];
3332
3760
  if (reply.timedOut.length > 0) lines.push(`timed out: ${reply.timedOut.join(", ")}`);
3333
3761
  if (reply.failed.length > 0) lines.push(`failed: ${reply.failed.join(", ")}`);
3334
- log10.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
3762
+ log11.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
3335
3763
  } else {
3336
- log10.success("all units ready");
3764
+ log11.success("all units ready");
3337
3765
  }
3338
3766
  } catch (err) {
3339
- log10.warn(`wait-ready failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
3767
+ log11.warn(`wait-ready failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
3340
3768
  }
3341
3769
  }
3342
3770
  if (!p.buildAttach) {
@@ -3358,7 +3786,7 @@ async function prepareCloudAttach(box, opts) {
3358
3786
  user: target.user,
3359
3787
  identityFile: target.identityFile
3360
3788
  });
3361
- log10.info(`updated ~/.ssh/config alias ${alias}`);
3789
+ log11.info(`updated ~/.ssh/config alias ${alias}`);
3362
3790
  return `vscode-remote://ssh-remote+${alias}/workspace`;
3363
3791
  }
3364
3792
  async function runWaitReadyDocker(container, timeoutMs) {
@@ -3383,7 +3811,7 @@ async function launchIde(folderUri, forced) {
3383
3811
  if (code !== null) return code;
3384
3812
  const cursor = await tryCli("cursor", folderUri);
3385
3813
  if (cursor !== null) return cursor;
3386
- log10.warn("neither `code` nor `cursor` found in PATH; falling back to `open vscode://...`");
3814
+ log11.warn("neither `code` nor `cursor` found in PATH; falling back to `open vscode://...`");
3387
3815
  return launchOne("vscode", folderUri);
3388
3816
  }
3389
3817
  async function tryCli(flavor, folderUri) {
@@ -3396,7 +3824,7 @@ async function launchOne(flavor, folderUri) {
3396
3824
  const profile = ideProfile(flavor);
3397
3825
  const cliCode = await spawnCommand(profile.cli, ["--folder-uri", folderUri]);
3398
3826
  if (cliCode !== 127) return { code: cliCode, flavor, via: "cli" };
3399
- log10.warn(
3827
+ log11.warn(
3400
3828
  `\`${profile.cli}\` not found in PATH; falling back to \`${hostOpenCommand()} ${profile.protocolScheme}://...\` (the %2B URL-encoding bug may break attach)`
3401
3829
  );
3402
3830
  const url = `${profile.protocolScheme}://${folderUri.replace(/^vscode-remote:\/\//, "vscode-remote/")}`;
@@ -3404,10 +3832,10 @@ async function launchOne(flavor, folderUri) {
3404
3832
  return { code: fallback, flavor, via: "open" };
3405
3833
  }
3406
3834
  function spawnCommand(cmd, args) {
3407
- return new Promise((resolve3) => {
3835
+ return new Promise((resolve4) => {
3408
3836
  const child = spawn(cmd, args, { stdio: "ignore" });
3409
- child.once("error", () => resolve3(127));
3410
- child.once("exit", (code) => resolve3(code ?? -1));
3837
+ child.once("error", () => resolve4(127));
3838
+ child.once("exit", (code) => resolve4(code ?? -1));
3411
3839
  });
3412
3840
  }
3413
3841
  async function fetchServiceNamesDocker(container) {
@@ -3425,9 +3853,9 @@ async function fetchServiceNamesDocker(container) {
3425
3853
 
3426
3854
  // src/commands/codex.ts
3427
3855
  import { access } from "fs/promises";
3428
- import { homedir as homedir9 } from "os";
3429
- import { join as join10 } from "path";
3430
- import { confirm as confirm5, intro as intro2, isCancel as isCancel6, log as log11, outro as outro2, spinner as spinner4 } from "@clack/prompts";
3856
+ import { homedir as homedir10 } from "os";
3857
+ import { join as join12 } from "path";
3858
+ import { confirm as confirm5, intro as intro2, isCancel as isCancel6, log as log12, outro as outro2, spinner as spinner4 } from "@clack/prompts";
3431
3859
  import { Command as Command5 } from "commander";
3432
3860
  function reattachRef2(r) {
3433
3861
  return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
@@ -3442,6 +3870,7 @@ function pickCodexCreateOpts(opts) {
3442
3870
  withPlaywright: opts.withPlaywright,
3443
3871
  withEnv: opts.withEnv,
3444
3872
  vnc: opts.vnc,
3873
+ resync: opts.resync,
3445
3874
  sharedDockerCache: opts.sharedDockerCache,
3446
3875
  portless: opts.portless,
3447
3876
  sessionName: opts.sessionName,
@@ -3504,7 +3933,7 @@ async function maybeRunCodexLogin(args) {
3504
3933
  initialValue: true
3505
3934
  });
3506
3935
  if (isCancel6(answer) || !answer) {
3507
- log11.info("Skipped sign-in \u2014 codex will prompt you to sign in inside the box.");
3936
+ log12.info("Skipped sign-in \u2014 codex will prompt you to sign in inside the box.");
3508
3937
  return;
3509
3938
  }
3510
3939
  const s = spinner4();
@@ -3515,14 +3944,14 @@ async function maybeRunCodexLogin(args) {
3515
3944
  s.stop("image ready");
3516
3945
  const exitCode = await runCodexLoginContainer(args.image, []);
3517
3946
  if (exitCode !== 0) {
3518
- log11.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
3947
+ log12.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
3519
3948
  return;
3520
3949
  }
3521
- log11.success("Signed in to Codex \u2014 saved for future boxes.");
3950
+ log12.success("Signed in to Codex \u2014 saved for future boxes.");
3522
3951
  }
3523
3952
  async function cloudCodexCredAvailable(env = process.env) {
3524
3953
  if ((env["OPENAI_API_KEY"] ?? "").length > 0) return true;
3525
- for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join10(homedir9(), ".codex", "auth.json")]) {
3954
+ for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join12(homedir10(), ".codex", "auth.json")]) {
3526
3955
  try {
3527
3956
  await access(p);
3528
3957
  return true;
@@ -3539,7 +3968,7 @@ async function maybeRunCloudCodexLogin(args) {
3539
3968
  initialValue: true
3540
3969
  });
3541
3970
  if (isCancel6(answer) || !answer) {
3542
- log11.info("Skipped sign-in \u2014 codex will prompt you to sign in inside the box.");
3971
+ log12.info("Skipped sign-in \u2014 codex will prompt you to sign in inside the box.");
3543
3972
  return;
3544
3973
  }
3545
3974
  const s = spinner4();
@@ -3550,12 +3979,12 @@ async function maybeRunCloudCodexLogin(args) {
3550
3979
  s.stop("image ready");
3551
3980
  const exitCode = await runCodexLoginContainer(args.image, []);
3552
3981
  if (exitCode !== 0) {
3553
- log11.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
3982
+ log12.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
3554
3983
  return;
3555
3984
  }
3556
3985
  const { copied } = await extractCodexCredentials(SHARED_CODEX_VOLUME, args.image);
3557
- if (copied) log11.success("Signed in to Codex \u2014 saved for future boxes.");
3558
- else log11.warn("Codex login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
3986
+ if (copied) log12.success("Signed in to Codex \u2014 saved for future boxes.");
3987
+ else log12.warn("Codex login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
3559
3988
  }
3560
3989
  var codexCommand = new Command5("codex").description("Create a sandboxed box and launch OpenAI Codex in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
3561
3990
  "--snapshot <ref>",
@@ -3580,6 +4009,9 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3580
4009
  "--with-env",
3581
4010
  "copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
3582
4011
  ).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
4012
+ "--no-resync",
4013
+ "do not sync the box with the host on start (default: merge the host's current branch + overlay its uncommitted/untracked changes, keeping the box's version on conflict)"
4014
+ ).option(
3583
4015
  "--shared-docker-cache",
3584
4016
  "use the shared 'agentbox-docker-cache' volume for in-box docker images (preserved on destroy; only one box can run at a time when set)"
3585
4017
  ).option(
@@ -3622,14 +4054,14 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3622
4054
  intro2("Starting Codex in a box...");
3623
4055
  let resumeMode = null;
3624
4056
  if (opts.continue === true && opts.resume) {
3625
- log11.error("only one of -c / --continue / --resume can be passed");
4057
+ log12.error("only one of -c / --continue / --resume can be passed");
3626
4058
  cmdLog.close();
3627
4059
  process.exit(2);
3628
4060
  }
3629
4061
  if (opts.continue === true) resumeMode = { kind: "continue" };
3630
4062
  else if (opts.resume) resumeMode = { kind: "resume", id: opts.resume };
3631
4063
  if (resumeMode && opts.initialPrompt && opts.initialPrompt.length > 0) {
3632
- log11.error("-i / --initial-prompt cannot be combined with -c / --resume.");
4064
+ log12.error("-i / --initial-prompt cannot be combined with -c / --resume.");
3633
4065
  cmdLog.close();
3634
4066
  process.exit(2);
3635
4067
  }
@@ -3644,7 +4076,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3644
4076
  });
3645
4077
  } catch (err) {
3646
4078
  if (err instanceof TeleportError) {
3647
- log11.error(err.message);
4079
+ log12.error(err.message);
3648
4080
  cmdLog.close();
3649
4081
  process.exit(2);
3650
4082
  }
@@ -3667,7 +4099,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3667
4099
  });
3668
4100
  } catch (err) {
3669
4101
  if (err instanceof MissingAgentCredsError) {
3670
- log11.error(err.message);
4102
+ log12.error(err.message);
3671
4103
  cmdLog.close();
3672
4104
  process.exit(2);
3673
4105
  }
@@ -3675,13 +4107,19 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3675
4107
  }
3676
4108
  const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
3677
4109
  const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
4110
+ const carryForQueue = await runQueuedCarryGate({
4111
+ projectRoot,
4112
+ opts,
4113
+ onLog: (line) => cmdLog.write(line),
4114
+ onClose: () => cmdLog.close()
4115
+ });
3678
4116
  const result = await submitQueueJob({
3679
4117
  agent: "codex",
3680
4118
  boxName: opts.name ?? "",
3681
4119
  providerName,
3682
4120
  prompt: opts.initialPrompt,
3683
4121
  agentArgs: codexArgs,
3684
- createOpts: pickCodexCreateOpts(opts),
4122
+ createOpts: { ...pickCodexCreateOpts(opts), carry: carryForQueue },
3685
4123
  maxRunningOverride,
3686
4124
  maxWorkingOverride
3687
4125
  });
@@ -3701,13 +4139,13 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3701
4139
  onLog: (line) => cmdLog.write(line)
3702
4140
  });
3703
4141
  if (gate.decision === "cancel") {
3704
- log11.warn("carry: cancelled \u2014 not creating the box");
4142
+ log12.warn("carry: cancelled \u2014 not creating the box");
3705
4143
  cmdLog.close();
3706
4144
  process.exit(0);
3707
4145
  }
3708
4146
  if (gate.decision === "approve") carryEntries = gate.entries;
3709
4147
  } catch (err) {
3710
- log11.error(err instanceof Error ? err.message : String(err));
4148
+ log12.error(err instanceof Error ? err.message : String(err));
3711
4149
  cmdLog.close();
3712
4150
  process.exit(1);
3713
4151
  }
@@ -3724,7 +4162,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3724
4162
  }));
3725
4163
  } catch (err) {
3726
4164
  if (err instanceof FromBranchError || err instanceof UseBranchError) {
3727
- log11.error(err.message);
4165
+ log12.error(err.message);
3728
4166
  cmdLog.close();
3729
4167
  process.exit(2);
3730
4168
  }
@@ -3768,7 +4206,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3768
4206
  return { agentArgsPrefix: resumePrepared.forwardArgs };
3769
4207
  } catch (err) {
3770
4208
  if (err instanceof TeleportError) {
3771
- log11.error(err.message);
4209
+ log12.error(err.message);
3772
4210
  cmdLog.close();
3773
4211
  process.exit(2);
3774
4212
  }
@@ -3799,6 +4237,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3799
4237
  checkpointRef,
3800
4238
  fromBranch,
3801
4239
  useBranch,
4240
+ resyncOnStart: opts.resync,
3802
4241
  image: cfg.effective.box.image,
3803
4242
  codexConfig: { isolate: cfg.effective.box.isolateCodexConfig },
3804
4243
  withPlaywright,
@@ -3843,8 +4282,8 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3843
4282
  } catch (err) {
3844
4283
  if (err instanceof TeleportError) {
3845
4284
  s.stop("teleport failed");
3846
- log11.error(err.message);
3847
- log11.info(
4285
+ log12.error(err.message);
4286
+ log12.info(
3848
4287
  `The box ${result.record.container} is up but unused. Destroy it with: agentbox destroy ${result.record.container} -y`
3849
4288
  );
3850
4289
  cmdLog.close();
@@ -3853,6 +4292,10 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3853
4292
  throw err;
3854
4293
  }
3855
4294
  }
4295
+ const createResyncWarning = result.resync ? buildResyncWarning(result.resync) : null;
4296
+ if (createResyncWarning && !resumePrepared) {
4297
+ effectiveCodexArgs = buildPromptArgs("codex", createResyncWarning, effectiveCodexArgs);
4298
+ }
3856
4299
  s.message("starting codex session");
3857
4300
  await startCodexSession({
3858
4301
  container: result.record.container,
@@ -3861,6 +4304,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3861
4304
  });
3862
4305
  const nSuffix = typeof result.record.projectIndex === "number" ? ` \xB7 n ${String(result.record.projectIndex)}` : "";
3863
4306
  s.stop(`box ready${nSuffix}`);
4307
+ if (createResyncWarning && resumePrepared) log12.warn(createResyncWarning);
3864
4308
  await printLaunchRecap({
3865
4309
  record: result.record,
3866
4310
  mode: "codex",
@@ -3885,10 +4329,10 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
3885
4329
  s.stop("failed");
3886
4330
  cmdLog.write(`FAIL: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
3887
4331
  if (err instanceof CodexSessionError) {
3888
- log11.error(err.message);
4332
+ log12.error(err.message);
3889
4333
  if (containerName) {
3890
- log11.info(`The box ${containerName} is still running. Destroy it with:`);
3891
- log11.info(` agentbox destroy ${containerName} -y`);
4334
+ log12.info(`The box ${containerName} is still running. Destroy it with:`);
4335
+ log12.info(` agentbox destroy ${containerName} -y`);
3892
4336
  }
3893
4337
  cmdLog.close();
3894
4338
  process.exit(1);
@@ -3909,6 +4353,7 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
3909
4353
  };
3910
4354
  }
3911
4355
  if (attachIn !== void 0) cliOverrides.attach = { openIn: attachIn };
4356
+ if (opts.resync !== void 0) cliOverrides.box = { resyncOnStart: opts.resync };
3912
4357
  const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides });
3913
4358
  const sessionName = cfg.effective.codex.sessionName;
3914
4359
  const openIn = cfg.effective.attach.openIn;
@@ -3937,6 +4382,7 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
3937
4382
  await maybeRunCodexLogin({ image: box.image, yes: false });
3938
4383
  const s = spinner4();
3939
4384
  s.start("preparing box");
4385
+ const wasDown = insp.state === "paused" || insp.state === "stopped";
3940
4386
  if (insp.state === "paused") {
3941
4387
  s.message("unpausing box");
3942
4388
  await unpauseBox(box.id);
@@ -3944,6 +4390,12 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
3944
4390
  s.message("starting box");
3945
4391
  await startBox(box.id);
3946
4392
  }
4393
+ const resyncWarning = await maybeResyncWorkspace({
4394
+ box,
4395
+ enabled: cfg.effective.box.resyncOnStart && wasDown,
4396
+ projectRoot: cfg.projectRoot,
4397
+ spinner: s
4398
+ });
3947
4399
  const syncConfig = opts.syncConfig !== false;
3948
4400
  if (syncConfig && box.codexConfigVolume) {
3949
4401
  s.message("syncing ~/.codex into box volume");
@@ -3974,15 +4426,19 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
3974
4426
  } catch (err) {
3975
4427
  if (err instanceof TeleportError) {
3976
4428
  s.stop("teleport failed");
3977
- log11.error(err.message);
4429
+ log12.error(err.message);
3978
4430
  process.exit(2);
3979
4431
  }
3980
4432
  throw err;
3981
4433
  }
3982
4434
  }
4435
+ if (resyncWarning && !resumePrepared) {
4436
+ effectiveArgs = buildPromptArgs("codex", resyncWarning, effectiveArgs);
4437
+ }
3983
4438
  s.message("starting codex session");
3984
4439
  await startCodexSession({ container: box.container, codexArgs: effectiveArgs, sessionName });
3985
4440
  s.stop(`box ${box.container} ready`);
4441
+ if (resyncWarning && resumePrepared) log12.warn(resyncWarning);
3986
4442
  if (!wantAttach) {
3987
4443
  outro2(
3988
4444
  `session "${sessionName}" started \u2014 attach with: agentbox codex attach ${reattachRef2(box)}`
@@ -4019,7 +4475,7 @@ var codexAttachCommand = new Command5("attach").description(
4019
4475
  await startOrAttachCodex(box, [], { ...opts, syncConfig: false });
4020
4476
  } catch (err) {
4021
4477
  if (err instanceof CodexSessionError) {
4022
- log11.error(err.message);
4478
+ log12.error(err.message);
4023
4479
  process.exit(1);
4024
4480
  }
4025
4481
  handleLifecycleError(err);
@@ -4051,7 +4507,7 @@ var codexStartCommand = new Command5("start").description(
4051
4507
  let effectiveCodexArgs = shifted && idOrName ? [idOrName, ...codexArgs] : codexArgs;
4052
4508
  let resumeMode = null;
4053
4509
  if (opts.continue === true && opts.resume) {
4054
- log11.error("only one of -c / --continue / --resume can be passed");
4510
+ log12.error("only one of -c / --continue / --resume can be passed");
4055
4511
  process.exit(2);
4056
4512
  }
4057
4513
  if (opts.continue === true) resumeMode = { kind: "continue" };
@@ -4066,7 +4522,7 @@ var codexStartCommand = new Command5("start").description(
4066
4522
  });
4067
4523
  } catch (err) {
4068
4524
  if (err instanceof TeleportError) {
4069
- log11.error(err.message);
4525
+ log12.error(err.message);
4070
4526
  process.exit(2);
4071
4527
  }
4072
4528
  throw err;
@@ -4093,7 +4549,7 @@ var codexStartCommand = new Command5("start").description(
4093
4549
  effectiveCodexArgs = [...resumePrepared.forwardArgs, ...effectiveCodexArgs];
4094
4550
  } catch (err) {
4095
4551
  if (err instanceof TeleportError) {
4096
- log11.error(err.message);
4552
+ log12.error(err.message);
4097
4553
  process.exit(2);
4098
4554
  }
4099
4555
  throw err;
@@ -4112,7 +4568,7 @@ var codexStartCommand = new Command5("start").description(
4112
4568
  await startOrAttachCodex(box, effectiveCodexArgs, opts, resumePrepared);
4113
4569
  } catch (err) {
4114
4570
  if (err instanceof CodexSessionError) {
4115
- log11.error(err.message);
4571
+ log12.error(err.message);
4116
4572
  process.exit(1);
4117
4573
  }
4118
4574
  handleLifecycleError(err);
@@ -4126,7 +4582,7 @@ var codexLoginCommand = new Command5("login").description(
4126
4582
  ).action(async (args) => {
4127
4583
  intro2("Signing in to Codex...");
4128
4584
  if (!process.stdin.isTTY) {
4129
- log11.error("`agentbox codex login` needs an interactive terminal.");
4585
+ log12.error("`agentbox codex login` needs an interactive terminal.");
4130
4586
  process.exit(1);
4131
4587
  }
4132
4588
  try {
@@ -4140,7 +4596,7 @@ var codexLoginCommand = new Command5("login").description(
4140
4596
  s.stop("image ready");
4141
4597
  const exitCode = await runCodexLoginContainer(image, args);
4142
4598
  if (exitCode !== 0) {
4143
- log11.warn(`\`codex login\` exited with code ${String(exitCode)}`);
4599
+ log12.warn(`\`codex login\` exited with code ${String(exitCode)}`);
4144
4600
  process.exit(exitCode);
4145
4601
  }
4146
4602
  outro2("signed in \u2014 credentials saved for future boxes");
@@ -4154,9 +4610,9 @@ codexCommand.addCommand(codexLoginCommand);
4154
4610
 
4155
4611
  // src/commands/opencode.ts
4156
4612
  import { access as access2 } from "fs/promises";
4157
- import { homedir as homedir10 } from "os";
4158
- import { join as join11 } from "path";
4159
- import { confirm as confirm6, intro as intro3, isCancel as isCancel7, log as log12, outro as outro3, spinner as spinner5 } from "@clack/prompts";
4613
+ import { homedir as homedir11 } from "os";
4614
+ import { join as join13 } from "path";
4615
+ import { confirm as confirm6, intro as intro3, isCancel as isCancel7, log as log13, outro as outro3, spinner as spinner5 } from "@clack/prompts";
4160
4616
  import { Command as Command6 } from "commander";
4161
4617
  function reattachRef3(r) {
4162
4618
  return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
@@ -4171,6 +4627,7 @@ function pickOpencodeCreateOpts(opts) {
4171
4627
  withPlaywright: opts.withPlaywright,
4172
4628
  withEnv: opts.withEnv,
4173
4629
  vnc: opts.vnc,
4630
+ resync: opts.resync,
4174
4631
  sharedDockerCache: opts.sharedDockerCache,
4175
4632
  portless: opts.portless,
4176
4633
  sessionName: opts.sessionName,
@@ -4230,7 +4687,7 @@ async function maybeRunOpencodeLogin(args) {
4230
4687
  initialValue: true
4231
4688
  });
4232
4689
  if (isCancel7(answer) || !answer) {
4233
- log12.info("Skipped sign-in \u2014 opencode will prompt you to sign in inside the box.");
4690
+ log13.info("Skipped sign-in \u2014 opencode will prompt you to sign in inside the box.");
4234
4691
  return;
4235
4692
  }
4236
4693
  const s = spinner5();
@@ -4244,16 +4701,16 @@ async function maybeRunOpencodeLogin(args) {
4244
4701
  s.stop("image ready");
4245
4702
  const exitCode = await runOpencodeLoginContainer(args.image, []);
4246
4703
  if (exitCode !== 0) {
4247
- log12.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
4704
+ log13.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
4248
4705
  return;
4249
4706
  }
4250
- log12.success("Signed in to OpenCode \u2014 saved for future boxes.");
4707
+ log13.success("Signed in to OpenCode \u2014 saved for future boxes.");
4251
4708
  }
4252
4709
  async function cloudOpencodeCredAvailable(env = process.env) {
4253
4710
  for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
4254
4711
  if ((env[k] ?? "").length > 0) return true;
4255
4712
  }
4256
- for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join11(homedir10(), ".local", "share", "opencode", "auth.json")]) {
4713
+ for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join13(homedir11(), ".local", "share", "opencode", "auth.json")]) {
4257
4714
  try {
4258
4715
  await access2(p);
4259
4716
  return true;
@@ -4270,7 +4727,7 @@ async function maybeRunCloudOpencodeLogin(args) {
4270
4727
  initialValue: true
4271
4728
  });
4272
4729
  if (isCancel7(answer) || !answer) {
4273
- log12.info("Skipped sign-in \u2014 opencode will prompt you to sign in inside the box.");
4730
+ log13.info("Skipped sign-in \u2014 opencode will prompt you to sign in inside the box.");
4274
4731
  return;
4275
4732
  }
4276
4733
  const s = spinner5();
@@ -4284,12 +4741,12 @@ async function maybeRunCloudOpencodeLogin(args) {
4284
4741
  s.stop("image ready");
4285
4742
  const exitCode = await runOpencodeLoginContainer(args.image, []);
4286
4743
  if (exitCode !== 0) {
4287
- log12.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
4744
+ log13.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
4288
4745
  return;
4289
4746
  }
4290
4747
  const { copied } = await extractOpencodeCredentials(SHARED_OPENCODE_VOLUME, args.image);
4291
- if (copied) log12.success("Signed in to OpenCode \u2014 saved for future boxes.");
4292
- else log12.warn("OpenCode login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
4748
+ if (copied) log13.success("Signed in to OpenCode \u2014 saved for future boxes.");
4749
+ else log13.warn("OpenCode login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
4293
4750
  }
4294
4751
  var opencodeCommand = new Command6("opencode").description("Create a sandboxed box and launch OpenCode in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
4295
4752
  "--snapshot <ref>",
@@ -4308,6 +4765,9 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4308
4765
  "--with-env",
4309
4766
  "copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
4310
4767
  ).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
4768
+ "--no-resync",
4769
+ "do not sync the box with the host on start (default: merge the host's current branch + overlay its uncommitted/untracked changes, keeping the box's version on conflict)"
4770
+ ).option(
4311
4771
  "--shared-docker-cache",
4312
4772
  "use the shared 'agentbox-docker-cache' volume for in-box docker images (preserved on destroy; only one box can run at a time when set)"
4313
4773
  ).option(
@@ -4357,7 +4817,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4357
4817
  });
4358
4818
  } catch (err) {
4359
4819
  if (err instanceof TeleportError) {
4360
- log12.error(err.message);
4820
+ log13.error(err.message);
4361
4821
  cmdLog.close();
4362
4822
  process.exit(2);
4363
4823
  }
@@ -4380,7 +4840,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4380
4840
  });
4381
4841
  } catch (err) {
4382
4842
  if (err instanceof MissingAgentCredsError) {
4383
- log12.error(err.message);
4843
+ log13.error(err.message);
4384
4844
  cmdLog.close();
4385
4845
  process.exit(2);
4386
4846
  }
@@ -4388,13 +4848,19 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4388
4848
  }
4389
4849
  const maxRunningOverride = parseMaxOption("--max-running", opts.maxRunning);
4390
4850
  const maxWorkingOverride = parseMaxOption("--max-working", opts.maxWorking);
4851
+ const carryForQueue = await runQueuedCarryGate({
4852
+ projectRoot,
4853
+ opts,
4854
+ onLog: (line) => cmdLog.write(line),
4855
+ onClose: () => cmdLog.close()
4856
+ });
4391
4857
  const result = await submitQueueJob({
4392
4858
  agent: "opencode",
4393
4859
  boxName: opts.name ?? "",
4394
4860
  providerName,
4395
4861
  prompt: opts.initialPrompt,
4396
4862
  agentArgs: opencodeArgs,
4397
- createOpts: pickOpencodeCreateOpts(opts),
4863
+ createOpts: { ...pickOpencodeCreateOpts(opts), carry: carryForQueue },
4398
4864
  maxRunningOverride,
4399
4865
  maxWorkingOverride
4400
4866
  });
@@ -4414,13 +4880,13 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4414
4880
  onLog: (line) => cmdLog.write(line)
4415
4881
  });
4416
4882
  if (gate.decision === "cancel") {
4417
- log12.warn("carry: cancelled \u2014 not creating the box");
4883
+ log13.warn("carry: cancelled \u2014 not creating the box");
4418
4884
  cmdLog.close();
4419
4885
  process.exit(0);
4420
4886
  }
4421
4887
  if (gate.decision === "approve") carryEntries = gate.entries;
4422
4888
  } catch (err) {
4423
- log12.error(err instanceof Error ? err.message : String(err));
4889
+ log13.error(err instanceof Error ? err.message : String(err));
4424
4890
  cmdLog.close();
4425
4891
  process.exit(1);
4426
4892
  }
@@ -4437,7 +4903,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4437
4903
  }));
4438
4904
  } catch (err) {
4439
4905
  if (err instanceof FromBranchError || err instanceof UseBranchError) {
4440
- log12.error(err.message);
4906
+ log13.error(err.message);
4441
4907
  cmdLog.close();
4442
4908
  process.exit(2);
4443
4909
  }
@@ -4494,6 +4960,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4494
4960
  checkpointRef,
4495
4961
  fromBranch,
4496
4962
  useBranch,
4963
+ resyncOnStart: opts.resync,
4497
4964
  image: cfg.effective.box.image,
4498
4965
  opencodeConfig: { isolate: cfg.effective.box.isolateOpencodeConfig },
4499
4966
  withPlaywright,
@@ -4525,8 +4992,10 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4525
4992
  opencodeArgs,
4526
4993
  sessionName
4527
4994
  });
4995
+ const createResyncWarning = result.resync ? buildResyncWarning(result.resync) : null;
4528
4996
  const nSuffix = typeof result.record.projectIndex === "number" ? ` \xB7 n ${String(result.record.projectIndex)}` : "";
4529
4997
  s.stop(`box ready${nSuffix}`);
4998
+ if (createResyncWarning) log13.warn(createResyncWarning);
4530
4999
  await printLaunchRecap({
4531
5000
  record: result.record,
4532
5001
  mode: "opencode",
@@ -4551,10 +5020,10 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
4551
5020
  s.stop("failed");
4552
5021
  cmdLog.write(`FAIL: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
4553
5022
  if (err instanceof OpencodeSessionError) {
4554
- log12.error(err.message);
5023
+ log13.error(err.message);
4555
5024
  if (containerName) {
4556
- log12.info(`The box ${containerName} is still running. Destroy it with:`);
4557
- log12.info(` agentbox destroy ${containerName} -y`);
5025
+ log13.info(`The box ${containerName} is still running. Destroy it with:`);
5026
+ log13.info(` agentbox destroy ${containerName} -y`);
4558
5027
  }
4559
5028
  cmdLog.close();
4560
5029
  process.exit(1);
@@ -4569,6 +5038,7 @@ async function startOrAttachOpencode(box, opencodeArgs, opts) {
4569
5038
  const cliOverrides = {};
4570
5039
  if (opts.sessionName) cliOverrides.opencode = { sessionName: opts.sessionName };
4571
5040
  if (attachIn !== void 0) cliOverrides.attach = { openIn: attachIn };
5041
+ if (opts.resync !== void 0) cliOverrides.box = { resyncOnStart: opts.resync };
4572
5042
  const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides });
4573
5043
  const sessionName = cfg.effective.opencode.sessionName;
4574
5044
  const openIn = cfg.effective.attach.openIn;
@@ -4592,6 +5062,7 @@ async function startOrAttachOpencode(box, opencodeArgs, opts) {
4592
5062
  await maybeRunOpencodeLogin({ image: box.image, yes: false });
4593
5063
  const s = spinner5();
4594
5064
  s.start("preparing box");
5065
+ const wasDown = insp.state === "paused" || insp.state === "stopped";
4595
5066
  if (insp.state === "paused") {
4596
5067
  s.message("unpausing box");
4597
5068
  await unpauseBox(box.id);
@@ -4599,6 +5070,12 @@ async function startOrAttachOpencode(box, opencodeArgs, opts) {
4599
5070
  s.message("starting box");
4600
5071
  await startBox(box.id);
4601
5072
  }
5073
+ const resyncWarning = await maybeResyncWorkspace({
5074
+ box,
5075
+ enabled: cfg.effective.box.resyncOnStart && wasDown,
5076
+ projectRoot: cfg.projectRoot,
5077
+ spinner: s
5078
+ });
4602
5079
  const syncConfig = opts.syncConfig !== false;
4603
5080
  if (syncConfig && box.opencodeConfigVolume) {
4604
5081
  s.message("syncing OpenCode config into box volume");
@@ -4614,6 +5091,7 @@ async function startOrAttachOpencode(box, opencodeArgs, opts) {
4614
5091
  s.message("starting opencode session");
4615
5092
  await startOpencodeSession({ container: box.container, opencodeArgs, sessionName });
4616
5093
  s.stop(`box ${box.container} ready`);
5094
+ if (resyncWarning) log13.warn(resyncWarning);
4617
5095
  if (!wantAttach) {
4618
5096
  outro3(
4619
5097
  `session "${sessionName}" started \u2014 attach with: agentbox opencode attach ${reattachRef3(box)}`
@@ -4650,7 +5128,7 @@ var opencodeAttachCommand = new Command6("attach").description(
4650
5128
  await startOrAttachOpencode(box, [], { ...opts, syncConfig: false });
4651
5129
  } catch (err) {
4652
5130
  if (err instanceof OpencodeSessionError) {
4653
- log12.error(err.message);
5131
+ log13.error(err.message);
4654
5132
  process.exit(1);
4655
5133
  }
4656
5134
  handleLifecycleError(err);
@@ -4689,7 +5167,7 @@ var opencodeStartCommand = new Command6("start").description(
4689
5167
  });
4690
5168
  } catch (err) {
4691
5169
  if (err instanceof TeleportError) {
4692
- log12.error(err.message);
5170
+ log13.error(err.message);
4693
5171
  process.exit(2);
4694
5172
  }
4695
5173
  throw err;
@@ -4718,7 +5196,7 @@ var opencodeStartCommand = new Command6("start").description(
4718
5196
  await startOrAttachOpencode(box, effectiveOpencodeArgs, opts);
4719
5197
  } catch (err) {
4720
5198
  if (err instanceof OpencodeSessionError) {
4721
- log12.error(err.message);
5199
+ log13.error(err.message);
4722
5200
  process.exit(1);
4723
5201
  }
4724
5202
  handleLifecycleError(err);
@@ -4732,7 +5210,7 @@ var opencodeLoginCommand = new Command6("login").description(
4732
5210
  ).action(async (args) => {
4733
5211
  intro3("Signing in to OpenCode...");
4734
5212
  if (!process.stdin.isTTY) {
4735
- log12.error("`agentbox opencode login` needs an interactive terminal.");
5213
+ log13.error("`agentbox opencode login` needs an interactive terminal.");
4736
5214
  process.exit(1);
4737
5215
  }
4738
5216
  try {
@@ -4746,7 +5224,7 @@ var opencodeLoginCommand = new Command6("login").description(
4746
5224
  s.stop("image ready");
4747
5225
  const exitCode = await runOpencodeLoginContainer(image, args);
4748
5226
  if (exitCode !== 0) {
4749
- log12.warn(`\`opencode auth login\` exited with code ${String(exitCode)}`);
5227
+ log13.warn(`\`opencode auth login\` exited with code ${String(exitCode)}`);
4750
5228
  process.exit(exitCode);
4751
5229
  }
4752
5230
  outro3("signed in \u2014 credentials saved for future boxes");
@@ -5025,7 +5503,7 @@ function handleError(err) {
5025
5503
  var configCommand = new Command7("config").description("Read / write layered config (global, per-project, workspace `defaults:` block)").addCommand(getCommand).addCommand(setCommand).addCommand(unsetCommand).addCommand(listCommand).addCommand(pathCommand).addCommand(editCommand).addCommand(listProjectsCommand);
5026
5504
 
5027
5505
  // src/commands/cp.ts
5028
- import { log as log13 } from "@clack/prompts";
5506
+ import { log as log14 } from "@clack/prompts";
5029
5507
  import { Command as Command8 } from "commander";
5030
5508
  function parseBoxArg(arg) {
5031
5509
  const idx = arg.indexOf(":");
@@ -5108,10 +5586,10 @@ var cpCommand = new Command8("cp").description("Copy files between host and box
5108
5586
  }
5109
5587
  const insp = await inspectBox(box.id);
5110
5588
  if (insp.state === "paused") {
5111
- log13.info("box is paused; unpausing");
5589
+ log14.info("box is paused; unpausing");
5112
5590
  await unpauseBox(box.id);
5113
5591
  } else if (insp.state === "stopped") {
5114
- log13.info("box is stopped; starting");
5592
+ log14.info("box is stopped; starting");
5115
5593
  await startBox(box.id);
5116
5594
  } else if (insp.state === "missing") {
5117
5595
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -5119,7 +5597,7 @@ var cpCommand = new Command8("cp").description("Copy files between host and box
5119
5597
  if (parsed.direction === "upload") {
5120
5598
  const result = await uploadToBox(box, parsed.hostPath, parsed.boxPath);
5121
5599
  if (result.warn) {
5122
- log13.warn(`copied to ${box.name}:${result.finalPath}, but ${result.warn}`);
5600
+ log14.warn(`copied to ${box.name}:${result.finalPath}, but ${result.warn}`);
5123
5601
  } else {
5124
5602
  process.stdout.write(`copied to ${box.name}:${result.finalPath}
5125
5603
  `);
@@ -5135,13 +5613,12 @@ var cpCommand = new Command8("cp").description("Copy files between host and box
5135
5613
  });
5136
5614
 
5137
5615
  // src/commands/create.ts
5138
- import { intro as intro4, log as log14, outro as outro4 } from "@clack/prompts";
5616
+ import { intro as intro4, log as log15, outro as outro4 } from "@clack/prompts";
5139
5617
  import { Command as Command9 } from "commander";
5140
5618
  import { execSync, spawnSync as spawnSync2 } from "child_process";
5141
5619
  function buildCliOverrides(opts) {
5142
5620
  const box = {};
5143
5621
  if (opts.hostSnapshot !== void 0) box.hostSnapshot = opts.hostSnapshot;
5144
- if (opts.image !== void 0) box.image = opts.image;
5145
5622
  if (opts.withPlaywright === true) box.withPlaywright = true;
5146
5623
  if (opts.withEnv === true) box.withEnv = true;
5147
5624
  if (opts.vnc === false) box.vnc = false;
@@ -5179,7 +5656,12 @@ async function attachShell(record) {
5179
5656
  });
5180
5657
  process.exit(code);
5181
5658
  }
5182
- var createCommand = new Command9("create").description("Create and start a new agent box (Docker container with /workspace seeded via in-container git worktree)").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--provider <name>", "sandbox backend: 'docker' (default) or 'daytona' (cloud)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "bind the live workspace directly (host edits leak into reads)").option(
5659
+ var createCommand = new Command9("create").description(
5660
+ "Create and start a new agent box (Docker container with /workspace seeded via in-container git worktree)"
5661
+ ).option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--provider <name>", "sandbox backend: 'docker' (default) or 'daytona' (cloud)").option(
5662
+ "--host-snapshot",
5663
+ "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)"
5664
+ ).option("--no-host-snapshot", "bind the live workspace directly (host edits leak into reads)").option(
5183
5665
  "--snapshot <ref>",
5184
5666
  "start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
5185
5667
  ).option("--image <ref>", "override the box image", void 0).option(
@@ -5189,17 +5671,27 @@ var createCommand = new Command9("create").description("Create and start a new a
5189
5671
  "--with-env",
5190
5672
  "copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
5191
5673
  ).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
5674
+ "--no-resync",
5675
+ "when starting from a checkpoint, do not merge the host's current branch + overlay its uncommitted/untracked changes (default: do, keeping the box's version on conflict)"
5676
+ ).option(
5192
5677
  "--shared-docker-cache",
5193
5678
  "use the shared 'agentbox-docker-cache' volume for in-box docker images (preserved on destroy; only one box can run at a time when set)"
5194
5679
  ).option(
5195
5680
  "--portless",
5196
5681
  "map the box web app to https://<name>.localhost via the Portless proxy (Docker Desktop)"
5197
- ).option("--no-portless", "do not register a Portless alias for this box").option("--memory <size>", "memory ceiling (e.g. 512m, 2g); unset = unlimited").option("--cpus <n>", "CPU count cap (fractional ok, e.g. 1.5); unset = unlimited").option("--pids-limit <n>", "max process count (PIDs cgroup); unset = unlimited").option("--disk <size>", "best-effort container writable-layer size (e.g. 10g); no-op on overlay2/macOS").option(
5682
+ ).option("--no-portless", "do not register a Portless alias for this box").option("--memory <size>", "memory ceiling (e.g. 512m, 2g); unset = unlimited").option("--cpus <n>", "CPU count cap (fractional ok, e.g. 1.5); unset = unlimited").option("--pids-limit <n>", "max process count (PIDs cgroup); unset = unlimited").option(
5683
+ "--disk <size>",
5684
+ "best-effort container writable-layer size (e.g. 10g); no-op on overlay2/macOS"
5685
+ ).option(
5686
+ "--size <spec>",
5687
+ "VM size for cloud providers. Hetzner: server type (e.g. cx33). Daytona: cpu-mem-disk GB (e.g. 4-8-20). Overrides box.size / box.size<Provider>."
5688
+ ).option(
5198
5689
  "--bundle-depth <n>",
5199
5690
  "cap commits shipped in the cloud-seed git bundle (daytona, hetzner). 0 = full history. Unset = adaptive (200 commits, re-bundle at 100 if >20 MB). Ignored for docker.",
5200
5691
  (v) => {
5201
5692
  const n = Number.parseInt(v, 10);
5202
- if (!Number.isInteger(n) || n < 0) throw new Error(`--bundle-depth: expected a non-negative integer, got "${v}"`);
5693
+ if (!Number.isInteger(n) || n < 0)
5694
+ throw new Error(`--bundle-depth: expected a non-negative integer, got "${v}"`);
5203
5695
  return n;
5204
5696
  }
5205
5697
  ).option(
@@ -5230,8 +5722,21 @@ var createCommand = new Command9("create").description("Create and start a new a
5230
5722
  const providerName = opts.provider ?? cfg.effective.box.provider ?? "docker";
5231
5723
  const checkpointRef = resolveCheckpointRef(
5232
5724
  opts,
5233
- resolveDefaultCheckpoint(cfg.effective, providerName)
5725
+ resolveDefaultCheckpoint(
5726
+ cfg.effective,
5727
+ providerName
5728
+ )
5729
+ );
5730
+ const sizeDefault = resolveBoxSize(
5731
+ cfg.effective,
5732
+ providerName
5234
5733
  );
5734
+ const effectiveSize = opts.size && opts.size.length > 0 ? opts.size : sizeDefault;
5735
+ const imageDefault = resolveBoxImage(
5736
+ cfg.effective,
5737
+ providerName
5738
+ );
5739
+ const effectiveImage = opts.image && opts.image.length > 0 ? opts.image : imageDefault;
5235
5740
  const isDocker = providerName === "docker";
5236
5741
  const isHetzner = providerName === "hetzner";
5237
5742
  let portlessEnabled;
@@ -5258,13 +5763,13 @@ var createCommand = new Command9("create").description("Create and start a new a
5258
5763
  onLog: (line) => cmdLog.write(line)
5259
5764
  });
5260
5765
  if (gate.decision === "cancel") {
5261
- log14.warn("carry: cancelled \u2014 not creating the box");
5766
+ log15.warn("carry: cancelled \u2014 not creating the box");
5262
5767
  cmdLog.close();
5263
5768
  process.exit(0);
5264
5769
  }
5265
5770
  if (gate.decision === "approve") carryEntries = gate.entries;
5266
5771
  } catch (err) {
5267
- log14.error(err instanceof Error ? err.message : String(err));
5772
+ log15.error(err instanceof Error ? err.message : String(err));
5268
5773
  cmdLog.close();
5269
5774
  process.exit(1);
5270
5775
  }
@@ -5273,9 +5778,11 @@ var createCommand = new Command9("create").description("Create and start a new a
5273
5778
  yes: !!opts.yes,
5274
5779
  command: "create",
5275
5780
  checkpointRef,
5781
+ checkpointFromDefault: !(opts.snapshot && opts.snapshot.length > 0),
5276
5782
  provider: providerName,
5277
5783
  withEnv: cfg.effective.box.withEnv
5278
5784
  });
5785
+ const effectiveCheckpointRef = wiz.discardCheckpoint ? void 0 : checkpointRef;
5279
5786
  if (wiz.action === "switch-to-claude" && isDocker) {
5280
5787
  process.env[WIZARD_AUTOLAUNCH_ENV] = "1";
5281
5788
  const serialized = serializeEnvFilesForEnv(wiz.envFilesToImport);
@@ -5312,7 +5819,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5312
5819
  } catch (err) {
5313
5820
  if (err instanceof FromBranchError || err instanceof UseBranchError) {
5314
5821
  s.stop("aborting: invalid branch selection");
5315
- log14.error(err.message);
5822
+ log15.error(err.message);
5316
5823
  cmdLog.close();
5317
5824
  process.exit(2);
5318
5825
  }
@@ -5321,8 +5828,8 @@ var createCommand = new Command9("create").description("Create and start a new a
5321
5828
  const result = await provider.create({
5322
5829
  workspacePath: opts.workspace,
5323
5830
  name: opts.name,
5324
- checkpointRef,
5325
- image: cfg.effective.box.image,
5831
+ checkpointRef: effectiveCheckpointRef,
5832
+ image: effectiveImage,
5326
5833
  allowPull: opts.build ? false : void 0,
5327
5834
  imageRegistry: cfg.effective.box.imageRegistry,
5328
5835
  withPlaywright,
@@ -5334,6 +5841,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5334
5841
  bundleDepth: cfg.effective.box.bundleDepth,
5335
5842
  fromBranch,
5336
5843
  useBranch,
5844
+ resyncOnStart: opts.resync,
5337
5845
  projectRoot,
5338
5846
  onLog: (line) => {
5339
5847
  s.message(line);
@@ -5344,6 +5852,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5344
5852
  sharedCache: cfg.effective.box.dockerCacheShared,
5345
5853
  portless: portlessEnabled,
5346
5854
  portlessStateDir: cfg.effective.portless.stateDir || void 0,
5855
+ ...effectiveSize ? { size: effectiveSize } : {},
5347
5856
  // Vercel-only sizing (box.vercelVcpus / vercelTimeoutMs). The cloud
5348
5857
  // scaffold reads these as overrides; other providers ignore them.
5349
5858
  ...provider.name === "vercel" ? {
@@ -5354,17 +5863,19 @@ var createCommand = new Command9("create").description("Create and start a new a
5354
5863
  }
5355
5864
  });
5356
5865
  s.stop(`box ${result.record.container} ready`);
5357
- log14.info(`id: ${result.record.id}`);
5866
+ const createResyncWarning = result.resync ? buildResyncWarning(result.resync) : null;
5867
+ if (createResyncWarning) log15.warn(createResyncWarning);
5868
+ log15.info(`id: ${result.record.id}`);
5358
5869
  if (typeof result.record.projectIndex === "number") {
5359
- log14.info(`n: ${String(result.record.projectIndex)} (in ${projectRoot})`);
5870
+ log15.info(`n: ${String(result.record.projectIndex)} (in ${projectRoot})`);
5360
5871
  }
5361
- log14.info(`container: ${result.record.container}`);
5362
- log14.info(`image: ${result.record.image}${result.imageBuilt ? " (built just now)" : ""}`);
5872
+ log15.info(`container: ${result.record.container}`);
5873
+ log15.info(`image: ${result.record.image}${result.imageBuilt ? " (built just now)" : ""}`);
5363
5874
  if (result.record.snapshotDir) {
5364
- log14.info(`snapshot: ${result.record.snapshotDir}`);
5875
+ log15.info(`snapshot: ${result.record.snapshotDir}`);
5365
5876
  }
5366
5877
  if (result.record.checkpointSource) {
5367
- log14.info(
5878
+ log15.info(
5368
5879
  `checkpoint: ${result.record.checkpointSource.ref} (${result.record.checkpointSource.type}) \u2192 ${result.record.checkpointImage ?? "(missing)"}`
5369
5880
  );
5370
5881
  }
@@ -5376,7 +5887,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5376
5887
  ` agentbox claude attach ${result.record.name}`,
5377
5888
  ` agentbox url ${result.record.name}`
5378
5889
  ];
5379
- log14.message(
5890
+ log15.message(
5380
5891
  [
5381
5892
  "",
5382
5893
  "Try it:",
@@ -5395,7 +5906,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5395
5906
  const protectedPaths = boxes.map((b) => b.projectRoot).filter((p) => typeof p === "string");
5396
5907
  const res = await pruneOrphanProjectConfigs({ protectedPaths });
5397
5908
  if (res.removed.length > 0) {
5398
- log14.info(
5909
+ log15.info(
5399
5910
  `cleaned ${String(res.removed.length)} orphan project config dir(s): ` + res.removed.map((r) => r.originalPath).join(", ")
5400
5911
  );
5401
5912
  }
@@ -5405,7 +5916,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5405
5916
  }
5406
5917
  outro4("done");
5407
5918
  if (attachClaudeAfter) {
5408
- const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-XWCVLO5V.js");
5919
+ const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-XKO4SHR3.js");
5409
5920
  await cloudAgentAttach2({
5410
5921
  box: result.record,
5411
5922
  binary: "claude",
@@ -5421,14 +5932,14 @@ var createCommand = new Command9("create").description("Create and start a new a
5421
5932
  s.stop("failed");
5422
5933
  const msg = err instanceof Error ? err.message : String(err);
5423
5934
  cmdLog.write(`FAIL: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
5424
- log14.error(msg);
5935
+ log15.error(msg);
5425
5936
  try {
5426
5937
  const running = execSync('docker ps --format "{{.Names}}"', {
5427
5938
  stdio: ["ignore", "pipe", "ignore"]
5428
5939
  }).toString().split("\n").filter((n) => n.startsWith("agentbox-"));
5429
5940
  if (running.length > 0) {
5430
- log14.warn(`leftover containers: ${running.join(", ")}`);
5431
- log14.warn(`remove with: docker rm -f ${running.join(" ")}`);
5941
+ log15.warn(`leftover containers: ${running.join(", ")}`);
5942
+ log15.warn(`remove with: docker rm -f ${running.join(" ")}`);
5432
5943
  }
5433
5944
  } catch {
5434
5945
  }
@@ -5441,7 +5952,7 @@ var createCommand = new Command9("create").description("Create and start a new a
5441
5952
 
5442
5953
  // src/commands/dashboard.ts
5443
5954
  import { spawn as spawn2 } from "child_process";
5444
- import { log as log15 } from "@clack/prompts";
5955
+ import { log as log16 } from "@clack/prompts";
5445
5956
  import { Command as Command10 } from "commander";
5446
5957
 
5447
5958
  // src/dashboard/layout.ts
@@ -6039,8 +6550,8 @@ var Compositor = class {
6039
6550
  this.drawChrome();
6040
6551
  this.scheduleRender();
6041
6552
  this.pollTimer = setInterval(() => void this.poll(), POLL_MS);
6042
- await new Promise((resolve3) => {
6043
- this.resolveDone = resolve3;
6553
+ await new Promise((resolve4) => {
6554
+ this.resolveDone = resolve4;
6044
6555
  });
6045
6556
  }
6046
6557
  async refreshBoxes() {
@@ -6965,7 +7476,7 @@ function toSidebar(b) {
6965
7476
  var dashboardCommand = new Command10("dashboard").description("Box list + the selected box live Agent session").argument("[box]", "initial box (default: first running box; -p restricts to the cwd project)").option("-p, --project", "only this project's boxes (default: all boxes globally)").action(async (idOrName, opts) => {
6966
7477
  try {
6967
7478
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
6968
- log15.error("agentbox dashboard needs an interactive terminal");
7479
+ log16.error("agentbox dashboard needs an interactive terminal");
6969
7480
  process.exit(2);
6970
7481
  }
6971
7482
  const backend = await loadPtyBackend();
@@ -6975,10 +7486,10 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
6975
7486
  ptySpawn = backend.ptySpawn;
6976
7487
  termCtor = backend.termCtor;
6977
7488
  } else {
6978
- log15.error(
7489
+ log16.error(
6979
7490
  "agentbox dashboard is unavailable here (native terminal backend failed to load)"
6980
7491
  );
6981
- log15.info("use `agentbox claude` / `agentbox claude attach` instead");
7492
+ log16.info("use `agentbox claude` / `agentbox claude attach` instead");
6982
7493
  process.exit(2);
6983
7494
  }
6984
7495
  const project = await findProjectRoot(process.cwd());
@@ -7383,11 +7894,11 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
7383
7894
  });
7384
7895
 
7385
7896
  // ../../packages/sandbox-daytona/dist/cli.js
7386
- import { log as log16, spinner as spinner6 } from "@clack/prompts";
7897
+ import { log as log17, spinner as spinner6 } from "@clack/prompts";
7387
7898
  import { Command as Command11 } from "commander";
7388
7899
  function reportError(err) {
7389
7900
  const message = err instanceof Error ? err.message : String(err);
7390
- log16.error(message);
7901
+ log17.error(message);
7391
7902
  process.exitCode = 1;
7392
7903
  }
7393
7904
  var loginSub = new Command11("login").description("Set up (or rotate) Daytona credentials for cloud boxes").option("--status", "show what is currently configured (masked) and exit").action(async (opts) => {
@@ -7446,7 +7957,7 @@ var resyncSub = new Command11("resync").description(
7446
7957
  const sb = spinner6();
7447
7958
  sb.start(`provisioning throwaway sandbox to refresh: ${agents.join(", ")}`);
7448
7959
  const ensured = await ensureAgentVolumesForCloud(daytonaBackend, {
7449
- onLog: (line) => log16.info(line)
7960
+ onLog: (line) => log17.info(line)
7450
7961
  });
7451
7962
  if (ensured.agents.length === 0) {
7452
7963
  sb.stop("no agent volumes available \u2014 the daytona backend has no volume primitive");
@@ -7486,7 +7997,7 @@ var resyncSub = new Command11("resync").description(
7486
7997
  }
7487
7998
  sb3.stop("throwaway sandbox destroyed");
7488
7999
  }
7489
- log16.success(
8000
+ log17.success(
7490
8001
  `Daytona agent volumes refreshed: ${agents.join(", ")}. Next \`agentbox create --provider daytona\` will use the updated credentials.`
7491
8002
  );
7492
8003
  } catch (err) {
@@ -7506,11 +8017,11 @@ var dockerCommand = new Command12("docker").description(
7506
8017
  });
7507
8018
 
7508
8019
  // ../../packages/sandbox-hetzner/dist/cli.js
7509
- import { log as log17 } from "@clack/prompts";
8020
+ import { log as log18 } from "@clack/prompts";
7510
8021
  import { Command as Command13 } from "commander";
7511
8022
  function reportError2(err) {
7512
8023
  const message = err instanceof Error ? err.message : String(err);
7513
- log17.error(message);
8024
+ log18.error(message);
7514
8025
  process.exitCode = 1;
7515
8026
  }
7516
8027
  var loginSub2 = new Command13("login").description("Set up (or rotate) Hetzner Cloud credentials for VPS boxes").option("--status", "show what is currently configured (masked) and exit").action(async (opts) => {
@@ -7654,11 +8165,11 @@ var hetznerCommand = new Command13("hetzner").description(
7654
8165
  ).addCommand(loginSub2, { isDefault: true }).addCommand(firewallSub);
7655
8166
 
7656
8167
  // ../../packages/sandbox-vercel/dist/cli.js
7657
- import { log as log18 } from "@clack/prompts";
8168
+ import { log as log19 } from "@clack/prompts";
7658
8169
  import { Command as Command14 } from "commander";
7659
8170
  function reportError3(err) {
7660
8171
  const message = err instanceof Error ? err.message : String(err);
7661
- log18.error(message);
8172
+ log19.error(message);
7662
8173
  process.exitCode = 1;
7663
8174
  }
7664
8175
  function relativeExpiry(expiresAt) {
@@ -7729,7 +8240,7 @@ var vercelCommand = new Command14("vercel").description(
7729
8240
  ).addCommand(loginSub3, { isDefault: true });
7730
8241
 
7731
8242
  // src/commands/destroy.ts
7732
- import { confirm as confirm7, isCancel as isCancel8, log as log19 } from "@clack/prompts";
8243
+ import { confirm as confirm7, isCancel as isCancel8, log as log20 } from "@clack/prompts";
7733
8244
  import { Command as Command15 } from "commander";
7734
8245
  var destroyCommand = new Command15("destroy").alias("rm").description("Destroy a box and discard its container writable layer (where /workspace lived)").argument(
7735
8246
  "[box]",
@@ -7738,7 +8249,7 @@ var destroyCommand = new Command15("destroy").alias("rm").description("Destroy a
7738
8249
  try {
7739
8250
  const box = await resolveBoxOrExit(idOrName);
7740
8251
  if (!opts.yes) {
7741
- log19.warn("Will also wipe the box volume and agent work-in-progress");
8252
+ log20.warn("Will also wipe the box volume and agent work-in-progress");
7742
8253
  const rootBranch = box.gitWorktrees?.find((w) => w.kind === "root")?.branch;
7743
8254
  const lines = [box.name];
7744
8255
  if (rootBranch) lines.push(`branch: ${rootBranch}`);
@@ -7746,13 +8257,13 @@ var destroyCommand = new Command15("destroy").alias("rm").description("Destroy a
7746
8257
  if (box.snapshotDir) {
7747
8258
  lines.push(`snapshot: ${box.snapshotDir}${opts.keepSnapshot ? " (will be kept)" : ""}`);
7748
8259
  }
7749
- log19.info(lines.join("\n"));
8260
+ log20.info(lines.join("\n"));
7750
8261
  const ok = await confirm7({
7751
8262
  message: "Destroy this box?",
7752
8263
  initialValue: false
7753
8264
  });
7754
8265
  if (isCancel8(ok) || !ok) {
7755
- log19.info("cancelled");
8266
+ log20.info("cancelled");
7756
8267
  return;
7757
8268
  }
7758
8269
  }
@@ -7785,11 +8296,11 @@ var destroyCommand = new Command15("destroy").alias("rm").description("Destroy a
7785
8296
  });
7786
8297
 
7787
8298
  // src/commands/download.ts
7788
- import { confirm as confirm13, isCancel as isCancel14, log as log25 } from "@clack/prompts";
8299
+ import { confirm as confirm13, isCancel as isCancel14, log as log26 } from "@clack/prompts";
7789
8300
  import { Command as Command21 } from "commander";
7790
8301
 
7791
8302
  // src/commands/download-claude.ts
7792
- import { confirm as confirm8, isCancel as isCancel9, log as log20 } from "@clack/prompts";
8303
+ import { confirm as confirm8, isCancel as isCancel9, log as log21 } from "@clack/prompts";
7793
8304
  import { Command as Command16 } from "commander";
7794
8305
  function tag(item) {
7795
8306
  const noun = item.category === "plugins" ? "plugin" : item.category.replace(/s$/, "");
@@ -7805,7 +8316,7 @@ var downloadClaudeCommand = new Command16("claude").description(
7805
8316
  const box = await resolveBoxOrExit(idOrName);
7806
8317
  const volume = box.claudeConfigVolume ?? resolveClaudeVolume({ isolate: false, boxId: box.id }).volume;
7807
8318
  if (volume === SHARED_CLAUDE_VOLUME) {
7808
- log20.warn(
8319
+ log21.warn(
7809
8320
  `Reading the shared ${SHARED_CLAUDE_VOLUME} volume \u2014 it aggregates Claude extensions installed in ANY box, not just ${box.name}.`
7810
8321
  );
7811
8322
  }
@@ -7835,7 +8346,7 @@ var downloadClaudeCommand = new Command16("claude").description(
7835
8346
  initialValue: false
7836
8347
  });
7837
8348
  if (isCancel9(ok) || !ok) {
7838
- log20.info("cancelled");
8349
+ log21.info("cancelled");
7839
8350
  return;
7840
8351
  }
7841
8352
  }
@@ -7850,7 +8361,7 @@ var downloadClaudeCommand = new Command16("claude").description(
7850
8361
  });
7851
8362
 
7852
8363
  // src/commands/download-codex.ts
7853
- import { confirm as confirm9, isCancel as isCancel10, log as log21 } from "@clack/prompts";
8364
+ import { confirm as confirm9, isCancel as isCancel10, log as log22 } from "@clack/prompts";
7854
8365
  import { Command as Command17 } from "commander";
7855
8366
  var downloadCodexCommand = new Command17("codex").description(
7856
8367
  "Download box-side Codex config/auth (config.toml, auth.json, prompts) back to host ~/.codex (additive)"
@@ -7862,7 +8373,7 @@ var downloadCodexCommand = new Command17("codex").description(
7862
8373
  const box = await resolveBoxOrExit(idOrName);
7863
8374
  const volume = box.codexConfigVolume ?? resolveCodexVolume({ isolate: false, boxId: box.id }).volume;
7864
8375
  if (volume === SHARED_CODEX_VOLUME) {
7865
- log21.warn(
8376
+ log22.warn(
7866
8377
  `Reading the shared ${SHARED_CODEX_VOLUME} volume \u2014 it aggregates Codex config from ANY box, not just ${box.name}.`
7867
8378
  );
7868
8379
  }
@@ -7888,7 +8399,7 @@ var downloadCodexCommand = new Command17("codex").description(
7888
8399
  initialValue: false
7889
8400
  });
7890
8401
  if (isCancel10(ok) || !ok) {
7891
- log21.info("cancelled");
8402
+ log22.info("cancelled");
7892
8403
  return;
7893
8404
  }
7894
8405
  }
@@ -7901,7 +8412,7 @@ var downloadCodexCommand = new Command17("codex").description(
7901
8412
  });
7902
8413
 
7903
8414
  // src/commands/download-opencode.ts
7904
- import { confirm as confirm10, isCancel as isCancel11, log as log22 } from "@clack/prompts";
8415
+ import { confirm as confirm10, isCancel as isCancel11, log as log23 } from "@clack/prompts";
7905
8416
  import { Command as Command18 } from "commander";
7906
8417
  var downloadOpencodeCommand = new Command18("opencode").description(
7907
8418
  "Download box-side OpenCode config/auth (auth.json, opencode.json, agents, commands, themes) back to host ~/.config + ~/.local/share opencode (additive)"
@@ -7913,7 +8424,7 @@ var downloadOpencodeCommand = new Command18("opencode").description(
7913
8424
  const box = await resolveBoxOrExit(idOrName);
7914
8425
  const volume = box.opencodeConfigVolume ?? resolveOpencodeVolume({ isolate: false, boxId: box.id }).volume;
7915
8426
  if (volume === SHARED_OPENCODE_VOLUME) {
7916
- log22.warn(
8427
+ log23.warn(
7917
8428
  `Reading the shared ${SHARED_OPENCODE_VOLUME} volume \u2014 it aggregates OpenCode config from ANY box, not just ${box.name}.`
7918
8429
  );
7919
8430
  }
@@ -7939,7 +8450,7 @@ var downloadOpencodeCommand = new Command18("opencode").description(
7939
8450
  initialValue: false
7940
8451
  });
7941
8452
  if (isCancel11(ok) || !ok) {
7942
- log22.info("cancelled");
8453
+ log23.info("cancelled");
7943
8454
  return;
7944
8455
  }
7945
8456
  }
@@ -7952,7 +8463,7 @@ var downloadOpencodeCommand = new Command18("opencode").description(
7952
8463
  });
7953
8464
 
7954
8465
  // src/commands/download-config.ts
7955
- import { confirm as confirm11, isCancel as isCancel12, log as log23 } from "@clack/prompts";
8466
+ import { confirm as confirm11, isCancel as isCancel12, log as log24 } from "@clack/prompts";
7956
8467
  import { Command as Command19 } from "commander";
7957
8468
  function tagChange(line) {
7958
8469
  const sp = line.indexOf(" ");
@@ -7970,15 +8481,15 @@ var downloadConfigCommand = new Command19("config").description("Download agentb
7970
8481
  const box = await resolveBoxOrExit(idOrName);
7971
8482
  const insp = await inspectBox(box.id);
7972
8483
  if (insp.state === "paused") {
7973
- log23.info("box is paused; unpausing");
8484
+ log24.info("box is paused; unpausing");
7974
8485
  await unpauseBox(box.id);
7975
8486
  } else if (insp.state === "stopped") {
7976
- log23.info("box is stopped; starting");
8487
+ log24.info("box is stopped; starting");
7977
8488
  await startBox(box.id);
7978
8489
  } else if (insp.state === "missing") {
7979
8490
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
7980
8491
  }
7981
- log23.info(`agentbox.yaml bypasses gitignore and copies directly into ${box.workspacePath}`);
8492
+ log24.info(`agentbox.yaml bypasses gitignore and copies directly into ${box.workspacePath}`);
7982
8493
  const preview = await pullToHost(box, {
7983
8494
  dryRun: true,
7984
8495
  respectGitignore: false,
@@ -8006,7 +8517,7 @@ var downloadConfigCommand = new Command19("config").description("Download agentb
8006
8517
  initialValue: false
8007
8518
  });
8008
8519
  if (isCancel12(ok) || !ok) {
8009
- log23.info("cancelled");
8520
+ log24.info("cancelled");
8010
8521
  return;
8011
8522
  }
8012
8523
  }
@@ -8028,7 +8539,7 @@ var downloadConfigCommand = new Command19("config").description("Download agentb
8028
8539
  });
8029
8540
 
8030
8541
  // src/commands/download-env.ts
8031
- import { confirm as confirm12, isCancel as isCancel13, log as log24 } from "@clack/prompts";
8542
+ import { confirm as confirm12, isCancel as isCancel13, log as log25 } from "@clack/prompts";
8032
8543
  import { Command as Command20 } from "commander";
8033
8544
  function tagChange2(line) {
8034
8545
  const sp = line.indexOf(" ");
@@ -8052,15 +8563,15 @@ var downloadEnvCommand = new Command20("env").description(
8052
8563
  const box = await resolveBoxOrExit(idOrName);
8053
8564
  const insp = await inspectBox(box.id);
8054
8565
  if (insp.state === "paused") {
8055
- log24.info("box is paused; unpausing");
8566
+ log25.info("box is paused; unpausing");
8056
8567
  await unpauseBox(box.id);
8057
8568
  } else if (insp.state === "stopped") {
8058
- log24.info("box is stopped; starting");
8569
+ log25.info("box is stopped; starting");
8059
8570
  await startBox(box.id);
8060
8571
  } else if (insp.state === "missing") {
8061
8572
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
8062
8573
  }
8063
- log24.info(
8574
+ log25.info(
8064
8575
  `env/config files bypass gitignore and copy directly into ${box.workspacePath}`
8065
8576
  );
8066
8577
  const patterns = [...DEFAULT_ENV_PATTERNS, ...opts.pattern];
@@ -8091,7 +8602,7 @@ var downloadEnvCommand = new Command20("env").description(
8091
8602
  initialValue: false
8092
8603
  });
8093
8604
  if (isCancel13(ok) || !ok) {
8094
- log24.info("cancelled");
8605
+ log25.info("cancelled");
8095
8606
  return;
8096
8607
  }
8097
8608
  }
@@ -8139,7 +8650,7 @@ var downloadCommand = new Command21("download").enablePositionalOptions().descri
8139
8650
  throw new Error("cloud download does not yet support --dry-run; omit to bulk-pull /workspace.");
8140
8651
  }
8141
8652
  if (!opts.respectGitignore || opts.includeNodeModules || opts.withEnv || opts.pattern.length > 0) {
8142
- log25.warn(
8653
+ log26.warn(
8143
8654
  "cloud download ignores gitignore/--with-env/--pattern filters in v1 \u2014 pulling the whole /workspace tree (Phase 6 polish)."
8144
8655
  );
8145
8656
  }
@@ -8149,7 +8660,7 @@ var downloadCommand = new Command21("download").enablePositionalOptions().descri
8149
8660
  initialValue: false
8150
8661
  });
8151
8662
  if (isCancel14(ok) || !ok) {
8152
- log25.info("cancelled");
8663
+ log26.info("cancelled");
8153
8664
  return;
8154
8665
  }
8155
8666
  }
@@ -8168,17 +8679,17 @@ var downloadCommand = new Command21("download").enablePositionalOptions().descri
8168
8679
  }
8169
8680
  const insp = await inspectBox(box.id);
8170
8681
  if (insp.state === "paused") {
8171
- log25.info("box is paused; unpausing");
8682
+ log26.info("box is paused; unpausing");
8172
8683
  await unpauseBox(box.id);
8173
8684
  } else if (insp.state === "stopped") {
8174
- log25.info("box is stopped; starting");
8685
+ log26.info("box is stopped; starting");
8175
8686
  await startBox(box.id);
8176
8687
  } else if (insp.state === "missing") {
8177
8688
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
8178
8689
  }
8179
8690
  const rootWorktree = box.gitWorktrees?.find((w) => w.kind === "root");
8180
8691
  if (rootWorktree) {
8181
- log25.warn(
8692
+ log26.warn(
8182
8693
  `This box has been committing to branch \`${rootWorktree.branch}\` in a separate worktree.
8183
8694
  For a git-aware merge instead of a file copy, run from your checkout:
8184
8695
  git merge ${rootWorktree.branch}
@@ -8214,7 +8725,7 @@ Continuing with rsync into ${box.workspacePath}`
8214
8725
  initialValue: false
8215
8726
  });
8216
8727
  if (isCancel14(ok) || !ok) {
8217
- log25.info("cancelled");
8728
+ log26.info("cancelled");
8218
8729
  return;
8219
8730
  }
8220
8731
  }
@@ -8242,7 +8753,7 @@ downloadCommand.addCommand(downloadOpencodeCommand);
8242
8753
  downloadCommand.addCommand(downloadConfigCommand);
8243
8754
 
8244
8755
  // src/commands/drive.ts
8245
- import { log as log26 } from "@clack/prompts";
8756
+ import { log as log27 } from "@clack/prompts";
8246
8757
  import { Command as Command22 } from "commander";
8247
8758
 
8248
8759
  // src/lib/drive/keys.ts
@@ -8528,7 +9039,7 @@ var driveWaitCommand = new Command22("wait").description("Block until --text app
8528
9039
  }) + "\n"
8529
9040
  );
8530
9041
  } else {
8531
- log26.error(`text not found within ${String(timeoutMs)}ms: ${opts.text}`);
9042
+ log27.error(`text not found within ${String(timeoutMs)}ms: ${opts.text}`);
8532
9043
  }
8533
9044
  process.exit(1);
8534
9045
  } catch (err) {
@@ -8555,8 +9066,8 @@ driveCommand.addCommand(driveWaitCommand);
8555
9066
  driveCommand.addCommand(driveResizeCommand);
8556
9067
  function handleDriveError(err) {
8557
9068
  if (err instanceof SessionNotFoundError) {
8558
- log26.error(err.message);
8559
- log26.info("start an agent first (e.g. `agentbox claude <box>`) or pass --session.");
9069
+ log27.error(err.message);
9070
+ log27.info("start an agent first (e.g. `agentbox claude <box>`) or pass --session.");
8560
9071
  process.exit(2);
8561
9072
  }
8562
9073
  handleLifecycleError(err);
@@ -8580,11 +9091,11 @@ function sleep2(ms) {
8580
9091
  }
8581
9092
 
8582
9093
  // src/commands/fork.ts
8583
- import { log as log27 } from "@clack/prompts";
9094
+ import { log as log28 } from "@clack/prompts";
8584
9095
  import { Command as Command23 } from "commander";
8585
- import { existsSync as existsSync4, readdirSync, statSync } from "fs";
8586
- import { homedir as homedir11 } from "os";
8587
- import { join as join12 } from "path";
9096
+ import { existsSync as existsSync5, readdirSync, statSync } from "fs";
9097
+ import { homedir as homedir12 } from "os";
9098
+ import { join as join14 } from "path";
8588
9099
  var FORK_AGENTS = ["claude", "codex", "opencode"];
8589
9100
  var AGENT_COMMAND = {
8590
9101
  claude: claudeCommand,
@@ -8604,12 +9115,12 @@ function resolveSessionArgs(agent, opts) {
8604
9115
  }
8605
9116
  if (opts.session) return ["--resume", opts.session];
8606
9117
  if (agent === "codex") return ["--continue"];
8607
- const dir = join12(homedir11(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
8608
- if (!existsSync4(dir)) return ["--continue"];
9118
+ const dir = join14(homedir12(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
9119
+ if (!existsSync5(dir)) return ["--continue"];
8609
9120
  const now = Date.now();
8610
9121
  const recent = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
8611
9122
  try {
8612
- return statSync(join12(dir, f)).mtimeMs;
9123
+ return statSync(join14(dir, f)).mtimeMs;
8613
9124
  } catch {
8614
9125
  return 0;
8615
9126
  }
@@ -8641,27 +9152,44 @@ var forkCommand = new Command23("fork").description(
8641
9152
  ).option(
8642
9153
  "--session <id>",
8643
9154
  "host agent session id to resume (default: the newest session for this cwd; claude refuses if several were used recently). Ignored for --agent opencode."
8644
- ).option("--provider <name>", "sandbox backend: 'docker' (default), 'daytona', or 'hetzner'").option("-n, --name <name>", "box name (default: fork-<HHMMSS>)").option(
9155
+ ).option(
9156
+ "--provider <name>",
9157
+ "sandbox backend: 'docker' (default), 'daytona', or 'hetzner', or 'vercel'"
9158
+ ).option("-n, --name <name>", "box name (default: fork-<HHMMSS>)").option(
8645
9159
  "--attach-in <mode>",
8646
9160
  "where to open the forked session: window | tab | split | background | same (default: tab). Falls back to background outside tmux/iTerm."
8647
9161
  ).option(
8648
- "--carry-yes",
8649
- "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)"
9162
+ "--carry <mode>",
9163
+ "carry: block handling \u2014 'send' (default) copies agentbox.yaml's declared host files into the box; 'skip' disables it. Host\u2192box copy is safe (the host is trusted; the box is the untrusted side).",
9164
+ "send"
9165
+ ).option(
9166
+ "--plan <path>",
9167
+ 'copy a Claude Code plan file (e.g. ~/.claude/plans/<slug>.md) into the box, start claude in plan mode, and seed a "resume the plan" prompt. Claude only.'
8650
9168
  ).action(async (opts) => {
8651
9169
  if ((process.env.AGENTBOX_RELAY_URL ?? "").trim().length > 0) {
8652
- log27.error(
9170
+ log28.error(
8653
9171
  "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."
8654
9172
  );
8655
9173
  process.exit(2);
8656
9174
  }
8657
9175
  const agent = opts.agent?.trim() || "claude";
8658
9176
  if (!FORK_AGENTS.includes(agent)) {
8659
- log27.error(`--agent: expected one of ${FORK_AGENTS.join(", ")}, got "${opts.agent ?? ""}"`);
9177
+ log28.error(`--agent: expected one of ${FORK_AGENTS.join(", ")}, got "${opts.agent ?? ""}"`);
8660
9178
  process.exit(2);
8661
9179
  }
8662
9180
  const attachIn = opts.attachIn ?? "tab";
8663
9181
  if (!FORK_ATTACH_VALUES.includes(attachIn)) {
8664
- log27.error(`--attach-in: expected one of ${FORK_ATTACH_VALUES.join(", ")}, got "${attachIn}"`);
9182
+ log28.error(`--attach-in: expected one of ${FORK_ATTACH_VALUES.join(", ")}, got "${attachIn}"`);
9183
+ process.exit(2);
9184
+ }
9185
+ const plan = opts.plan?.trim();
9186
+ if (plan && agent !== "claude") {
9187
+ log28.error("--plan is only supported with --agent claude (plan mode is Claude-specific).");
9188
+ process.exit(2);
9189
+ }
9190
+ const carryMode = opts.carry ?? "send";
9191
+ if (carryMode !== "send" && carryMode !== "skip") {
9192
+ log28.error(`--carry: expected 'send' or 'skip', got "${opts.carry ?? ""}"`);
8665
9193
  process.exit(2);
8666
9194
  }
8667
9195
  const provider = opts.provider?.trim();
@@ -8669,17 +9197,21 @@ var forkCommand = new Command23("fork").description(
8669
9197
  try {
8670
9198
  sessionArgs = resolveSessionArgs(agent, opts);
8671
9199
  } catch (err) {
8672
- log27.error(err instanceof Error ? err.message : String(err));
9200
+ log28.error(err instanceof Error ? err.message : String(err));
8673
9201
  process.exit(2);
8674
9202
  }
8675
9203
  const subArgv = [
8676
9204
  "-y",
8677
- ...opts.carryYes ? ["--carry-yes"] : ["--carry", "skip"],
9205
+ // Host is trusted, the box is the unsafe side, so host→box carry is safe:
9206
+ // fork sends the declared carry: block by default (--carry-yes auto-approves
9207
+ // the same gate create uses). `--carry skip` opts out.
9208
+ ...carryMode === "skip" ? ["--carry", "skip"] : ["--carry-yes"],
8678
9209
  "-w",
8679
9210
  opts.workspace,
8680
9211
  "-n",
8681
9212
  opts.name ?? defaultForkName(),
8682
9213
  ...provider ? ["--provider", provider] : [],
9214
+ ...plan ? ["--plan", plan] : [],
8683
9215
  ...sessionArgs,
8684
9216
  ...resolveAttachArgs(attachIn)
8685
9217
  ];
@@ -8687,17 +9219,25 @@ var forkCommand = new Command23("fork").description(
8687
9219
  });
8688
9220
 
8689
9221
  // src/commands/install.ts
8690
- import { confirm as confirm14, intro as intro6, isCancel as isCancel15, log as log29, note as note2, outro as outro5, select as select2, spinner as spinner8 } from "@clack/prompts";
9222
+ import { confirm as confirm14, intro as intro6, isCancel as isCancel15, log as log30, note as note2, outro as outro5, select as select2, spinner as spinner8 } from "@clack/prompts";
8691
9223
  import { Command as Command25 } from "commander";
8692
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync, writeFileSync as writeFileSync4 } from "fs";
8693
- import { homedir as homedir14 } from "os";
8694
- import { dirname as dirname2, join as join15, resolve as resolve2 } from "path";
9224
+ import {
9225
+ existsSync as existsSync7,
9226
+ lstatSync,
9227
+ mkdirSync as mkdirSync5,
9228
+ readFileSync,
9229
+ rmSync,
9230
+ symlinkSync as symlinkSync2,
9231
+ writeFileSync as writeFileSync4
9232
+ } from "fs";
9233
+ import { homedir as homedir15 } from "os";
9234
+ import { dirname as dirname2, join as join17, resolve as resolve3, sep } from "path";
8695
9235
  import { fileURLToPath } from "url";
8696
9236
 
8697
9237
  // src/lib/doctor-checks.ts
8698
9238
  import { accessSync, constants as fsConstants, mkdirSync as mkdirSync3 } from "fs";
8699
- import { homedir as homedir12 } from "os";
8700
- import { join as join13 } from "path";
9239
+ import { homedir as homedir13 } from "os";
9240
+ import { join as join15 } from "path";
8701
9241
  import { execa as execa2 } from "execa";
8702
9242
  var ALL_PROVIDERS = ["docker", "daytona", "hetzner", "vercel"];
8703
9243
  var NODE_MIN_MAJOR = 20;
@@ -8745,7 +9285,7 @@ function checkPlatform() {
8745
9285
  };
8746
9286
  }
8747
9287
  function checkAgentboxHome() {
8748
- const dir = join13(homedir12(), ".agentbox");
9288
+ const dir = join15(homedir13(), ".agentbox");
8749
9289
  try {
8750
9290
  mkdirSync3(dir, { recursive: true });
8751
9291
  accessSync(dir, fsConstants.W_OK);
@@ -8817,7 +9357,7 @@ async function dockerChecks() {
8817
9357
  ];
8818
9358
  }
8819
9359
  const daemonRes = { label: "docker daemon", status: "ok", detail: "reachable" };
8820
- const mod = await import("./dist-ASLPRUQR.js");
9360
+ const mod = await import("./dist-RZZSSUNB.js");
8821
9361
  let imgRes;
8822
9362
  try {
8823
9363
  const img = await mod.imageInfo(mod.DEFAULT_BOX_IMAGE);
@@ -8848,7 +9388,7 @@ async function dockerChecks() {
8848
9388
  }
8849
9389
  async function daytonaChecks() {
8850
9390
  try {
8851
- const mod = await import("./dist-PTJ6CEQY.js");
9391
+ const mod = await import("./dist-47LVLYUV.js");
8852
9392
  const status = await mod.getDaytonaStatus();
8853
9393
  if (!status.configured) {
8854
9394
  return [
@@ -8884,7 +9424,7 @@ async function daytonaChecks() {
8884
9424
  }
8885
9425
  async function hetznerChecks() {
8886
9426
  try {
8887
- const mod = await import("./dist-WMQDMTWS.js");
9427
+ const mod = await import("./dist-SWUOU34W.js");
8888
9428
  const cred = mod.readHetznerCredStatus();
8889
9429
  const credRes = cred.source === "none" ? {
8890
9430
  label: "credentials",
@@ -8916,7 +9456,7 @@ async function hetznerChecks() {
8916
9456
  }
8917
9457
  async function vercelChecks() {
8918
9458
  try {
8919
- const mod = await import("./dist-RAZP76VX.js");
9459
+ const mod = await import("./dist-24PY2ZMO.js");
8920
9460
  const cred = mod.readVercelCredStatus();
8921
9461
  const credRes = cred.auth === "none" ? {
8922
9462
  label: "credentials",
@@ -9042,15 +9582,15 @@ function formatDetailed(groups) {
9042
9582
  }
9043
9583
 
9044
9584
  // src/lib/first-run.ts
9045
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
9046
- import { homedir as homedir13 } from "os";
9047
- import { dirname, join as join14 } from "path";
9585
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
9586
+ import { homedir as homedir14 } from "os";
9587
+ import { dirname, join as join16 } from "path";
9048
9588
  var MARKER_VERSION = 1;
9049
9589
  function setupMarkerPath() {
9050
- return join14(homedir13(), ".agentbox", "setup-complete.json");
9590
+ return join16(homedir14(), ".agentbox", "setup-complete.json");
9051
9591
  }
9052
9592
  function isFirstRun() {
9053
- return !existsSync5(setupMarkerPath());
9593
+ return !existsSync6(setupMarkerPath());
9054
9594
  }
9055
9595
  function markSetupComplete(provider) {
9056
9596
  const path = setupMarkerPath();
@@ -9064,7 +9604,7 @@ function markSetupComplete(provider) {
9064
9604
  }
9065
9605
 
9066
9606
  // src/commands/prepare.ts
9067
- import { intro as intro5, log as log28, spinner as spinner7 } from "@clack/prompts";
9607
+ import { intro as intro5, log as log29, spinner as spinner7 } from "@clack/prompts";
9068
9608
  import { Command as Command24 } from "commander";
9069
9609
  async function dockerStatus() {
9070
9610
  let img;
@@ -9127,7 +9667,7 @@ async function renderDocker(status) {
9127
9667
  }
9128
9668
  async function daytonaStatus() {
9129
9669
  try {
9130
- const mod = await import("./dist-PTJ6CEQY.js");
9670
+ const mod = await import("./dist-47LVLYUV.js");
9131
9671
  return await mod.getDaytonaStatus();
9132
9672
  } catch (err) {
9133
9673
  return {
@@ -9201,7 +9741,7 @@ async function runPrepare(providerName, opts = {}) {
9201
9741
  }
9202
9742
  const provider = await getProvider(providerName);
9203
9743
  if (typeof provider.prepare !== "function") {
9204
- log28.error(`provider '${providerName}' does not implement prepare`);
9744
+ log29.error(`provider '${providerName}' does not implement prepare`);
9205
9745
  process.exit(1);
9206
9746
  }
9207
9747
  const cwd = opts.cwd ?? process.cwd();
@@ -9219,25 +9759,41 @@ async function runPrepare(providerName, opts = {}) {
9219
9759
  });
9220
9760
  if (result.snapshotName !== void 0) {
9221
9761
  sp.stop(`prepared ${providerName}: snapshot '${result.snapshotName}'`);
9762
+ const configKey = boxImageConfigKey(providerName);
9222
9763
  try {
9223
- const written = await setConfigValue("project", "box.image", result.snapshotName, cwd);
9224
- log28.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
9764
+ const written = await setConfigValue("project", configKey, result.snapshotName, cwd);
9765
+ log29.success(`${configKey} = ${result.snapshotName} (written to ${written.path})`);
9225
9766
  } catch (err) {
9226
9767
  const msg = err instanceof Error ? err.message : String(err);
9227
- log28.warn(
9768
+ log29.warn(
9228
9769
  `prepared snapshot '${result.snapshotName}', but failed to pin it into the project config: ${msg}
9229
- Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.`
9770
+ Run \`agentbox config set --project ${configKey} ${result.snapshotName}\` manually.`
9230
9771
  );
9231
9772
  }
9232
9773
  } else {
9233
9774
  sp.stop(`${providerName.slice(0, 1).toUpperCase() + providerName.slice(1)} provider ready`);
9234
9775
  }
9776
+ try {
9777
+ const cfg = await loadEffectiveConfig(cwd).catch(() => null);
9778
+ const projectImage = cfg?.layers.project.values.box?.image;
9779
+ if (typeof projectImage === "string" && projectImage.length > 0 && projectImage !== DEFAULT_BOX_IMAGE) {
9780
+ const cleared = await unsetConfigValue("project", "box.image", cwd);
9781
+ if (cleared.existed) {
9782
+ log29.warn(
9783
+ `migrated stale \`box.image\` from a previous prepare (was \`${projectImage}\`); re-set manually if you actually meant it: \`agentbox config set --project box.image <ref>\``
9784
+ );
9785
+ }
9786
+ }
9787
+ } catch (err) {
9788
+ const msg = err instanceof Error ? err.message : String(err);
9789
+ log29.warn(`could not migrate stale box.image (continuing): ${msg}`);
9790
+ }
9235
9791
  if (!opts.suppressStatus) {
9236
9792
  process.stdout.write("\n");
9237
9793
  await showStatus({ onlyProvider: providerName });
9238
9794
  }
9239
9795
  if (!opts.suppressTip) {
9240
- log28.info(
9796
+ log29.info(
9241
9797
  "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"
9242
9798
  );
9243
9799
  }
@@ -9369,40 +9925,53 @@ ${LOGO_L2}\x1B[0m` + SYNC_END2);
9369
9925
  }
9370
9926
  var LEGACY_INFO_MARKER = "Drive AgentBox from the host:";
9371
9927
  function installTargets() {
9372
- const home = homedir14();
9373
- const claudeSkills = join15(home, ".claude", "skills");
9928
+ const home = homedir15();
9929
+ const claudeSkills = join17(home, ".claude", "skills");
9374
9930
  return [
9375
- { src: join15("agentbox", "SKILL.md"), dest: join15(claudeSkills, "agentbox", "SKILL.md") },
9931
+ { src: join17("agentbox", "SKILL.md"), dest: join17(claudeSkills, "agentbox", "SKILL.md") },
9376
9932
  {
9377
- src: join15("agentbox-info", "SKILL.md"),
9378
- dest: join15(claudeSkills, "agentbox-info", "SKILL.md")
9933
+ src: join17("agentbox-info", "SKILL.md"),
9934
+ dest: join17(claudeSkills, "agentbox-info", "SKILL.md")
9379
9935
  },
9380
9936
  {
9381
- src: join15("codex", "agentbox.md"),
9382
- dest: join15(home, ".codex", "prompts", "agentbox.md"),
9383
- gateDir: join15(home, ".codex")
9937
+ src: join17("codex", "agentbox.md"),
9938
+ dest: join17(home, ".codex", "prompts", "agentbox.md"),
9939
+ gateDir: join17(home, ".codex")
9384
9940
  },
9385
9941
  {
9386
- src: join15("opencode", "agentbox.md"),
9387
- dest: join15(home, ".config", "opencode", "commands", "agentbox.md"),
9388
- gateDir: join15(home, ".config", "opencode")
9942
+ src: join17("opencode", "agentbox.md"),
9943
+ dest: join17(home, ".config", "opencode", "commands", "agentbox.md"),
9944
+ gateDir: join17(home, ".config", "opencode")
9389
9945
  }
9390
9946
  ];
9391
9947
  }
9392
9948
  function resolveHostSkillsDir() {
9393
9949
  const here = dirname2(fileURLToPath(import.meta.url));
9394
9950
  const candidates = [
9395
- resolve2(here, "..", "share", "host-skills"),
9396
- resolve2(here, "..", "..", "share", "host-skills")
9951
+ resolve3(here, "..", "share", "host-skills"),
9952
+ resolve3(here, "..", "..", "share", "host-skills")
9397
9953
  ];
9398
9954
  for (const c of candidates) {
9399
- if (existsSync6(c)) return c;
9955
+ if (existsSync7(c)) return c;
9400
9956
  }
9401
9957
  throw new Error(`could not locate bundled host skills; tried:
9402
9958
  ${candidates.join("\n ")}`);
9403
9959
  }
9960
+ function isSymlink(target) {
9961
+ try {
9962
+ return lstatSync(target).isSymbolicLink();
9963
+ } catch {
9964
+ return false;
9965
+ }
9966
+ }
9967
+ function isSourceCheckout(srcDir) {
9968
+ return !srcDir.split(sep).includes("node_modules");
9969
+ }
9404
9970
  function writableReason(target, force) {
9405
- if (!existsSync6(target)) return "new";
9971
+ if (!existsSync7(target)) {
9972
+ if (isSymlink(target)) return "managed";
9973
+ return "new";
9974
+ }
9406
9975
  const existing = readFileSync(target, "utf8");
9407
9976
  if (existing.includes(MANAGED_SENTINEL) || existing.includes(LEGACY_INFO_MARKER)) {
9408
9977
  return "managed";
@@ -9414,31 +9983,38 @@ function installHostSkills(opts = {}) {
9414
9983
  const dryRun = opts.dryRun === true;
9415
9984
  const quiet = opts.quiet === true;
9416
9985
  const srcDir = resolveHostSkillsDir();
9986
+ const link = isSourceCheckout(srcDir);
9417
9987
  const written = [];
9418
9988
  const blocked = [];
9419
9989
  let skipped = 0;
9420
9990
  for (const t of installTargets()) {
9421
- const src = join15(srcDir, t.src);
9422
- if (!existsSync6(src)) {
9423
- if (!quiet) log29.warn(`bundled file missing (skipped): ${src}`);
9991
+ const src = join17(srcDir, t.src);
9992
+ if (!existsSync7(src)) {
9993
+ if (!quiet) log30.warn(`bundled file missing (skipped): ${src}`);
9424
9994
  skipped++;
9425
9995
  continue;
9426
9996
  }
9427
- if (t.gateDir && !existsSync6(t.gateDir)) continue;
9997
+ if (t.gateDir && !existsSync7(t.gateDir)) continue;
9428
9998
  const reason = writableReason(t.dest, force);
9429
9999
  if (reason === "skip") {
9430
- if (!quiet) log29.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
10000
+ if (!quiet) log30.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
9431
10001
  blocked.push(t.dest);
9432
10002
  skipped++;
9433
10003
  continue;
9434
10004
  }
9435
10005
  if (dryRun) {
9436
- if (!quiet) log29.info(`would write ${t.dest} (${reason})`);
10006
+ if (!quiet) log30.info(`would ${link ? "link" : "write"} ${t.dest} (${reason})`);
9437
10007
  written.push(t.dest);
9438
10008
  continue;
9439
10009
  }
9440
10010
  mkdirSync5(dirname2(t.dest), { recursive: true });
9441
- writeFileSync4(t.dest, readFileSync(src, "utf8"));
10011
+ if (link) {
10012
+ rmSync(t.dest, { force: true });
10013
+ symlinkSync2(resolve3(srcDir, t.src), t.dest);
10014
+ } else {
10015
+ if (isSymlink(t.dest)) rmSync(t.dest, { force: true });
10016
+ writeFileSync4(t.dest, readFileSync(src, "utf8"));
10017
+ }
9442
10018
  written.push(t.dest);
9443
10019
  }
9444
10020
  return { written, skipped, blocked };
@@ -9465,10 +10041,10 @@ function ensureTty() {
9465
10041
  async function runProviderLogin(name) {
9466
10042
  if (name === "docker") return true;
9467
10043
  if (name === "daytona") {
9468
- const mod2 = await import("./dist-PTJ6CEQY.js");
10044
+ const mod2 = await import("./dist-47LVLYUV.js");
9469
10045
  const status2 = await mod2.getDaytonaStatus();
9470
10046
  if (status2.configured) {
9471
- log29.info("daytona: already configured");
10047
+ log30.info("daytona: already configured");
9472
10048
  const rotate = await confirm14({ message: "Re-authenticate Daytona?", initialValue: false });
9473
10049
  if (isCancel15(rotate)) return false;
9474
10050
  if (rotate) await mod2.ensureDaytonaCredentials({ force: true });
@@ -9478,10 +10054,10 @@ async function runProviderLogin(name) {
9478
10054
  return true;
9479
10055
  }
9480
10056
  if (name === "hetzner") {
9481
- const mod2 = await import("./dist-WMQDMTWS.js");
10057
+ const mod2 = await import("./dist-SWUOU34W.js");
9482
10058
  const status2 = mod2.readHetznerCredStatus();
9483
10059
  if (status2.source !== "none") {
9484
- log29.info("hetzner: already configured");
10060
+ log30.info("hetzner: already configured");
9485
10061
  const rotate = await confirm14({ message: "Re-authenticate Hetzner?", initialValue: false });
9486
10062
  if (isCancel15(rotate)) return false;
9487
10063
  if (rotate) await mod2.ensureHetznerCredentials({ force: true });
@@ -9490,10 +10066,10 @@ async function runProviderLogin(name) {
9490
10066
  await mod2.ensureHetznerCredentials();
9491
10067
  return true;
9492
10068
  }
9493
- const mod = await import("./dist-RAZP76VX.js");
10069
+ const mod = await import("./dist-24PY2ZMO.js");
9494
10070
  const status = mod.readVercelCredStatus();
9495
10071
  if (status.auth !== "none") {
9496
- log29.info(`vercel: already configured (${status.auth})`);
10072
+ log30.info(`vercel: already configured (${status.auth})`);
9497
10073
  const rotate = await confirm14({ message: "Re-authenticate Vercel?", initialValue: false });
9498
10074
  if (isCancel15(rotate)) return false;
9499
10075
  if (rotate) await mod.ensureVercelCredentials({ force: true });
@@ -9524,8 +10100,8 @@ async function runInstallWizard(opts = {}) {
9524
10100
  process.stdout.write(" " + formatCompact([sysGroup]) + "\n");
9525
10101
  const hardFail = sysResults.find((r) => r.status === "fail");
9526
10102
  if (hardFail) {
9527
- log29.error(`system check failed: ${hardFail.label} \u2014 ${hardFail.detail}`);
9528
- log29.info("run `agentbox doctor` for full detail");
10103
+ log30.error(`system check failed: ${hardFail.label} \u2014 ${hardFail.detail}`);
10104
+ log30.info("run `agentbox doctor` for full detail");
9529
10105
  const cont = await confirm14({ message: "Continue anyway?", initialValue: false });
9530
10106
  if (isCancel15(cont) || !cont) {
9531
10107
  outro5("aborted");
@@ -9536,7 +10112,7 @@ async function runInstallWizard(opts = {}) {
9536
10112
  if (opts.provider) {
9537
10113
  const candidate = opts.provider.trim();
9538
10114
  if (!isProviderName(candidate)) {
9539
- log29.error(`unknown --provider: ${candidate}`);
10115
+ log30.error(`unknown --provider: ${candidate}`);
9540
10116
  return false;
9541
10117
  }
9542
10118
  providerName = candidate;
@@ -9578,12 +10154,12 @@ async function runInstallWizard(opts = {}) {
9578
10154
  suppressTip: true
9579
10155
  });
9580
10156
  } catch (err) {
9581
- log29.warn(
10157
+ log30.warn(
9582
10158
  `prepare failed: ${err instanceof Error ? err.message : String(err)} \u2014 you can rerun \`agentbox prepare --provider ${providerName}\` later`
9583
10159
  );
9584
10160
  }
9585
10161
  } else {
9586
- log29.info(
10162
+ log30.info(
9587
10163
  `skipped \u2014 the ${providerName} base will build lazily on first \`agentbox ${providerName === "docker" ? "" : providerName + " "}create\``
9588
10164
  );
9589
10165
  }
@@ -9598,14 +10174,14 @@ async function runInstallWizard(opts = {}) {
9598
10174
  sp.stop(`Agentbox Skills: nothing to write (${String(skillRes.skipped)} skipped)`);
9599
10175
  }
9600
10176
  if (skillRes.blocked.length > 0) {
9601
- log29.warn(
10177
+ log30.warn(
9602
10178
  `user-modified host skill file(s) left in place: ${skillRes.blocked.join(", ")}
9603
10179
  pass \`agentbox install --skills-only --force\` to overwrite`
9604
10180
  );
9605
10181
  }
9606
10182
  } catch (err) {
9607
10183
  sp.stop("Agentbox Skills: failed");
9608
- log29.warn(err instanceof Error ? err.message : String(err));
10184
+ log30.warn(err instanceof Error ? err.message : String(err));
9609
10185
  }
9610
10186
  markSetupComplete(providerName);
9611
10187
  const providerGroup = await runProviderChecks(providerName);
@@ -9631,7 +10207,7 @@ var installCommand = new Command25("install").description(
9631
10207
  try {
9632
10208
  res = installHostSkills({ force: opts.force, dryRun: opts.dryRun });
9633
10209
  } catch (err) {
9634
- log29.error(err instanceof Error ? err.message : String(err));
10210
+ log30.error(err instanceof Error ? err.message : String(err));
9635
10211
  process.exit(1);
9636
10212
  }
9637
10213
  if (opts.dryRun) {
@@ -9798,6 +10374,8 @@ var PR_OP_DESCRIPTIONS = {
9798
10374
  create: "Open a PR for the box's branch (host `gh pr create`, no prompt).",
9799
10375
  view: "Show a PR (read-only).",
9800
10376
  list: "List PRs (read-only).",
10377
+ diff: "Show a PR diff (read-only).",
10378
+ checks: "Show a PR's CI check status (read-only).",
9801
10379
  comment: "Comment on a PR.",
9802
10380
  review: "Review a PR.",
9803
10381
  merge: "Merge a PR (host `gh pr merge`). AGENTBOX_PROMPT=off bypass still requires AGENTBOX_GH_FORCE=1.",
@@ -9836,7 +10414,7 @@ for (const op of GH_PR_OPS) {
9836
10414
  var gitCommand = new Command27("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);
9837
10415
 
9838
10416
  // src/commands/list.ts
9839
- import { log as log30 } from "@clack/prompts";
10417
+ import { log as log31 } from "@clack/prompts";
9840
10418
  import { Command as Command28 } from "commander";
9841
10419
  import { pathToFileURL } from "url";
9842
10420
 
@@ -9866,17 +10444,17 @@ async function applyLiveCloudStates(boxes) {
9866
10444
  );
9867
10445
  }
9868
10446
  function withTimeout(p, ms) {
9869
- return new Promise((resolve3) => {
9870
- const t = setTimeout(() => resolve3(null), ms);
10447
+ return new Promise((resolve4) => {
10448
+ const t = setTimeout(() => resolve4(null), ms);
9871
10449
  if (typeof t.unref === "function") t.unref();
9872
10450
  p.then(
9873
10451
  (v) => {
9874
10452
  clearTimeout(t);
9875
- resolve3(v);
10453
+ resolve4(v);
9876
10454
  },
9877
10455
  () => {
9878
10456
  clearTimeout(t);
9879
- resolve3(null);
10457
+ resolve4(null);
9880
10458
  }
9881
10459
  );
9882
10460
  });
@@ -9948,10 +10526,10 @@ function urlCell(box, stream) {
9948
10526
  const label = "(VNC)";
9949
10527
  parts.push({ text: hyperlink(label, vnc.url, stream), width: label.length });
9950
10528
  }
9951
- const sep = " ";
10529
+ const sep2 = " ";
9952
10530
  return {
9953
- text: parts.map((p) => p.text).join(sep),
9954
- width: parts.reduce((a, p) => a + p.width, 0) + sep.length * (parts.length - 1)
10531
+ text: parts.map((p) => p.text).join(sep2),
10532
+ width: parts.reduce((a, p) => a + p.width, 0) + sep2.length * (parts.length - 1)
9955
10533
  };
9956
10534
  }
9957
10535
  function workspaceCell(path, target, stream) {
@@ -10051,7 +10629,7 @@ var listCommand2 = withWatchOptions(
10051
10629
  )
10052
10630
  ).action(async (opts) => {
10053
10631
  if (opts.json && opts.watch) {
10054
- log30.error("cannot combine --json with --watch");
10632
+ log31.error("cannot combine --json with --watch");
10055
10633
  process.exit(2);
10056
10634
  }
10057
10635
  const all = opts.global ?? false;
@@ -10069,7 +10647,7 @@ var listCommand2 = withWatchOptions(
10069
10647
  });
10070
10648
 
10071
10649
  // src/commands/logs.ts
10072
- import { log as log31 } from "@clack/prompts";
10650
+ import { log as log32 } from "@clack/prompts";
10073
10651
  import { Command as Command29 } from "commander";
10074
10652
  import { spawn as spawn3 } from "child_process";
10075
10653
  var DAEMON_LOG_PATH = "/var/log/agentbox/ctl-daemon.log";
@@ -10091,9 +10669,9 @@ var logsCommand = new Command29("logs").description("Print recent log lines from
10091
10669
  service = boxArg;
10092
10670
  }
10093
10671
  if (!service && !opts.daemon) {
10094
- log31.error("missing <service> argument");
10095
- log31.info("usage: agentbox logs [box] <service> [-n N] [-f]");
10096
- log31.info(" agentbox logs [box] --daemon [-n N] [-f]");
10672
+ log32.error("missing <service> argument");
10673
+ log32.info("usage: agentbox logs [box] <service> [-n N] [-f]");
10674
+ log32.info(" agentbox logs [box] --daemon [-n N] [-f]");
10097
10675
  process.exit(2);
10098
10676
  }
10099
10677
  const box = await resolveBoxOrExit(idOrName);
@@ -10104,7 +10682,7 @@ var logsCommand = new Command29("logs").description("Print recent log lines from
10104
10682
  if (!opts.follow) {
10105
10683
  const proc = await provider.exec(box, args, { user: "vscode" });
10106
10684
  if (proc.exitCode !== 0) {
10107
- log31.error(
10685
+ log32.error(
10108
10686
  `${opts.daemon ? "daemon log" : "agentbox-ctl logs"} failed: ${proc.stderr || proc.stdout}`
10109
10687
  );
10110
10688
  process.exit(1);
@@ -10160,11 +10738,11 @@ var logsCommand = new Command29("logs").description("Print recent log lines from
10160
10738
  });
10161
10739
 
10162
10740
  // src/commands/open.ts
10163
- import { log as log32 } from "@clack/prompts";
10741
+ import { log as log33 } from "@clack/prompts";
10164
10742
  import { execa as execa3 } from "execa";
10165
- import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
10166
- import { homedir as homedir15 } from "os";
10167
- import { join as join16 } from "path";
10743
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
10744
+ import { homedir as homedir16 } from "os";
10745
+ import { join as join18 } from "path";
10168
10746
  import { Command as Command30 } from "commander";
10169
10747
 
10170
10748
  // src/commands/path.ts
@@ -10226,7 +10804,7 @@ var openCommand = new Command30("open").description("Open a box's /workspace in
10226
10804
  }
10227
10805
  });
10228
10806
  async function runCloudOpen(box, provider, opts) {
10229
- const mountRoot = join16(homedir15(), ".agentbox", "mounts", box.name);
10807
+ const mountRoot = join18(homedir16(), ".agentbox", "mounts", box.name);
10230
10808
  if (opts.unmount) {
10231
10809
  const ok = await tryUnmount(mountRoot);
10232
10810
  if (ok) process.stdout.write(`unmounted ${mountRoot}
@@ -10263,13 +10841,13 @@ async function runCloudOpen(box, provider, opts) {
10263
10841
  user: target.user,
10264
10842
  identityFile: target.identityFile
10265
10843
  });
10266
- if (!existsSync7(mountRoot)) {
10844
+ if (!existsSync8(mountRoot)) {
10267
10845
  mkdirSync6(mountRoot, { recursive: true, mode: 493 });
10268
10846
  } else if (await isMounted(mountRoot)) {
10269
- log32.info(`re-mounting (stale mount detected at ${mountRoot})`);
10847
+ log33.info(`re-mounting (stale mount detected at ${mountRoot})`);
10270
10848
  await tryUnmount(mountRoot);
10271
10849
  }
10272
- log32.info(`mounting ${alias}:/workspace at ${mountRoot}`);
10850
+ log33.info(`mounting ${alias}:/workspace at ${mountRoot}`);
10273
10851
  const mount = await execa3(
10274
10852
  sshfsBin,
10275
10853
  [
@@ -10340,7 +10918,7 @@ var pauseCommand = new Command31("pause").description(
10340
10918
  });
10341
10919
 
10342
10920
  // src/commands/prune.ts
10343
- import { confirm as confirm15, isCancel as isCancel16, log as log33 } from "@clack/prompts";
10921
+ import { confirm as confirm15, isCancel as isCancel16, log as log34 } from "@clack/prompts";
10344
10922
  import { Command as Command32 } from "commander";
10345
10923
  function totalRemovals(r, projectConfigs) {
10346
10924
  return r.removedRecords.length + r.removedContainers.length + r.removedVolumes.length + r.removedSnapshotDirs.length + r.removedBoxDirs.length + projectConfigs.length;
@@ -10400,7 +10978,7 @@ var pruneCommand = new Command32("prune").description("Clean up orphan state.jso
10400
10978
  return;
10401
10979
  }
10402
10980
  if (opts.provider !== void 0 && opts.provider !== "docker") {
10403
- log33.error(`unknown provider '${opts.provider}'; expected docker, daytona, hetzner, or vercel`);
10981
+ log34.error(`unknown provider '${opts.provider}'; expected docker, daytona, hetzner, or vercel`);
10404
10982
  process.exit(2);
10405
10983
  }
10406
10984
  const dryRun = opts.dryRun ?? false;
@@ -10413,13 +10991,13 @@ var pruneCommand = new Command32("prune").description("Clean up orphan state.jso
10413
10991
  process.stdout.write("nothing to prune\n");
10414
10992
  return;
10415
10993
  }
10416
- log33.info(`would remove:
10994
+ log34.info(`would remove:
10417
10995
  ${summary(preview, previewProjects)}`);
10418
10996
  if (dryRun) return;
10419
10997
  if (!opts.yes) {
10420
10998
  const ok = await confirm15({ message: "Proceed with prune?", initialValue: true });
10421
10999
  if (isCancel16(ok) || !ok) {
10422
- log33.info("cancelled");
11000
+ log34.info("cancelled");
10423
11001
  return;
10424
11002
  }
10425
11003
  }
@@ -10440,7 +11018,7 @@ async function pruneCloud(provider, opts) {
10440
11018
  const dryRun = opts.dryRun ?? false;
10441
11019
  const backend = await cloudBackendForProvider(provider);
10442
11020
  if (!backend.list) {
10443
- log33.error(`${provider} backend doesn't expose \`list()\`; cannot enumerate sandboxes for prune`);
11021
+ log34.error(`${provider} backend doesn't expose \`list()\`; cannot enumerate sandboxes for prune`);
10444
11022
  process.exit(2);
10445
11023
  }
10446
11024
  const [remote, state] = await Promise.all([backend.list(), readState()]);
@@ -10460,7 +11038,7 @@ async function pruneCloud(provider, opts) {
10460
11038
  `);
10461
11039
  return;
10462
11040
  }
10463
- log33.info(`found ${String(orphans.length)} ${provider} sandbox(es) not in this CLI's state:`);
11041
+ log34.info(`found ${String(orphans.length)} ${provider} sandbox(es) not in this CLI's state:`);
10464
11042
  for (const sb of orphans) {
10465
11043
  const parts = [sb.sandboxId];
10466
11044
  if (sb.name) parts.push(sb.name);
@@ -10476,7 +11054,7 @@ async function pruneCloud(provider, opts) {
10476
11054
  initialValue: false
10477
11055
  });
10478
11056
  if (isCancel16(ok) || !ok) {
10479
- log33.info("cancelled");
11057
+ log34.info("cancelled");
10480
11058
  return;
10481
11059
  }
10482
11060
  }
@@ -10488,7 +11066,7 @@ async function pruneCloud(provider, opts) {
10488
11066
  deleted++;
10489
11067
  } catch (err) {
10490
11068
  failed++;
10491
- log33.warn(
11069
+ log34.warn(
10492
11070
  `delete ${sb.sandboxId} failed: ${err instanceof Error ? err.message : String(err)}`
10493
11071
  );
10494
11072
  }
@@ -10500,8 +11078,8 @@ async function pruneCloud(provider, opts) {
10500
11078
  }
10501
11079
 
10502
11080
  // src/commands/queue.ts
10503
- import { readFile as readFile4, stat as stat5 } from "fs/promises";
10504
- import { intro as intro7, log as log34, outro as outro6 } from "@clack/prompts";
11081
+ import { readFile as readFile5, stat as stat5 } from "fs/promises";
11082
+ import { intro as intro7, log as log35, outro as outro6 } from "@clack/prompts";
10505
11083
  import { Command as Command33 } from "commander";
10506
11084
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
10507
11085
  var queueCommand = new Command33("queue").description("Inspect and manage background `agentbox claude|codex|opencode -i` jobs");
@@ -10510,8 +11088,8 @@ var queueListCommand = new Command33("list").description("List queued, running,
10510
11088
  const cfg = await loadQueueConfig();
10511
11089
  const visible = opts.all === true ? jobs : jobs.filter((j) => !TERMINAL_STATUSES.has(j.status));
10512
11090
  if (visible.length === 0) {
10513
- log34.info(opts.all ? "no queued jobs." : "no active queued jobs (--all to see terminal).");
10514
- log34.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
11091
+ log35.info(opts.all ? "no queued jobs." : "no active queued jobs (--all to see terminal).");
11092
+ log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
10515
11093
  return;
10516
11094
  }
10517
11095
  const rows = visible.map((j) => ({
@@ -10536,19 +11114,19 @@ var queueListCommand = new Command33("list").description("List queued, running,
10536
11114
  headers.map((h, i) => pad4(String(r[h]), widths[i])).join(" ") + "\n"
10537
11115
  );
10538
11116
  }
10539
- log34.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
11117
+ log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
10540
11118
  });
10541
11119
  var queueShowCommand = new Command33("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) => {
10542
11120
  const job = await readJob(id);
10543
11121
  if (!job) {
10544
- log34.error(`no job with id ${id}`);
11122
+ log35.error(`no job with id ${id}`);
10545
11123
  process.exit(1);
10546
11124
  }
10547
11125
  process.stdout.write(JSON.stringify(job, null, 2) + "\n");
10548
11126
  const tailN = Number.parseInt(opts.tail, 10) || 50;
10549
11127
  try {
10550
11128
  await stat5(job.logPath);
10551
- const text = await readFile4(job.logPath, "utf8");
11129
+ const text = await readFile5(job.logPath, "utf8");
10552
11130
  const lines = text.split(/\r?\n/);
10553
11131
  const slice = lines.slice(Math.max(0, lines.length - tailN - 1));
10554
11132
  process.stdout.write(`
@@ -10557,18 +11135,18 @@ var queueShowCommand = new Command33("show").description("Dump a job manifest an
10557
11135
  process.stdout.write(slice.join("\n"));
10558
11136
  if (!slice.join("\n").endsWith("\n")) process.stdout.write("\n");
10559
11137
  } catch {
10560
- log34.info(`(no log at ${job.logPath} yet)`);
11138
+ log35.info(`(no log at ${job.logPath} yet)`);
10561
11139
  }
10562
11140
  });
10563
11141
  var queueCancelCommand = new Command33("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) => {
10564
11142
  intro7(`Cancelling queue job ${id}...`);
10565
11143
  const job = await readJob(id);
10566
11144
  if (!job) {
10567
- log34.error(`no job with id ${id}`);
11145
+ log35.error(`no job with id ${id}`);
10568
11146
  process.exit(1);
10569
11147
  }
10570
11148
  if (job.status !== "queued") {
10571
- log34.error(
11149
+ log35.error(
10572
11150
  `job ${id} is ${job.status}; cancel only flips 'queued' \u2192 'cancelled'.` + (job.status === "running" ? ` Use 'agentbox destroy ${job.boxName || id}' to stop the box.` : "")
10573
11151
  );
10574
11152
  process.exit(1);
@@ -10588,7 +11166,7 @@ var queueClearCommand = new Command33("clear").description("Sweep terminal-state
10588
11166
  if (opts.all === true || opts.failed === true) targets.add("failed");
10589
11167
  if (opts.all === true || opts.cancelled === true) targets.add("cancelled");
10590
11168
  if (targets.size === 0) {
10591
- log34.error("pick at least one of: --done, --failed, --cancelled, --all");
11169
+ log35.error("pick at least one of: --done, --failed, --cancelled, --all");
10592
11170
  process.exit(2);
10593
11171
  }
10594
11172
  const jobs = await loadQueue();
@@ -10598,7 +11176,7 @@ var queueClearCommand = new Command33("clear").description("Sweep terminal-state
10598
11176
  await deleteJob(j.id);
10599
11177
  removed += 1;
10600
11178
  }
10601
- log34.success(`removed ${String(removed)} manifest${removed === 1 ? "" : "s"}`);
11179
+ log35.success(`removed ${String(removed)} manifest${removed === 1 ? "" : "s"}`);
10602
11180
  });
10603
11181
  var QUEUE_WAIT_EVENTS = [
10604
11182
  "new-box",
@@ -10615,7 +11193,7 @@ var queueWaitForCommand = new Command33("wait-for").description(
10615
11193
  `Block until a queue / box event fires. <event> one of: ${QUEUE_WAIT_EVENTS.join(" | ")}.`
10616
11194
  ).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) => {
10617
11195
  if (!QUEUE_WAIT_EVENTS.includes(eventRaw)) {
10618
- log34.error(`unknown event '${eventRaw}' (one of: ${QUEUE_WAIT_EVENTS.join(", ")})`);
11196
+ log35.error(`unknown event '${eventRaw}' (one of: ${QUEUE_WAIT_EVENTS.join(", ")})`);
10619
11197
  process.exit(2);
10620
11198
  }
10621
11199
  const event = eventRaw;
@@ -10635,11 +11213,11 @@ var queueWaitForCommand = new Command33("wait-for").description(
10635
11213
  if (opts.json === true) {
10636
11214
  process.stdout.write(JSON.stringify({ matched: false, event, elapsedMs }) + "\n");
10637
11215
  } else {
10638
- log34.error(`'${event}' did not occur within ${String(timeoutMs)}ms`);
11216
+ log35.error(`'${event}' did not occur within ${String(timeoutMs)}ms`);
10639
11217
  }
10640
11218
  process.exit(1);
10641
11219
  }
10642
- log34.error(err instanceof Error ? err.message : String(err));
11220
+ log35.error(err instanceof Error ? err.message : String(err));
10643
11221
  process.exit(1);
10644
11222
  }
10645
11223
  });
@@ -10736,7 +11314,7 @@ function truncate(s, max) {
10736
11314
  }
10737
11315
 
10738
11316
  // src/commands/relay.ts
10739
- import { log as log35, spinner as spinner9 } from "@clack/prompts";
11317
+ import { log as log36, spinner as spinner9 } from "@clack/prompts";
10740
11318
  import { Command as Command34 } from "commander";
10741
11319
  async function rehydrateFromState() {
10742
11320
  const state = await readState();
@@ -10830,7 +11408,7 @@ var restartSub = new Command34("restart").description("Stop then start the host
10830
11408
  s2.stop(`relay running on ${ep.hostUrl}`);
10831
11409
  } catch (err) {
10832
11410
  s2.stop("relay start failed");
10833
- log35.warn(err instanceof Error ? err.message : String(err));
11411
+ log36.warn(err instanceof Error ? err.message : String(err));
10834
11412
  throw err;
10835
11413
  }
10836
11414
  } catch (err) {
@@ -10842,23 +11420,23 @@ var relayCommand = new Command34("relay").description("Manage the host relay pro
10842
11420
  // src/commands/_run-queued-job.ts
10843
11421
  import { Command as Command35 } from "commander";
10844
11422
  var runQueuedJobCommand = new Command35("_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) => {
10845
- const log44 = openCommandLog(`queue-${id}`);
10846
- log44.write(`worker pid=${String(process.pid)} starting for job ${id}`);
11423
+ const log45 = openCommandLog(`queue-${id}`);
11424
+ log45.write(`worker pid=${String(process.pid)} starting for job ${id}`);
10847
11425
  let job = null;
10848
11426
  try {
10849
11427
  job = await readJob(id);
10850
11428
  if (!job) {
10851
- log44.write(`FATAL: no manifest at id=${id}`);
10852
- log44.close();
11429
+ log45.write(`FATAL: no manifest at id=${id}`);
11430
+ log45.close();
10853
11431
  process.exit(64);
10854
11432
  }
10855
11433
  const onBoxCreated = (boxId) => {
10856
11434
  if (job) job = { ...job, boxId };
10857
11435
  };
10858
11436
  if ((job.providerName || "docker") === "docker") {
10859
- await runDockerJob(job, log44, onBoxCreated);
11437
+ await runDockerJob(job, log45, onBoxCreated);
10860
11438
  } else {
10861
- await runCloudJob(job, log44, onBoxCreated);
11439
+ await runCloudJob(job, log45, onBoxCreated);
10862
11440
  }
10863
11441
  const done = {
10864
11442
  ...job,
@@ -10867,12 +11445,12 @@ var runQueuedJobCommand = new Command35("_run-queued-job").description("internal
10867
11445
  exitCode: 0
10868
11446
  };
10869
11447
  await writeJob(done);
10870
- log44.write(`done`);
10871
- log44.close();
11448
+ log45.write(`done`);
11449
+ log45.close();
10872
11450
  process.exit(0);
10873
11451
  } catch (err) {
10874
11452
  const msg = err instanceof Error ? err.stack ?? err.message : String(err);
10875
- log44.write(`FAIL: ${msg}`);
11453
+ log45.write(`FAIL: ${msg}`);
10876
11454
  if (job) {
10877
11455
  try {
10878
11456
  const failed = {
@@ -10886,11 +11464,11 @@ var runQueuedJobCommand = new Command35("_run-queued-job").description("internal
10886
11464
  } catch {
10887
11465
  }
10888
11466
  }
10889
- log44.close();
11467
+ log45.close();
10890
11468
  process.exit(1);
10891
11469
  }
10892
11470
  });
10893
- async function runDockerJob(job, log44, onBoxCreated) {
11471
+ async function runDockerJob(job, log45, onBoxCreated) {
10894
11472
  const opts = job.createOpts;
10895
11473
  const cfg = await loadEffectiveConfig(opts.workspace, {
10896
11474
  cliOverrides: buildOverridesFromJob(job)
@@ -10902,7 +11480,7 @@ async function runDockerJob(job, log44, onBoxCreated) {
10902
11480
  const useSnapshot = opts.hostSnapshot === false ? false : opts.hostSnapshot === true ? true : cfg.effective.box.hostSnapshot ?? false;
10903
11481
  const resolved = job.agent === "claude-code" ? await resolveClaudeAuth(process.env) : null;
10904
11482
  const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
10905
- log44.write(`creating box for agent=${job.agent}`);
11483
+ log45.write(`creating box for agent=${job.agent}`);
10906
11484
  const result = await createBox({
10907
11485
  workspacePath: opts.workspace,
10908
11486
  name: opts.name && opts.name.length > 0 ? opts.name : void 0,
@@ -10922,21 +11500,28 @@ async function runDockerJob(job, log44, onBoxCreated) {
10922
11500
  // undefined so the create path skips the live prompt.
10923
11501
  portless: opts.portless,
10924
11502
  portlessStateDir: cfg.effective.portless.stateDir || void 0,
11503
+ resyncOnStart: opts.resync,
10925
11504
  limits: resolveLimits(cfg.effective.box, opts),
11505
+ // carry: entries the submitter resolved + approved on the host; applied here
11506
+ // at box-create time (the worker runs on the host, so it can read the files).
11507
+ carry: opts.carry,
10926
11508
  projectRoot,
10927
- onLog: (line) => log44.write(line)
11509
+ onLog: (line) => log45.write(line)
10928
11510
  });
10929
- log44.write(`box created: ${result.record.container}`);
11511
+ log45.write(`box created: ${result.record.container}`);
10930
11512
  onBoxCreated(result.record.id);
10931
11513
  await writeJob({ ...job, boxId: result.record.id });
10932
- const promptedArgs = buildPromptArgs(job.agent, job.prompt, job.agentArgs);
11514
+ const resyncWarning = result.resync ? buildResyncWarning(result.resync) : null;
11515
+ if (resyncWarning) log45.write(resyncWarning);
11516
+ const prompt = prependResyncWarning(resyncWarning, job.prompt);
11517
+ const promptedArgs = buildPromptArgs(job.agent, prompt, job.agentArgs);
10933
11518
  if (job.agent === "claude-code") {
10934
- log44.write(`checking plugin native deps`);
11519
+ log45.write(`checking plugin native deps`);
10935
11520
  await rebuildPluginNativeDeps(result.record.container, {
10936
11521
  volume: result.record.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME,
10937
- onProgress: (line) => log44.write(line)
11522
+ onProgress: (line) => log45.write(line)
10938
11523
  });
10939
- log44.write(`starting claude session`);
11524
+ log45.write(`starting claude session`);
10940
11525
  await startClaudeSession({
10941
11526
  container: result.record.container,
10942
11527
  claudeArgs: applyClaudeSkipPermissions(promptedArgs, cfg.effective),
@@ -10944,22 +11529,22 @@ async function runDockerJob(job, log44, onBoxCreated) {
10944
11529
  boxName: result.record.name
10945
11530
  });
10946
11531
  } else if (job.agent === "codex") {
10947
- log44.write(`checking codex`);
11532
+ log45.write(`checking codex`);
10948
11533
  await ensureCodexInstalled(result.record.container, {
10949
- onProgress: (line) => log44.write(line)
11534
+ onProgress: (line) => log45.write(line)
10950
11535
  });
10951
- log44.write(`starting codex session`);
11536
+ log45.write(`starting codex session`);
10952
11537
  await startCodexSession({
10953
11538
  container: result.record.container,
10954
11539
  codexArgs: applyCodexSkipPermissions(promptedArgs, cfg.effective),
10955
11540
  sessionName: cfg.effective.codex.sessionName
10956
11541
  });
10957
11542
  } else if (job.agent === "opencode") {
10958
- log44.write(`checking opencode`);
11543
+ log45.write(`checking opencode`);
10959
11544
  await ensureOpencodeInstalled(result.record.container, {
10960
- onProgress: (line) => log44.write(line)
11545
+ onProgress: (line) => log45.write(line)
10961
11546
  });
10962
- log44.write(`starting opencode session`);
11547
+ log45.write(`starting opencode session`);
10963
11548
  await startOpencodeSession({
10964
11549
  container: result.record.container,
10965
11550
  opencodeArgs: promptedArgs,
@@ -10969,7 +11554,7 @@ async function runDockerJob(job, log44, onBoxCreated) {
10969
11554
  throw new Error(`unknown agent kind: ${String(job.agent)}`);
10970
11555
  }
10971
11556
  }
10972
- async function runCloudJob(job, log44, onBoxCreated) {
11557
+ async function runCloudJob(job, log45, onBoxCreated) {
10973
11558
  const opts = job.createOpts;
10974
11559
  const cfg = await loadEffectiveConfig(opts.workspace, {
10975
11560
  cliOverrides: buildOverridesFromJob(job)
@@ -10980,7 +11565,7 @@ async function runCloudJob(job, log44, onBoxCreated) {
10980
11565
  const providerDefault = resolveDefaultCheckpoint(cfg.effective, providerName);
10981
11566
  const checkpointRef = opts.snapshot && opts.snapshot.length > 0 ? opts.snapshot : providerDefault.length > 0 ? providerDefault : void 0;
10982
11567
  const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
10983
- log44.write(`creating cloud box (${providerName}) for agent=${job.agent}`);
11568
+ log45.write(`creating cloud box (${providerName}) for agent=${job.agent}`);
10984
11569
  const result = await provider.create({
10985
11570
  workspacePath: opts.workspace,
10986
11571
  name: opts.name && opts.name.length > 0 ? opts.name : void 0,
@@ -10990,10 +11575,13 @@ async function runCloudJob(job, log44, onBoxCreated) {
10990
11575
  withEnv: cfg.effective.box.withEnv,
10991
11576
  vnc: { enabled: cfg.effective.box.vnc },
10992
11577
  limits: resolveLimits(cfg.effective.box, opts),
11578
+ // carry: entries the submitter resolved + approved on the host; the cloud
11579
+ // worker runs on the host too, so it reads the files and uploads them.
11580
+ carry: opts.carry,
10993
11581
  projectRoot,
10994
- onLog: (line) => log44.write(line)
11582
+ onLog: (line) => log45.write(line)
10995
11583
  });
10996
- log44.write(`box created: ${result.record.id}`);
11584
+ log45.write(`box created: ${result.record.id}`);
10997
11585
  onBoxCreated(result.record.id);
10998
11586
  await writeJob({ ...job, boxId: result.record.id });
10999
11587
  const promptedArgs = buildPromptArgs(job.agent, job.prompt, job.agentArgs);
@@ -11015,7 +11603,7 @@ async function runCloudJob(job, log44, onBoxCreated) {
11015
11603
  } else {
11016
11604
  throw new Error(`unknown agent kind: ${String(job.agent)}`);
11017
11605
  }
11018
- log44.write(`starting detached ${job.agent} session`);
11606
+ log45.write(`starting detached ${job.agent} session`);
11019
11607
  await cloudAgentStartDetached({
11020
11608
  box: result.record,
11021
11609
  binary,
@@ -11052,7 +11640,7 @@ function buildOverridesFromJob(job) {
11052
11640
 
11053
11641
  // src/commands/screen.ts
11054
11642
  import { spawnSync as spawnSync3 } from "child_process";
11055
- import { log as log36 } from "@clack/prompts";
11643
+ import { log as log37 } from "@clack/prompts";
11056
11644
  import { Command as Command36 } from "commander";
11057
11645
  var SIGNED_URL_TTL_MIN = 1;
11058
11646
  var SIGNED_URL_TTL_MAX = 86400;
@@ -11083,10 +11671,10 @@ var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC
11083
11671
  if (provider === "docker") {
11084
11672
  const insp = await inspectBox(box.id);
11085
11673
  if (insp.state === "paused") {
11086
- log36.info("box is paused; unpausing");
11674
+ log37.info("box is paused; unpausing");
11087
11675
  await unpauseBox(box.id);
11088
11676
  } else if (insp.state === "stopped") {
11089
- log36.info("box is stopped; starting");
11677
+ log37.info("box is stopped; starting");
11090
11678
  await startBox(box.id);
11091
11679
  } else if (insp.state === "missing") {
11092
11680
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -11096,13 +11684,13 @@ var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC
11096
11684
  const inBoxUrl = exposePort !== void 0 ? box.portlessUrl ?? `http://localhost:${String(exposePort)}` : "about:blank";
11097
11685
  const br = await ensureBoxBrowser(box.container, void 0, inBoxUrl);
11098
11686
  if (br.up && !br.alreadyRunning) {
11099
- log36.info(
11687
+ log37.info(
11100
11688
  exposePort !== void 0 ? `opened ${inBoxUrl} in the in-box browser (visible in the VNC view)` : "started in-box browser"
11101
11689
  );
11102
11690
  } else if (br.alreadyRunning) {
11103
- log36.info("in-box browser already running; left it untouched");
11691
+ log37.info("in-box browser already running; left it untouched");
11104
11692
  } else {
11105
- log36.warn(`could not start in-box browser: ${br.reason ?? "unknown"}`);
11693
+ log37.warn(`could not start in-box browser: ${br.reason ?? "unknown"}`);
11106
11694
  }
11107
11695
  const engine = await detectEngine();
11108
11696
  const urls = buildVncUrls(box, engine);
@@ -11123,10 +11711,10 @@ var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC
11123
11711
  const p = await providerForBox(box);
11124
11712
  const state = await p.probeState(box);
11125
11713
  if (state === "paused") {
11126
- log36.info("box is paused; resuming");
11714
+ log37.info("box is paused; resuming");
11127
11715
  await p.resume(box);
11128
11716
  } else if (state === "stopped") {
11129
- log36.info("box is stopped; starting");
11717
+ log37.info("box is stopped; starting");
11130
11718
  await p.start(box);
11131
11719
  } else if (state === "missing") {
11132
11720
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -11141,14 +11729,14 @@ var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC
11141
11729
  user: "vscode"
11142
11730
  });
11143
11731
  if (br.exitCode === 0) {
11144
- log36.info(`opened ${webUrl} in the in-box browser (visible in the VNC view)`);
11732
+ log37.info(`opened ${webUrl} in the in-box browser (visible in the VNC view)`);
11145
11733
  } else {
11146
- log36.warn(
11734
+ log37.warn(
11147
11735
  `could not open in-box browser (continuing): ${br.stderr.trim() || br.stdout.trim() || `exit ${String(br.exitCode)}`}`
11148
11736
  );
11149
11737
  }
11150
11738
  } catch (err) {
11151
- log36.warn(
11739
+ log37.warn(
11152
11740
  `in-box browser skipped: ${err instanceof Error ? err.message : String(err)}`
11153
11741
  );
11154
11742
  }
@@ -11174,18 +11762,18 @@ var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC
11174
11762
 
11175
11763
  // src/commands/shell.ts
11176
11764
  import { spawnSync as spawnSync4 } from "child_process";
11177
- import { log as log38 } from "@clack/prompts";
11765
+ import { log as log39 } from "@clack/prompts";
11178
11766
  import { Command as Command37 } from "commander";
11179
11767
 
11180
11768
  // src/commands/_provider-guard.ts
11181
- import { log as log37 } from "@clack/prompts";
11769
+ import { log as log38 } from "@clack/prompts";
11182
11770
  function requireDockerProvider(box, commandName) {
11183
11771
  const provider = box.provider ?? "docker";
11184
11772
  if (provider === "docker") return;
11185
- log37.error(
11773
+ log38.error(
11186
11774
  `\`agentbox ${commandName}\` doesn't yet support cloud boxes (this box's provider is '${provider}').`
11187
11775
  );
11188
- log37.info(
11776
+ log38.info(
11189
11777
  "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."
11190
11778
  );
11191
11779
  process.exit(2);
@@ -11225,10 +11813,10 @@ function fmtAgo2(iso) {
11225
11813
  async function ensureBoxRunning(box) {
11226
11814
  const insp = await inspectBox(box.id);
11227
11815
  if (insp.state === "paused") {
11228
- log38.info("box is paused; unpausing");
11816
+ log39.info("box is paused; unpausing");
11229
11817
  await unpauseBox(box.id);
11230
11818
  } else if (insp.state === "stopped") {
11231
- log38.info("box is stopped; starting");
11819
+ log39.info("box is stopped; starting");
11232
11820
  await startBox(box.id);
11233
11821
  } else if (insp.state === "missing") {
11234
11822
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -11267,7 +11855,7 @@ async function startOrAttachShell(box, cfg) {
11267
11855
  const label = shellLabel(cfg.sessionName);
11268
11856
  const info = await shellSessionInfo(box.container, cfg.sessionName, cfg.user);
11269
11857
  if (info.running) {
11270
- log38.info(`reattaching to shell "${label}" \u2014 Control+a d to detach`);
11858
+ log39.info(`reattaching to shell "${label}" \u2014 Control+a d to detach`);
11271
11859
  } else {
11272
11860
  await startShellSession({
11273
11861
  container: box.container,
@@ -11275,7 +11863,7 @@ async function startOrAttachShell(box, cfg) {
11275
11863
  user: cfg.user,
11276
11864
  login: cfg.login
11277
11865
  });
11278
- log38.info(`shell "${label}" \u2014 Control+a d to detach, leaves it running`);
11866
+ log39.info(`shell "${label}" \u2014 Control+a d to detach, leaves it running`);
11279
11867
  }
11280
11868
  const code = await runWrappedAttach({
11281
11869
  container: box.container,
@@ -11428,11 +12016,11 @@ var shellLsCommand = new Command37("ls").description("List the shell tmux sessio
11428
12016
  requireDockerProvider(box, "shell");
11429
12017
  const insp = await inspectBox(box.id);
11430
12018
  if (insp.state !== "running") {
11431
- log38.info(`box ${box.name} is ${insp.state} \u2014 no live shell sessions`);
12019
+ log39.info(`box ${box.name} is ${insp.state} \u2014 no live shell sessions`);
11432
12020
  return;
11433
12021
  }
11434
12022
  if (insp.shellSessions.length === 0) {
11435
- log38.info(
12023
+ log39.info(
11436
12024
  `no shell sessions in ${box.name} \u2014 start one with: agentbox shell ${reattachRef4(box)}`
11437
12025
  );
11438
12026
  return;
@@ -11452,25 +12040,25 @@ var shellKillCommand = new Command37("kill").description("Kill a shell tmux sess
11452
12040
  requireDockerProvider(box, "shell");
11453
12041
  const insp = await inspectBox(box.id);
11454
12042
  if (insp.state !== "running") {
11455
- log38.info(`box ${box.name} is ${insp.state} \u2014 no shell sessions to kill`);
12043
+ log39.info(`box ${box.name} is ${insp.state} \u2014 no shell sessions to kill`);
11456
12044
  return;
11457
12045
  }
11458
12046
  if (opts.all) {
11459
12047
  if (insp.shellSessions.length === 0) {
11460
- log38.info(`no shell sessions in ${box.name}`);
12048
+ log39.info(`no shell sessions in ${box.name}`);
11461
12049
  return;
11462
12050
  }
11463
12051
  let killed = 0;
11464
12052
  for (const s of insp.shellSessions) {
11465
12053
  if (await killShellSession(box.container, s.sessionName)) killed++;
11466
12054
  }
11467
- log38.success(`killed ${String(killed)} shell session${killed === 1 ? "" : "s"} in ${box.name}`);
12055
+ log39.success(`killed ${String(killed)} shell session${killed === 1 ? "" : "s"} in ${box.name}`);
11468
12056
  return;
11469
12057
  }
11470
12058
  const target = shellSessionName(opts.name);
11471
12059
  const ok = await killShellSession(box.container, target);
11472
- if (ok) log38.success(`killed shell "${shellLabel(target)}" in ${box.name}`);
11473
- else log38.warn(`no shell "${shellLabel(target)}" in ${box.name} (already gone?)`);
12060
+ if (ok) log39.success(`killed shell "${shellLabel(target)}" in ${box.name}`);
12061
+ else log39.warn(`no shell "${shellLabel(target)}" in ${box.name} (already gone?)`);
11474
12062
  } catch (err) {
11475
12063
  handleLifecycleError(err);
11476
12064
  }
@@ -11504,7 +12092,7 @@ var startCommand = new Command38("start").description(
11504
12092
  });
11505
12093
 
11506
12094
  // src/commands/status.ts
11507
- import { log as log40 } from "@clack/prompts";
12095
+ import { log as log41 } from "@clack/prompts";
11508
12096
  import { Command as Command39 } from "commander";
11509
12097
 
11510
12098
  // src/endpoints-render.ts
@@ -11535,7 +12123,7 @@ function renderEndpointLines(endpoints, stream) {
11535
12123
  }
11536
12124
 
11537
12125
  // src/commands/inspect.ts
11538
- import { log as log39 } from "@clack/prompts";
12126
+ import { log as log40 } from "@clack/prompts";
11539
12127
  function fmtLimit(n, unit) {
11540
12128
  return n && n > 0 ? `${String(n)}${unit}` : "unlimited";
11541
12129
  }
@@ -11680,7 +12268,7 @@ function renderCodexActivityCloud(persisted) {
11680
12268
  async function runInspect(box, opts) {
11681
12269
  try {
11682
12270
  if (opts.json && opts.watch) {
11683
- log39.error("cannot combine --json with --watch");
12271
+ log40.error("cannot combine --json with --watch");
11684
12272
  process.exit(2);
11685
12273
  }
11686
12274
  const isCloud = (box.provider ?? "docker") !== "docker";
@@ -11724,7 +12312,7 @@ var statusCommand2 = withWatchOptions(
11724
12312
  ).action(async (idOrName, opts) => {
11725
12313
  try {
11726
12314
  if (opts.json && opts.watch) {
11727
- log40.error("cannot combine --json with --watch");
12315
+ log41.error("cannot combine --json with --watch");
11728
12316
  process.exit(2);
11729
12317
  }
11730
12318
  const box = await resolveBoxOrExit(idOrName);
@@ -12053,7 +12641,7 @@ var unpauseCommand = new Command42("unpause").description(
12053
12641
 
12054
12642
  // src/commands/update.ts
12055
12643
  import { spawn as spawn4 } from "child_process";
12056
- import { confirm as confirm16, intro as intro8, isCancel as isCancel17, log as log41, outro as outro7, spinner as spinner10 } from "@clack/prompts";
12644
+ import { confirm as confirm16, intro as intro8, isCancel as isCancel17, log as log42, outro as outro7, spinner as spinner10 } from "@clack/prompts";
12057
12645
  import { Command as Command43 } from "commander";
12058
12646
 
12059
12647
  // src/exec-method.ts
@@ -12099,8 +12687,8 @@ function runInherit(cmd, args) {
12099
12687
  });
12100
12688
  }
12101
12689
  var updateCommand = new Command43("self-update").description(
12102
- "Update agentbox: self-update via npm/pnpm (unless run via npx), wipe the box image so it rebuilds, and reload the relay"
12103
- ).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) => {
12690
+ "Update agentbox: self-update via npm/pnpm (unless run via npx), refresh the host skills, wipe the box image so it rebuilds, and reload the relay"
12691
+ ).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 skills + image + relay").option("--skip-skills", "skip refreshing the host skill files in ~/.claude, ~/.codex, ~/.config/opencode").action(async (opts) => {
12104
12692
  try {
12105
12693
  const method = detectExecutionMethod({
12106
12694
  userAgent: process.env.npm_config_user_agent,
@@ -12108,10 +12696,12 @@ var updateCommand = new Command43("self-update").description(
12108
12696
  });
12109
12697
  intro8("agentbox self-update");
12110
12698
  const selfStep = opts.skipSelf ? "self-update: skipped (--skip-self)" : describeSelfUpdate(method);
12111
- log41.info(
12699
+ const skillsStep = opts.skipSkills ? "skills: skipped (--skip-skills)" : "skills: refresh agentbox-managed host skill files in ~/.claude (and Codex/OpenCode)";
12700
+ log42.info(
12112
12701
  [
12113
12702
  "plan:",
12114
12703
  ` ${selfStep}`,
12704
+ ` ${skillsStep}`,
12115
12705
  ` image: docker image rm -f ${DEFAULT_BOX_IMAGE} (rebuilds on next create/claude)`,
12116
12706
  " relay: stop, then respawn unless a self-update ran"
12117
12707
  ].join("\n")
@@ -12123,25 +12713,55 @@ var updateCommand = new Command43("self-update").description(
12123
12713
  if (!opts.yes) {
12124
12714
  const ok = await confirm16({ message: "Proceed with update?", initialValue: true });
12125
12715
  if (isCancel17(ok) || !ok) {
12126
- log41.info("cancelled");
12716
+ log42.info("cancelled");
12127
12717
  return;
12128
12718
  }
12129
12719
  }
12130
12720
  let selfUpdated = false;
12131
12721
  if (opts.skipSelf) {
12132
- log41.info("skipping self-update (--skip-self)");
12722
+ log42.info("skipping self-update (--skip-self)");
12133
12723
  } else {
12134
12724
  const cmd = selfUpdateCommand(method);
12135
12725
  if (cmd === null) {
12136
- log41.info(describeSelfUpdate(method));
12726
+ log42.info(describeSelfUpdate(method));
12137
12727
  } else {
12138
- log41.info(`running: ${cmd.cmd} ${cmd.args.join(" ")}`);
12728
+ log42.info(`running: ${cmd.cmd} ${cmd.args.join(" ")}`);
12139
12729
  const code = await runInherit(cmd.cmd, cmd.args);
12140
12730
  if (code !== 0) {
12141
12731
  throw new Error(`${cmd.cmd} exited with code ${String(code)}`);
12142
12732
  }
12143
12733
  selfUpdated = true;
12144
- log41.success(`updated ${PKG} via ${cmd.cmd}`);
12734
+ log42.success(`updated ${PKG} via ${cmd.cmd}`);
12735
+ }
12736
+ }
12737
+ if (opts.skipSkills) {
12738
+ log42.info("skipping skills refresh (--skip-skills)");
12739
+ } else if (selfUpdated) {
12740
+ const code = await runInherit("agentbox", ["install", "--skills-only"]);
12741
+ if (code === 0) {
12742
+ log42.success("refreshed host skills (via updated build)");
12743
+ } else {
12744
+ log42.warn(
12745
+ `host skills not refreshed (agentbox install --skills-only exited ${String(code)}) \u2014 run it manually to pick up the new versions`
12746
+ );
12747
+ }
12748
+ } else {
12749
+ try {
12750
+ const res = installHostSkills({ quiet: true });
12751
+ if (res.written.length > 0) {
12752
+ log42.success(`refreshed host skills (${String(res.written.length)} file(s))`);
12753
+ } else {
12754
+ log42.info(`host skills already current (${String(res.skipped)} skipped)`);
12755
+ }
12756
+ if (res.blocked.length > 0) {
12757
+ log42.warn(
12758
+ `user-modified skill file(s) left in place: ${res.blocked.join(", ")} \u2014 run \`agentbox install --skills-only --force\` to overwrite`
12759
+ );
12760
+ }
12761
+ } catch (err) {
12762
+ log42.warn(
12763
+ `host skills not refreshed (${err instanceof Error ? err.message : String(err)})`
12764
+ );
12145
12765
  }
12146
12766
  }
12147
12767
  const s = spinner10();
@@ -12157,7 +12777,7 @@ var updateCommand = new Command43("self-update").description(
12157
12777
  stop.stopped ? `stopped relay (pid ${String(stop.pid)})` : "relay was not running"
12158
12778
  );
12159
12779
  if (selfUpdated) {
12160
- log41.info(
12780
+ log42.info(
12161
12781
  "relay will restart automatically (with the updated build) on your next `agentbox create` / `agentbox claude`"
12162
12782
  );
12163
12783
  } else {
@@ -12168,7 +12788,7 @@ var updateCommand = new Command43("self-update").description(
12168
12788
  sr2.stop(`relay back up on ${ep.hostUrl}`);
12169
12789
  } catch (err) {
12170
12790
  sr2.stop("relay restart failed");
12171
- log41.warn(
12791
+ log42.warn(
12172
12792
  `${err instanceof Error ? err.message : String(err)} \u2014 it will retry on the next box command`
12173
12793
  );
12174
12794
  }
@@ -12181,7 +12801,7 @@ var updateCommand = new Command43("self-update").description(
12181
12801
 
12182
12802
  // src/commands/url.ts
12183
12803
  import { spawnSync as spawnSync5 } from "child_process";
12184
- import { log as log42 } from "@clack/prompts";
12804
+ import { log as log43 } from "@clack/prompts";
12185
12805
  import { Command as Command44 } from "commander";
12186
12806
  var SIGNED_URL_TTL_MIN2 = 1;
12187
12807
  var SIGNED_URL_TTL_MAX2 = 86400;
@@ -12214,10 +12834,10 @@ var urlCommand = new Command44("url").description(
12214
12834
  if (provider === "docker") {
12215
12835
  const insp = await inspectBox(box.id);
12216
12836
  if (insp.state === "paused") {
12217
- log42.info("box is paused; unpausing");
12837
+ log43.info("box is paused; unpausing");
12218
12838
  await unpauseBox(box.id);
12219
12839
  } else if (insp.state === "stopped") {
12220
- log42.info("box is stopped; starting");
12840
+ log43.info("box is stopped; starting");
12221
12841
  await startBox(box.id);
12222
12842
  } else if (insp.state === "missing") {
12223
12843
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
@@ -12246,10 +12866,10 @@ var urlCommand = new Command44("url").description(
12246
12866
  const p = await providerForBox(box);
12247
12867
  const state = await p.probeState(box);
12248
12868
  if (state === "paused") {
12249
- log42.info("box is paused; resuming");
12869
+ log43.info("box is paused; resuming");
12250
12870
  await p.resume(box);
12251
12871
  } else if (state === "stopped") {
12252
- log42.info("box is stopped; starting");
12872
+ log43.info("box is stopped; starting");
12253
12873
  await p.start(box);
12254
12874
  } else if (state === "missing") {
12255
12875
  throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
@@ -12273,7 +12893,7 @@ var urlCommand = new Command44("url").description(
12273
12893
  });
12274
12894
 
12275
12895
  // src/commands/wait.ts
12276
- import { log as log43 } from "@clack/prompts";
12896
+ import { log as log44 } from "@clack/prompts";
12277
12897
  import { Command as Command45 } from "commander";
12278
12898
  var waitCommand = new Command45("wait").description("Block until the box reports all autostart units ready").argument(
12279
12899
  "[box]",
@@ -12291,7 +12911,7 @@ var waitCommand = new Command45("wait").description("Block until the box reports
12291
12911
  try {
12292
12912
  parsed = JSON.parse(proc.stdout);
12293
12913
  } catch {
12294
- log43.error(`agentbox-ctl wait-ready failed: ${proc.stderr || proc.stdout}`);
12914
+ log44.error(`agentbox-ctl wait-ready failed: ${proc.stderr || proc.stdout}`);
12295
12915
  process.exit(1);
12296
12916
  }
12297
12917
  if (opts.json) {