@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.
- package/CHANGELOG.md +75 -0
- package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-XKO4SHR3.js} +3 -3
- package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
- package/dist/chunk-2LF5YILI.js.map +1 -0
- package/dist/{chunk-MXXXKJYS.js → chunk-DHJ7OMIP.js} +234 -83
- package/dist/chunk-DHJ7OMIP.js.map +1 -0
- package/dist/{chunk-GYJ62GFL.js → chunk-HFV6THYG.js} +6 -6
- package/dist/{chunk-ZJXTIH6C.js → chunk-IZXPJPPV.js} +1347 -852
- package/dist/chunk-IZXPJPPV.js.map +1 -0
- package/dist/{dist-RAZP76VX.js → dist-24PY2ZMO.js} +3 -3
- package/dist/{dist-PTJ6CEQY.js → dist-47LVLYUV.js} +4 -4
- package/dist/{dist-ASLPRUQR.js → dist-RZZSSUNB.js} +28 -2
- package/dist/{dist-WMQDMTWS.js → dist-SWUOU34W.js} +8 -5
- package/dist/dist-SWUOU34W.js.map +1 -0
- package/dist/index.js +1351 -731
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/runtime/docker/packages/ctl/dist/bin.cjs +335 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
- package/runtime/hetzner/ctl.cjs +335 -4
- package/runtime/hetzner/gh-shim +86 -5
- package/runtime/relay/bin.cjs +285 -2
- package/runtime/vercel/ctl.cjs +335 -4
- package/runtime/vercel/gh-shim +86 -5
- package/share/host-skills/agentbox/SKILL.md +16 -5
- package/share/host-skills/agentbox-info/SKILL.md +29 -7
- package/dist/chunk-MXXXKJYS.js.map +0 -1
- package/dist/chunk-ZGVMN54V.js.map +0 -1
- package/dist/chunk-ZJXTIH6C.js.map +0 -1
- package/dist/dist-WMQDMTWS.js.map +0 -1
- /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-XKO4SHR3.js.map} +0 -0
- /package/dist/{chunk-GYJ62GFL.js.map → chunk-HFV6THYG.js.map} +0 -0
- /package/dist/{dist-RAZP76VX.js.map → dist-24PY2ZMO.js.map} +0 -0
- /package/dist/{dist-PTJ6CEQY.js.map → dist-47LVLYUV.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
225
|
-
var AGENTBOX_COMMIT = true ? "
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
1447
|
+
const emit = args.onLog ?? (() => {
|
|
1344
1448
|
});
|
|
1345
|
-
const yamlPath =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
1410
|
-
const stagedFile =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
1608
|
+
import { join as join7 } from "path";
|
|
1484
1609
|
async function resolveCodexTeleport(opts) {
|
|
1485
1610
|
const hostHome = opts.hostHome ?? homedir5();
|
|
1486
|
-
const sessionsRoot =
|
|
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(
|
|
1529
|
-
const stagedFile =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1693
|
-
import { existsSync as
|
|
1694
|
-
import { homedir as
|
|
1695
|
-
import { join as
|
|
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 =
|
|
1699
|
-
if (
|
|
1700
|
-
const marker =
|
|
1701
|
-
if (
|
|
1702
|
-
mkdirSync(
|
|
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
|
-
|
|
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
|
|
1712
|
-
import { join as
|
|
1873
|
+
import { homedir as homedir8 } from "os";
|
|
1874
|
+
import { join as join10 } from "path";
|
|
1713
1875
|
function stateDir() {
|
|
1714
|
-
return process.env.AGENTBOX_HOME ??
|
|
1876
|
+
return process.env.AGENTBOX_HOME ?? join10(homedir8(), ".agentbox");
|
|
1715
1877
|
}
|
|
1716
1878
|
function logsDir() {
|
|
1717
|
-
return
|
|
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 =
|
|
1723
|
-
const 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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
2059
|
+
return (await import("./dist-47LVLYUV.js")).daytonaBackend;
|
|
1898
2060
|
case "hetzner":
|
|
1899
|
-
return (await import("./dist-
|
|
2061
|
+
return (await import("./dist-SWUOU34W.js")).hetznerBackend;
|
|
1900
2062
|
case "vercel":
|
|
1901
|
-
return (await import("./dist-
|
|
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
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
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
|
-
|
|
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 (
|
|
1916
|
-
|
|
1917
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1951
|
-
if (args.
|
|
1952
|
-
|
|
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
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
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
|
|
1983
|
-
if (!args.checkpointRef) return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2368
|
+
log9.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
|
|
2120
2369
|
return;
|
|
2121
2370
|
}
|
|
2122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2396
|
+
log9.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
|
|
2148
2397
|
return;
|
|
2149
2398
|
}
|
|
2150
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2469
|
-
|
|
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
|
-
|
|
2490
|
-
${f.
|
|
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
|
-
|
|
2868
|
+
log9.error(err.message);
|
|
2518
2869
|
if (containerName) {
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2638
|
-
${f.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
3180
|
+
return (await import("./dist-47LVLYUV.js")).daytonaProvider;
|
|
2815
3181
|
case "hetzner":
|
|
2816
|
-
return (await import("./dist-
|
|
3182
|
+
return (await import("./dist-SWUOU34W.js")).hetznerProvider;
|
|
2817
3183
|
case "vercel":
|
|
2818
|
-
return (await import("./dist-
|
|
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
|
-
|
|
3208
|
+
log10.info("box is paused; unpausing");
|
|
2843
3209
|
await unpauseBox(box.id);
|
|
2844
3210
|
} else if (insp.state === "stopped") {
|
|
2845
|
-
|
|
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) =>
|
|
3246
|
+
onLog: (line) => log10.info(line)
|
|
2881
3247
|
});
|
|
2882
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3468
|
+
log10.info(`cleared project ${key} (was ${ref})`);
|
|
3041
3469
|
} else if (effectiveValue === ref) {
|
|
3042
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3488
|
+
log10.info("box is paused; resuming");
|
|
3061
3489
|
await provider.resume(box);
|
|
3062
3490
|
} else if (state === "stopped") {
|
|
3063
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3520
|
+
log10.info(`saved ${saved.join(", ")} login to ~/.agentbox for future boxes`);
|
|
3093
3521
|
}
|
|
3094
3522
|
} catch (err) {
|
|
3095
|
-
|
|
3523
|
+
log10.warn(`agent credential extract skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
3096
3524
|
}
|
|
3097
3525
|
}
|
|
3098
|
-
|
|
3526
|
+
log10.info(`capturing cloud snapshot '${name}' (this may take a few minutes)`);
|
|
3099
3527
|
const result = await provider.checkpoint.create(box, name);
|
|
3100
|
-
|
|
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
|
-
|
|
3532
|
+
log10.info(`set project default checkpoint (${key}) -> ${result.ref}`);
|
|
3105
3533
|
} else {
|
|
3106
|
-
|
|
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
|
|
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
|
|
3124
|
-
import { join as
|
|
3551
|
+
import { homedir as homedir9 } from "os";
|
|
3552
|
+
import { join as join11 } from "path";
|
|
3125
3553
|
function sshConfigPath() {
|
|
3126
|
-
return
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3701
|
+
log11.info(`box is paused; unpausing`);
|
|
3274
3702
|
await unpauseBox(box.id);
|
|
3275
3703
|
} else if (insp.state === "stopped") {
|
|
3276
|
-
|
|
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
|
-
|
|
3715
|
+
log11.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
|
|
3288
3716
|
} else {
|
|
3289
|
-
|
|
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
|
-
|
|
3727
|
+
log11.info(`wrote /workspace/.vscode/tasks.json (${String(services.length)} service(s))`);
|
|
3300
3728
|
} else if (r.status === "skipped-user-owned") {
|
|
3301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3746
|
+
log11.info("box is paused; resuming");
|
|
3319
3747
|
await p.resume(box);
|
|
3320
3748
|
} else if (state === "stopped") {
|
|
3321
|
-
|
|
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
|
-
|
|
3762
|
+
log11.warn(`box not fully ready (${lines.join("; ")}). Opening anyway.`);
|
|
3335
3763
|
} else {
|
|
3336
|
-
|
|
3764
|
+
log11.success("all units ready");
|
|
3337
3765
|
}
|
|
3338
3766
|
} catch (err) {
|
|
3339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
3835
|
+
return new Promise((resolve4) => {
|
|
3408
3836
|
const child = spawn(cmd, args, { stdio: "ignore" });
|
|
3409
|
-
child.once("error", () =>
|
|
3410
|
-
child.once("exit", (code) =>
|
|
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
|
|
3429
|
-
import { join as
|
|
3430
|
-
import { confirm as confirm5, intro as intro2, isCancel as isCancel6, log as
|
|
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
|
-
|
|
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
|
-
|
|
3947
|
+
log12.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
|
|
3519
3948
|
return;
|
|
3520
3949
|
}
|
|
3521
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
3558
|
-
else
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3847
|
-
|
|
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
|
-
|
|
4332
|
+
log12.error(err.message);
|
|
3889
4333
|
if (containerName) {
|
|
3890
|
-
|
|
3891
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4158
|
-
import { join as
|
|
4159
|
-
import { confirm as confirm6, intro as intro3, isCancel as isCancel7, log as
|
|
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
|
-
|
|
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
|
-
|
|
4704
|
+
log13.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
|
|
4248
4705
|
return;
|
|
4249
4706
|
}
|
|
4250
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
4292
|
-
else
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5023
|
+
log13.error(err.message);
|
|
4555
5024
|
if (containerName) {
|
|
4556
|
-
|
|
4557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5589
|
+
log14.info("box is paused; unpausing");
|
|
5112
5590
|
await unpauseBox(box.id);
|
|
5113
5591
|
} else if (insp.state === "stopped") {
|
|
5114
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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)
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
5870
|
+
log15.info(`n: ${String(result.record.projectIndex)} (in ${projectRoot})`);
|
|
5360
5871
|
}
|
|
5361
|
-
|
|
5362
|
-
|
|
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
|
-
|
|
5875
|
+
log15.info(`snapshot: ${result.record.snapshotDir}`);
|
|
5365
5876
|
}
|
|
5366
5877
|
if (result.record.checkpointSource) {
|
|
5367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
5431
|
-
|
|
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
|
|
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((
|
|
6043
|
-
this.resolveDone =
|
|
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
|
-
|
|
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
|
-
|
|
7489
|
+
log16.error(
|
|
6979
7490
|
"agentbox dashboard is unavailable here (native terminal backend failed to load)"
|
|
6980
7491
|
);
|
|
6981
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8484
|
+
log24.info("box is paused; unpausing");
|
|
7974
8485
|
await unpauseBox(box.id);
|
|
7975
8486
|
} else if (insp.state === "stopped") {
|
|
7976
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
8566
|
+
log25.info("box is paused; unpausing");
|
|
8056
8567
|
await unpauseBox(box.id);
|
|
8057
8568
|
} else if (insp.state === "stopped") {
|
|
8058
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8682
|
+
log26.info("box is paused; unpausing");
|
|
8172
8683
|
await unpauseBox(box.id);
|
|
8173
8684
|
} else if (insp.state === "stopped") {
|
|
8174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
8559
|
-
|
|
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
|
|
9094
|
+
import { log as log28 } from "@clack/prompts";
|
|
8584
9095
|
import { Command as Command23 } from "commander";
|
|
8585
|
-
import { existsSync as
|
|
8586
|
-
import { homedir as
|
|
8587
|
-
import { join as
|
|
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 =
|
|
8608
|
-
if (!
|
|
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(
|
|
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(
|
|
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
|
|
8649
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
8693
|
-
|
|
8694
|
-
|
|
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
|
|
8700
|
-
import { join as
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
9046
|
-
import { homedir as
|
|
9047
|
-
import { dirname, join as
|
|
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
|
|
9590
|
+
return join16(homedir14(), ".agentbox", "setup-complete.json");
|
|
9051
9591
|
}
|
|
9052
9592
|
function isFirstRun() {
|
|
9053
|
-
return !
|
|
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
|
|
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-
|
|
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
|
-
|
|
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",
|
|
9224
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
9373
|
-
const claudeSkills =
|
|
9928
|
+
const home = homedir15();
|
|
9929
|
+
const claudeSkills = join17(home, ".claude", "skills");
|
|
9374
9930
|
return [
|
|
9375
|
-
{ src:
|
|
9931
|
+
{ src: join17("agentbox", "SKILL.md"), dest: join17(claudeSkills, "agentbox", "SKILL.md") },
|
|
9376
9932
|
{
|
|
9377
|
-
src:
|
|
9378
|
-
dest:
|
|
9933
|
+
src: join17("agentbox-info", "SKILL.md"),
|
|
9934
|
+
dest: join17(claudeSkills, "agentbox-info", "SKILL.md")
|
|
9379
9935
|
},
|
|
9380
9936
|
{
|
|
9381
|
-
src:
|
|
9382
|
-
dest:
|
|
9383
|
-
gateDir:
|
|
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:
|
|
9387
|
-
dest:
|
|
9388
|
-
gateDir:
|
|
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
|
-
|
|
9396
|
-
|
|
9951
|
+
resolve3(here, "..", "share", "host-skills"),
|
|
9952
|
+
resolve3(here, "..", "..", "share", "host-skills")
|
|
9397
9953
|
];
|
|
9398
9954
|
for (const c of candidates) {
|
|
9399
|
-
if (
|
|
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 (!
|
|
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 =
|
|
9422
|
-
if (!
|
|
9423
|
-
if (!quiet)
|
|
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 && !
|
|
9997
|
+
if (t.gateDir && !existsSync7(t.gateDir)) continue;
|
|
9428
9998
|
const reason = writableReason(t.dest, force);
|
|
9429
9999
|
if (reason === "skip") {
|
|
9430
|
-
if (!quiet)
|
|
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)
|
|
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
|
-
|
|
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-
|
|
10044
|
+
const mod2 = await import("./dist-47LVLYUV.js");
|
|
9469
10045
|
const status2 = await mod2.getDaytonaStatus();
|
|
9470
10046
|
if (status2.configured) {
|
|
9471
|
-
|
|
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-
|
|
10057
|
+
const mod2 = await import("./dist-SWUOU34W.js");
|
|
9482
10058
|
const status2 = mod2.readHetznerCredStatus();
|
|
9483
10059
|
if (status2.source !== "none") {
|
|
9484
|
-
|
|
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-
|
|
10069
|
+
const mod = await import("./dist-24PY2ZMO.js");
|
|
9494
10070
|
const status = mod.readVercelCredStatus();
|
|
9495
10071
|
if (status.auth !== "none") {
|
|
9496
|
-
|
|
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
|
-
|
|
9528
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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((
|
|
9870
|
-
const t = setTimeout(() =>
|
|
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
|
-
|
|
10453
|
+
resolve4(v);
|
|
9876
10454
|
},
|
|
9877
10455
|
() => {
|
|
9878
10456
|
clearTimeout(t);
|
|
9879
|
-
|
|
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
|
|
10529
|
+
const sep2 = " ";
|
|
9952
10530
|
return {
|
|
9953
|
-
text: parts.map((p) => p.text).join(
|
|
9954
|
-
width: parts.reduce((a, p) => a + p.width, 0) +
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
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
|
-
|
|
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
|
|
10741
|
+
import { log as log33 } from "@clack/prompts";
|
|
10164
10742
|
import { execa as execa3 } from "execa";
|
|
10165
|
-
import { existsSync as
|
|
10166
|
-
import { homedir as
|
|
10167
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
10844
|
+
if (!existsSync8(mountRoot)) {
|
|
10267
10845
|
mkdirSync6(mountRoot, { recursive: true, mode: 493 });
|
|
10268
10846
|
} else if (await isMounted(mountRoot)) {
|
|
10269
|
-
|
|
10847
|
+
log33.info(`re-mounting (stale mount detected at ${mountRoot})`);
|
|
10270
10848
|
await tryUnmount(mountRoot);
|
|
10271
10849
|
}
|
|
10272
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10504
|
-
import { intro as intro7, log as
|
|
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
|
-
|
|
10514
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
11145
|
+
log35.error(`no job with id ${id}`);
|
|
10568
11146
|
process.exit(1);
|
|
10569
11147
|
}
|
|
10570
11148
|
if (job.status !== "queued") {
|
|
10571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11216
|
+
log35.error(`'${event}' did not occur within ${String(timeoutMs)}ms`);
|
|
10639
11217
|
}
|
|
10640
11218
|
process.exit(1);
|
|
10641
11219
|
}
|
|
10642
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
10846
|
-
|
|
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
|
-
|
|
10852
|
-
|
|
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,
|
|
11437
|
+
await runDockerJob(job, log45, onBoxCreated);
|
|
10860
11438
|
} else {
|
|
10861
|
-
await runCloudJob(job,
|
|
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
|
-
|
|
10871
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11467
|
+
log45.close();
|
|
10890
11468
|
process.exit(1);
|
|
10891
11469
|
}
|
|
10892
11470
|
});
|
|
10893
|
-
async function runDockerJob(job,
|
|
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
|
-
|
|
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) =>
|
|
11509
|
+
onLog: (line) => log45.write(line)
|
|
10928
11510
|
});
|
|
10929
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
11522
|
+
onProgress: (line) => log45.write(line)
|
|
10938
11523
|
});
|
|
10939
|
-
|
|
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
|
-
|
|
11532
|
+
log45.write(`checking codex`);
|
|
10948
11533
|
await ensureCodexInstalled(result.record.container, {
|
|
10949
|
-
onProgress: (line) =>
|
|
11534
|
+
onProgress: (line) => log45.write(line)
|
|
10950
11535
|
});
|
|
10951
|
-
|
|
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
|
-
|
|
11543
|
+
log45.write(`checking opencode`);
|
|
10959
11544
|
await ensureOpencodeInstalled(result.record.container, {
|
|
10960
|
-
onProgress: (line) =>
|
|
11545
|
+
onProgress: (line) => log45.write(line)
|
|
10961
11546
|
});
|
|
10962
|
-
|
|
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,
|
|
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
|
-
|
|
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) =>
|
|
11582
|
+
onLog: (line) => log45.write(line)
|
|
10995
11583
|
});
|
|
10996
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
11674
|
+
log37.info("box is paused; unpausing");
|
|
11087
11675
|
await unpauseBox(box.id);
|
|
11088
11676
|
} else if (insp.state === "stopped") {
|
|
11089
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11691
|
+
log37.info("in-box browser already running; left it untouched");
|
|
11104
11692
|
} else {
|
|
11105
|
-
|
|
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
|
-
|
|
11714
|
+
log37.info("box is paused; resuming");
|
|
11127
11715
|
await p.resume(box);
|
|
11128
11716
|
} else if (state === "stopped") {
|
|
11129
|
-
|
|
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
|
-
|
|
11732
|
+
log37.info(`opened ${webUrl} in the in-box browser (visible in the VNC view)`);
|
|
11145
11733
|
} else {
|
|
11146
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
11773
|
+
log38.error(
|
|
11186
11774
|
`\`agentbox ${commandName}\` doesn't yet support cloud boxes (this box's provider is '${provider}').`
|
|
11187
11775
|
);
|
|
11188
|
-
|
|
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
|
-
|
|
11816
|
+
log39.info("box is paused; unpausing");
|
|
11229
11817
|
await unpauseBox(box.id);
|
|
11230
11818
|
} else if (insp.state === "stopped") {
|
|
11231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
11473
|
-
else
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
12716
|
+
log42.info("cancelled");
|
|
12127
12717
|
return;
|
|
12128
12718
|
}
|
|
12129
12719
|
}
|
|
12130
12720
|
let selfUpdated = false;
|
|
12131
12721
|
if (opts.skipSelf) {
|
|
12132
|
-
|
|
12722
|
+
log42.info("skipping self-update (--skip-self)");
|
|
12133
12723
|
} else {
|
|
12134
12724
|
const cmd = selfUpdateCommand(method);
|
|
12135
12725
|
if (cmd === null) {
|
|
12136
|
-
|
|
12726
|
+
log42.info(describeSelfUpdate(method));
|
|
12137
12727
|
} else {
|
|
12138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
12837
|
+
log43.info("box is paused; unpausing");
|
|
12218
12838
|
await unpauseBox(box.id);
|
|
12219
12839
|
} else if (insp.state === "stopped") {
|
|
12220
|
-
|
|
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
|
-
|
|
12869
|
+
log43.info("box is paused; resuming");
|
|
12250
12870
|
await p.resume(box);
|
|
12251
12871
|
} else if (state === "stopped") {
|
|
12252
|
-
|
|
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
|
|
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
|
-
|
|
12914
|
+
log44.error(`agentbox-ctl wait-ready failed: ${proc.stderr || proc.stdout}`);
|
|
12295
12915
|
process.exit(1);
|
|
12296
12916
|
}
|
|
12297
12917
|
if (opts.json) {
|