@madarco/agentbox 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/dist/{_cloud-attach-GUBB5RH2.js → _cloud-attach-5KJWOASL.js} +4 -4
  3. package/dist/{chunk-RSKG7AFU.js → chunk-3WCEB6RE.js} +2 -2
  4. package/dist/{chunk-XKH7NTT7.js → chunk-DBBUDKKB.js} +248 -5
  5. package/dist/chunk-DBBUDKKB.js.map +1 -0
  6. package/dist/{chunk-TCS5HXJX.js → chunk-GXJNJUEV.js} +1090 -527
  7. package/dist/chunk-GXJNJUEV.js.map +1 -0
  8. package/dist/{chunk-LDMYHWUS.js → chunk-NW2UZQV6.js} +10 -6
  9. package/dist/chunk-NW2UZQV6.js.map +1 -0
  10. package/dist/{chunk-TBSIJVSN.js → chunk-PIK47622.js} +37 -17
  11. package/dist/chunk-PIK47622.js.map +1 -0
  12. package/dist/{chunk-BKU34KYY.js → chunk-QXFNLKJJ.js} +9 -3
  13. package/dist/{chunk-BKU34KYY.js.map → chunk-QXFNLKJJ.js.map} +1 -1
  14. package/dist/{chunk-BYCLD6D6.js → chunk-SB4QTF2T.js} +98 -54
  15. package/dist/chunk-SB4QTF2T.js.map +1 -0
  16. package/dist/{chunk-VATTS2MR.js → chunk-SENASAU4.js} +10 -6
  17. package/dist/{chunk-VATTS2MR.js.map → chunk-SENASAU4.js.map} +1 -1
  18. package/dist/{dist-34RKQ74M.js → dist-4IQFJJQI.js} +5 -5
  19. package/dist/{dist-4DPOL5A7.js → dist-7YB7BMNG.js} +5 -5
  20. package/dist/{dist-3IMQNTTV.js → dist-SL2QSMBE.js} +5 -5
  21. package/dist/{dist-J2IHD5T7.js → dist-VHI5QOSQ.js} +6 -6
  22. package/dist/{dist-57M6ZA7H.js → dist-XC47DSCR.js} +5 -5
  23. package/dist/index.js +1043 -333
  24. package/dist/index.js.map +1 -1
  25. package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js → prepared-state-MQHD3M5F-2LANTRL7.js} +2 -2
  26. package/package.json +6 -5
  27. package/runtime/docker/Dockerfile.box +21 -2
  28. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +112 -29
  29. package/runtime/docker/packages/ctl/dist/bin.cjs +10353 -8575
  30. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +5 -2
  31. package/runtime/docker/packages/sandbox-docker/scripts/linear-shim +181 -0
  32. package/runtime/docker/packages/sandbox-docker/scripts/ntn-shim +95 -0
  33. package/runtime/e2b/agentbox-checkpoint-cleanup +5 -2
  34. package/runtime/e2b/agentbox-setup-skill.md +112 -29
  35. package/runtime/e2b/ctl.cjs +10353 -8575
  36. package/runtime/e2b/linear-shim +181 -0
  37. package/runtime/e2b/ntn-shim +95 -0
  38. package/runtime/e2b/scripts/build-template.sh +13 -7
  39. package/runtime/hetzner/agentbox-checkpoint-cleanup +5 -2
  40. package/runtime/hetzner/agentbox-setup-skill.md +112 -29
  41. package/runtime/hetzner/ctl.cjs +10353 -8575
  42. package/runtime/hetzner/linear-shim +181 -0
  43. package/runtime/hetzner/ntn-shim +95 -0
  44. package/runtime/hetzner/scripts/install-box.sh +19 -9
  45. package/runtime/relay/bin.cjs +3707 -2828
  46. package/runtime/vercel/agentbox-checkpoint-cleanup +5 -2
  47. package/runtime/vercel/agentbox-setup-skill.md +112 -29
  48. package/runtime/vercel/ctl.cjs +10353 -8575
  49. package/runtime/vercel/linear-shim +181 -0
  50. package/runtime/vercel/ntn-shim +95 -0
  51. package/runtime/vercel/scripts/provision.sh +13 -7
  52. package/share/agentbox-setup/SKILL.md +112 -29
  53. package/share/host-skills/agentbox-info/SKILL.md +22 -2
  54. package/dist/chunk-BYCLD6D6.js.map +0 -1
  55. package/dist/chunk-LDMYHWUS.js.map +0 -1
  56. package/dist/chunk-TBSIJVSN.js.map +0 -1
  57. package/dist/chunk-TCS5HXJX.js.map +0 -1
  58. package/dist/chunk-XKH7NTT7.js.map +0 -1
  59. /package/dist/{_cloud-attach-GUBB5RH2.js.map → _cloud-attach-5KJWOASL.js.map} +0 -0
  60. /package/dist/{chunk-RSKG7AFU.js.map → chunk-3WCEB6RE.js.map} +0 -0
  61. /package/dist/{dist-34RKQ74M.js.map → dist-4IQFJJQI.js.map} +0 -0
  62. /package/dist/{dist-4DPOL5A7.js.map → dist-7YB7BMNG.js.map} +0 -0
  63. /package/dist/{dist-3IMQNTTV.js.map → dist-SL2QSMBE.js.map} +0 -0
  64. /package/dist/{dist-J2IHD5T7.js.map → dist-VHI5QOSQ.js.map} +0 -0
  65. /package/dist/{dist-57M6ZA7H.js.map → dist-XC47DSCR.js.map} +0 -0
  66. /package/dist/{prepared-state-MQHD3M5F-Q27AZU53.js.map → prepared-state-MQHD3M5F-2LANTRL7.js.map} +0 -0
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  clipboardCaptureAvailable,
9
9
  cloudAgentAttach,
10
10
  cloudAgentStartDetached,
11
+ cmuxBinary,
11
12
  createMenuLines,
12
13
  detectHostTerminal,
13
14
  getProvider,
@@ -26,10 +27,11 @@ import {
26
27
  runWrappedAttach,
27
28
  setTerminalTitle,
28
29
  sidebarLines,
30
+ spawnInNewTerminal,
29
31
  statusLine,
30
32
  stripTitleGlyph,
31
33
  subscribePrompts
32
- } from "./chunk-BYCLD6D6.js";
34
+ } from "./chunk-SB4QTF2T.js";
33
35
  import {
34
36
  daytonaBackend,
35
37
  ensureDaytonaCredentials,
@@ -37,7 +39,7 @@ import {
37
39
  readDaytonaCredStatus,
38
40
  readPreparedDaytonaState,
39
41
  secretsPath
40
- } from "./chunk-RSKG7AFU.js";
42
+ } from "./chunk-3WCEB6RE.js";
41
43
  import {
42
44
  detectEgressIp,
43
45
  ensureHetznerCredentials,
@@ -48,7 +50,7 @@ import {
48
50
  readPreparedState,
49
51
  secretsPath as secretsPath2,
50
52
  syncFirewallSource
51
- } from "./chunk-BKU34KYY.js";
53
+ } from "./chunk-QXFNLKJJ.js";
52
54
  import {
53
55
  detectSbx,
54
56
  ensureVercelCredentials,
@@ -57,14 +59,14 @@ import {
57
59
  readPreparedState as readPreparedState2,
58
60
  readVercelCredStatus,
59
61
  secretsPath as secretsPath3
60
- } from "./chunk-VATTS2MR.js";
62
+ } from "./chunk-SENASAU4.js";
61
63
  import {
62
64
  ensureE2bCredentials,
63
65
  maskKey as maskKey4,
64
66
  readE2bCredStatus,
65
67
  readPreparedState as readPreparedState3,
66
68
  secretsPath as secretsPath4
67
- } from "./chunk-LDMYHWUS.js";
69
+ } from "./chunk-NW2UZQV6.js";
68
70
  import {
69
71
  agentSpecsForCloud,
70
72
  currentCloudBaseFingerprint,
@@ -74,11 +76,11 @@ import {
74
76
  probeCloudCheckpoint,
75
77
  resolveCloudCheckpoint,
76
78
  seedAgentVolumesIfFresh
77
- } from "./chunk-TBSIJVSN.js";
79
+ } from "./chunk-PIK47622.js";
78
80
  import {
79
- AmbiguousBoxError,
81
+ ALL_CONNECTORS,
80
82
  BOX_STATUS_EVENT,
81
- BoxNotFoundError,
83
+ BUILT_IN_DEFAULTS,
82
84
  CODEX_CREDENTIALS_BACKUP_FILE,
83
85
  ClaudeSessionError,
84
86
  CodexSessionError,
@@ -96,7 +98,6 @@ import {
96
98
  SHARED_CODEX_VOLUME,
97
99
  SHARED_OPENCODE_VOLUME,
98
100
  UserConfigError,
99
- UserFacingError,
100
101
  agentboxHomeBytes,
101
102
  allCheckpointImagesBytes,
102
103
  allocateShellSessionName,
@@ -165,6 +166,8 @@ import {
165
166
  mintHostInitiatedToken,
166
167
  openBoxInFinder,
167
168
  opencodeSessionInfo,
169
+ parseCarrySection,
170
+ parseReplacementsSection,
168
171
  parseShellSessionList,
169
172
  pauseBox,
170
173
  portlessGetUrl,
@@ -190,7 +193,6 @@ import {
190
193
  renderStatusTable,
191
194
  renderTaskTable,
192
195
  resetPortlessCache,
193
- resolveAgentLauncher,
194
196
  resolveBoxImage,
195
197
  resolveBoxSize,
196
198
  resolveCheckpoint,
@@ -229,10 +231,13 @@ import {
229
231
  waitForTmuxPaneContent,
230
232
  warmUpClaudeCredentials,
231
233
  writeJob
232
- } from "./chunk-TCS5HXJX.js";
234
+ } from "./chunk-GXJNJUEV.js";
233
235
  import {
236
+ AmbiguousBoxError,
237
+ BoxNotFoundError,
234
238
  DEFAULT_BOX_IMAGE,
235
239
  STATE_DIR,
240
+ UserFacingError,
236
241
  computeDockerContextFingerprint,
237
242
  ensureImage,
238
243
  hostOpenCommand,
@@ -241,13 +246,15 @@ import {
241
246
  readPreparedDockerState,
242
247
  readState,
243
248
  recordBox,
244
- resolveBoxRef
245
- } from "./chunk-XKH7NTT7.js";
249
+ resolveAgentLauncher,
250
+ resolveBoxRef,
251
+ resolveRuleRefs
252
+ } from "./chunk-DBBUDKKB.js";
246
253
  import "./chunk-G3H2L3O2.js";
247
254
 
248
255
  // src/version.ts
249
- var AGENTBOX_VERSION = true ? "0.14.0" : "0.0.0-dev";
250
- var AGENTBOX_COMMIT = true ? "38f96cffd" : "dev";
256
+ var AGENTBOX_VERSION = true ? "0.16.0" : "0.0.0-dev";
257
+ var AGENTBOX_COMMIT = true ? "de3c2792d" : "dev";
251
258
 
252
259
  // src/index.ts
253
260
  import { Command as Command48 } from "commander";
@@ -430,6 +437,178 @@ function derivedAgentState(claude) {
430
437
  return claude.state;
431
438
  }
432
439
 
440
+ // src/lib/agent-answer.ts
441
+ import { createHash } from "crypto";
442
+ var TUI_ID_PREFIX = "tui";
443
+ function inTuiKind(claude) {
444
+ if (claude.state === "working" || claude.state === "compacting") return null;
445
+ if (claude.plan !== void 0) return "plan";
446
+ if (claude.question !== void 0) return "question";
447
+ if (claude.state === "waiting") return "permission";
448
+ return null;
449
+ }
450
+ function digestForBlock(claude, kind) {
451
+ let material;
452
+ if (kind === "plan") {
453
+ material = `plan|${claude.plan?.capturedAt ?? ""}|${claude.plan?.plan ?? ""}`;
454
+ } else if (kind === "question") {
455
+ material = `question|${claude.question?.capturedAt ?? ""}|${JSON.stringify(claude.question?.questions ?? [])}`;
456
+ } else {
457
+ material = `permission|${claude.updatedAt ?? ""}`;
458
+ }
459
+ return createHash("sha256").update(material).digest("hex").slice(0, 12);
460
+ }
461
+ function mintTuiId(boxId, claude) {
462
+ const kind = inTuiKind(claude);
463
+ if (kind === null) return null;
464
+ return { id: `${TUI_ID_PREFIX}:${boxId}:${kind}:${digestForBlock(claude, kind)}`, kind };
465
+ }
466
+ function parseTuiId(id) {
467
+ const parts = id.split(":");
468
+ if (parts.length !== 4 || parts[0] !== TUI_ID_PREFIX) return null;
469
+ const [, boxId, kind, digest] = parts;
470
+ if (!boxId || !digest) return null;
471
+ if (kind !== "plan" && kind !== "question" && kind !== "permission") return null;
472
+ return { boxId, kind, digest };
473
+ }
474
+ function isTuiId(id) {
475
+ return id.startsWith(`${TUI_ID_PREFIX}:`);
476
+ }
477
+ var OPTION_ENTER_DELAY_MS = 150;
478
+ function answerKeystrokes(_agent, _kind, decision) {
479
+ if (decision.deny === true) {
480
+ return [{ type: "key", value: "Escape" }];
481
+ }
482
+ if (decision.option !== void 0) {
483
+ return [
484
+ { type: "literal", value: String(decision.option) },
485
+ { type: "delay", ms: OPTION_ENTER_DELAY_MS },
486
+ { type: "key", value: "Enter" }
487
+ ];
488
+ }
489
+ return [{ type: "key", value: "Enter" }];
490
+ }
491
+ function resolveQuestionOption(claude, raw) {
492
+ const options = claude.question?.questions?.[0]?.options ?? [];
493
+ const asNum = Number.parseInt(raw, 10);
494
+ if (Number.isFinite(asNum) && String(asNum) === raw.trim()) {
495
+ return asNum >= 1 && asNum <= options.length ? asNum : null;
496
+ }
497
+ const needle = raw.trim().toLowerCase();
498
+ const exact = options.findIndex((o) => o.label.toLowerCase() === needle);
499
+ if (exact !== -1) return exact + 1;
500
+ const prefix = options.findIndex((o) => o.label.toLowerCase().startsWith(needle));
501
+ return prefix !== -1 ? prefix + 1 : null;
502
+ }
503
+
504
+ // src/lib/drive/tmux.ts
505
+ var TMUX_USER = "vscode";
506
+ async function captureSession(provider, box, session, opts = {}) {
507
+ const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
508
+ if (opts.rows) {
509
+ argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
510
+ }
511
+ const res = await provider.exec(box, argv2, { user: TMUX_USER });
512
+ if (res.exitCode !== 0) {
513
+ throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
514
+ }
515
+ return res.stdout.replace(/\n$/, "");
516
+ }
517
+ async function paneInfo(provider, box, session) {
518
+ const fmt = "#{pane_width},#{pane_height},#{cursor_x},#{cursor_y}";
519
+ const res = await provider.exec(box, ["tmux", "display-message", "-p", "-t", session, fmt], {
520
+ user: TMUX_USER
521
+ });
522
+ if (res.exitCode !== 0) {
523
+ throw new Error(failure("display-message", session, res.stderr || res.stdout));
524
+ }
525
+ const m = /^(\d+),(\d+),(\d+),(\d+)/.exec(res.stdout.trim());
526
+ if (!m) throw new Error(`tmux display-message returned unexpected output: ${res.stdout}`);
527
+ return {
528
+ cols: Number(m[1]),
529
+ rows: Number(m[2]),
530
+ cursor: { x: Number(m[3]), y: Number(m[4]) }
531
+ };
532
+ }
533
+ async function sendLiteral(provider, box, session, literal) {
534
+ if (literal.length === 0) return;
535
+ const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, "-l", "--", literal], {
536
+ user: TMUX_USER
537
+ });
538
+ if (res.exitCode !== 0) {
539
+ throw new Error(failure("send-keys -l", session, res.stderr || res.stdout));
540
+ }
541
+ }
542
+ async function sendKey(provider, box, session, key) {
543
+ const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, key], {
544
+ user: TMUX_USER
545
+ });
546
+ if (res.exitCode !== 0) {
547
+ throw new Error(failure("send-keys", session, res.stderr || res.stdout));
548
+ }
549
+ }
550
+ async function resizeWindow(provider, box, session, cols, rows) {
551
+ const res = await provider.exec(
552
+ box,
553
+ ["tmux", "resize-window", "-t", session, "-x", String(cols), "-y", String(rows)],
554
+ { user: TMUX_USER }
555
+ );
556
+ if (res.exitCode !== 0) {
557
+ throw new Error(failure("resize-window", session, res.stderr || res.stdout));
558
+ }
559
+ }
560
+ async function listSessions(provider, box) {
561
+ const res = await provider.exec(
562
+ box,
563
+ ["tmux", "list-sessions", "-F", "#{session_name}"],
564
+ { user: TMUX_USER }
565
+ );
566
+ if (res.exitCode !== 0) return [];
567
+ return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
568
+ }
569
+ function failure(op, session, detail) {
570
+ const tail = detail.trim();
571
+ return `tmux ${op} failed for session '${session}'${tail ? `: ${tail}` : ""}`;
572
+ }
573
+
574
+ // src/lib/drive/session.ts
575
+ var AGENT_SESSION_PRIORITY = ["claude", "codex", "opencode"];
576
+ async function resolveDriveSession(provider, box, explicit) {
577
+ if (await provider.probeState(box) === "paused") {
578
+ process.stderr.write(`drive: box ${box.name} is paused; unpausing
579
+ `);
580
+ await provider.resume(box);
581
+ }
582
+ const sessions = await listSessions(provider, box);
583
+ if (explicit !== void 0 && explicit !== "") {
584
+ if (!sessions.includes(explicit)) {
585
+ throw new SessionNotFoundError(explicit, sessions);
586
+ }
587
+ return { name: explicit, available: sessions };
588
+ }
589
+ for (const candidate of AGENT_SESSION_PRIORITY) {
590
+ if (sessions.includes(candidate)) {
591
+ return { name: candidate, available: sessions };
592
+ }
593
+ }
594
+ if (sessions.length === 1 && sessions[0]) {
595
+ return { name: sessions[0], available: sessions };
596
+ }
597
+ throw new SessionNotFoundError(void 0, sessions);
598
+ }
599
+ var SessionNotFoundError = class extends Error {
600
+ wanted;
601
+ available;
602
+ constructor(wanted, available) {
603
+ const head = wanted ? `no tmux session '${wanted}' in this box` : "no agent tmux session running in this box";
604
+ const tail = available.length ? ` (running: ${available.join(", ")})` : " (tmux server not running or no sessions)";
605
+ super(head + tail);
606
+ this.name = "SessionNotFoundError";
607
+ this.wanted = wanted;
608
+ this.available = available;
609
+ }
610
+ };
611
+
433
612
  // src/lib/wait/events.ts
434
613
  var POLL_INTERVAL_MS = 500;
435
614
  var WaitTimeoutError = class extends Error {
@@ -606,9 +785,209 @@ var agentGetPlanQuestionCommand = new Command("get-plan-question").description("
606
785
  handleLifecycleError(err);
607
786
  }
608
787
  });
788
+ var agentApprovalsCommand = new Command("approvals").description(
789
+ "List everything a box is blocked on: relay host-action approvals (git push, cp host<->box, gh PR writes, checkpoint) AND the agent's in-TUI prompts (plan approval, question, tool permission). Each row carries an id to pass to `agent approve`."
790
+ ).argument("[box]", "box ref (default: only box in this project)").option("--json", "emit the pending approvals as a JSON array").option(
791
+ "--wait <ms>",
792
+ "block until at least one approval is pending (or this wall-clock cap elapses), then print"
793
+ ).action(async (boxRef, opts) => {
794
+ try {
795
+ const box = await resolveBoxOrExit(boxRef);
796
+ const relayUrl = (await ensureRelay()).hostUrl;
797
+ const waitMs = opts.wait !== void 0 ? parsePositiveInt(opts.wait, "--wait") : void 0;
798
+ let rows = await gatherApprovals(relayUrl, box);
799
+ if (waitMs !== void 0 && rows.length === 0) {
800
+ const start = Date.now();
801
+ while (rows.length === 0 && Date.now() - start < waitMs) {
802
+ await sleep2(Math.min(500, waitMs - (Date.now() - start)));
803
+ rows = await gatherApprovals(relayUrl, box);
804
+ }
805
+ }
806
+ if (opts.json === true) {
807
+ process.stdout.write(JSON.stringify(rows) + "\n");
808
+ return;
809
+ }
810
+ if (rows.length === 0) {
811
+ log3.info("nothing pending for this box (no relay approvals, agent not parked on a prompt)");
812
+ return;
813
+ }
814
+ for (const row2 of rows) {
815
+ process.stdout.write(approvalDisplay(row2) + "\n");
816
+ }
817
+ } catch (err) {
818
+ handleLifecycleError(err);
819
+ }
820
+ });
821
+ var agentApproveCommand = new Command("approve").description(
822
+ "Answer a pending approval by id (see `agent approvals`). The id is a safety token: you answer the exact prompt you inspected, and if a different one has since taken its place the approve is refused. Works for both relay host-action approvals and the agent's in-TUI prompts (plan / question / tool permission). Approves by default; --deny rejects."
823
+ ).argument("<id>", "approval id from `agent approvals` (relay UUID or a tui:... id)").option("--deny", "reject instead of approving").option("--cancel", "relay approvals only: dismiss (treated as denied; marks it cancelled)").option(
824
+ "--option <n|label>",
825
+ "in-TUI question/permission: pick this 1-based option (or match its label) instead of the default"
826
+ ).action(async (id, opts) => {
827
+ try {
828
+ if (isTuiId(id)) {
829
+ await approveInTui(id, opts);
830
+ return;
831
+ }
832
+ await approveRelay(id, opts);
833
+ } catch (err) {
834
+ handleLifecycleError(err);
835
+ }
836
+ });
837
+ async function approveRelay(id, opts) {
838
+ const relayUrl = (await ensureRelay()).hostUrl;
839
+ const cancelled = opts.cancel === true;
840
+ const answer = opts.deny === true || cancelled ? "n" : "y";
841
+ const url = new URL("/admin/prompts/answer", relayUrl);
842
+ const res = await fetch(url, {
843
+ method: "POST",
844
+ headers: { "Content-Type": "application/json" },
845
+ body: JSON.stringify({ id, answer, cancelled: cancelled || void 0 })
846
+ });
847
+ if (res.status === 204) {
848
+ log3.success(`approval ${id}: ${answer === "y" ? "approved" : "denied"}`);
849
+ return;
850
+ }
851
+ if (res.status === 404) {
852
+ log3.info(`approval ${id} already resolved (or expired)`);
853
+ return;
854
+ }
855
+ log3.error(`relay /admin/prompts/answer: HTTP ${String(res.status)}`);
856
+ process.exit(1);
857
+ }
858
+ async function approveInTui(id, opts) {
859
+ const parsed = parseTuiId(id);
860
+ if (!parsed) {
861
+ log3.error(`malformed in-TUI approval id: ${id}`);
862
+ process.exit(2);
863
+ }
864
+ if (opts.cancel === true) {
865
+ log3.error("--cancel applies to relay approvals only; use --deny for in-TUI prompts");
866
+ process.exit(2);
867
+ }
868
+ const box = await resolveBoxOrExit(parsed.boxId);
869
+ const status = await readBoxStatus(box);
870
+ const claude = status?.claude;
871
+ const current = claude ? mintTuiId(box.id, claude) : null;
872
+ if (!current || current.id !== id) {
873
+ log3.error(
874
+ `approval ${id} is no longer the pending prompt for ${box.name} (it changed or was answered) \u2014 re-run \`agentbox agent approvals ${box.name}\``
875
+ );
876
+ process.exit(1);
877
+ }
878
+ let option;
879
+ if (opts.option !== void 0) {
880
+ if (parsed.kind === "question" && claude) {
881
+ const resolved = resolveQuestionOption(claude, opts.option);
882
+ if (resolved === null) {
883
+ const labels = (claude.question?.questions?.[0]?.options ?? []).map((o) => o.label);
884
+ log3.error(`--option '${opts.option}' did not match an option (have: ${labels.join(" | ")})`);
885
+ process.exit(2);
886
+ }
887
+ option = resolved;
888
+ } else {
889
+ const n = Number.parseInt(opts.option, 10);
890
+ if (!Number.isFinite(n) || n < 1) {
891
+ log3.error(`--option must be a 1-based number for a ${parsed.kind} prompt (got: ${opts.option})`);
892
+ process.exit(2);
893
+ }
894
+ option = n;
895
+ }
896
+ }
897
+ const provider = await providerForBox(box);
898
+ const session = await resolveDriveSession(provider, box, void 0);
899
+ const agent = agentKindForSession(session.name);
900
+ const steps = answerKeystrokes(agent, parsed.kind, { option, deny: opts.deny });
901
+ await runAnswerSteps(provider, box, session.name, steps);
902
+ const verb = opts.deny === true ? "denied" : option !== void 0 ? `answered (option ${String(option)})` : "approved";
903
+ log3.success(`${parsed.kind} prompt on ${box.name}: ${verb}`);
904
+ }
905
+ function agentKindForSession(session) {
906
+ if (session === "codex") return "codex";
907
+ if (session === "opencode") return "opencode";
908
+ return "claude";
909
+ }
910
+ async function runAnswerSteps(provider, box, session, steps) {
911
+ for (const step of steps) {
912
+ if (step.type === "literal") await sendLiteral(provider, box, session, step.value);
913
+ else if (step.type === "key") await sendKey(provider, box, session, step.value);
914
+ else await sleep2(step.ms);
915
+ }
916
+ }
609
917
  agentCommand.addCommand(agentStateCommand);
610
918
  agentCommand.addCommand(agentWaitForCommand);
611
919
  agentCommand.addCommand(agentGetPlanQuestionCommand);
920
+ agentCommand.addCommand(agentApprovalsCommand);
921
+ agentCommand.addCommand(agentApproveCommand);
922
+ async function gatherApprovals(relayUrl, box) {
923
+ const rows = [];
924
+ const relay = await fetchRelayApprovals(relayUrl, box.id);
925
+ for (const ev of relay) {
926
+ rows.push({
927
+ id: ev.id,
928
+ kind: "host-action",
929
+ command: ev.context?.command,
930
+ argv: ev.context?.argv,
931
+ cwd: ev.context?.cwd,
932
+ message: ev.message,
933
+ detail: ev.detail,
934
+ defaultAnswer: ev.defaultAnswer
935
+ });
936
+ }
937
+ const claude = (await readBoxStatus(box))?.claude;
938
+ const tui = claude ? mintTuiId(box.id, claude) : null;
939
+ if (claude && tui) {
940
+ if (tui.kind === "plan") {
941
+ rows.push({ id: tui.id, kind: "plan", message: "Approve plan?", plan: claude.plan?.plan ?? "" });
942
+ } else if (tui.kind === "question") {
943
+ const q = claude.question?.questions?.[0];
944
+ rows.push({
945
+ id: tui.id,
946
+ kind: "question",
947
+ message: q?.question ?? "Answer question?",
948
+ options: (q?.options ?? []).map((o) => o.label)
949
+ });
950
+ } else {
951
+ rows.push({
952
+ id: tui.id,
953
+ kind: "permission",
954
+ message: "Tool-permission prompt (screen-driven; inspect with `agentbox drive snapshot`)",
955
+ state: claude.state
956
+ });
957
+ }
958
+ }
959
+ return rows;
960
+ }
961
+ async function fetchRelayApprovals(relayUrl, boxId) {
962
+ const url = new URL("/admin/prompts", relayUrl);
963
+ url.searchParams.set("boxId", boxId);
964
+ const res = await fetch(url);
965
+ if (!res.ok) throw new Error(`relay /admin/prompts: HTTP ${String(res.status)}`);
966
+ const body = await res.json();
967
+ return body.prompts ?? [];
968
+ }
969
+ function approvalDisplay(row2) {
970
+ if (row2.kind === "host-action") {
971
+ const cmd = row2.command ?? row2.message;
972
+ const argv2 = row2.argv?.length ? ` ${row2.argv.join(" ")}` : "";
973
+ const detail = row2.detail ? ` (${row2.detail})` : "";
974
+ return `${row2.id} [host-action] ${cmd}${argv2}${detail}`;
975
+ }
976
+ if (row2.kind === "plan") {
977
+ return `${row2.id} [plan] ${firstLine(row2.plan)}`;
978
+ }
979
+ if (row2.kind === "question") {
980
+ return `${row2.id} [question] ${row2.message} {${row2.options.join(" | ")}}`;
981
+ }
982
+ return `${row2.id} [permission] ${row2.message}`;
983
+ }
984
+ function firstLine(s) {
985
+ const line = s.split("\n", 1)[0] ?? "";
986
+ return line.length > 100 ? line.slice(0, 99) + "\u2026" : line;
987
+ }
988
+ function sleep2(ms) {
989
+ return new Promise((r) => setTimeout(r, ms));
990
+ }
612
991
  function emitMatch(claude, asJson) {
613
992
  if (asJson) {
614
993
  process.stdout.write(JSON.stringify(claude) + "\n");
@@ -727,25 +1106,149 @@ function buildPromptArgs(agentKind, prompt, userArgs) {
727
1106
  }
728
1107
 
729
1108
  // src/lib/carry-resync.ts
730
- import { join as join4 } from "path";
1109
+ import { join as join5 } from "path";
731
1110
 
732
1111
  // src/lib/carry-resolve.ts
733
- import { realpath, stat as stat2 } from "fs/promises";
1112
+ import { realpath, stat as stat3 } from "fs/promises";
734
1113
  import { homedir as homedir2 } from "os";
735
- import { isAbsolute, join as join3, normalize, resolve } from "path";
736
- var DEFAULT_MAX_BYTES = 50 * 1024 * 1024;
1114
+ import { isAbsolute, join as join4, normalize, relative, resolve } from "path";
1115
+
1116
+ // src/lib/dir-breakdown.ts
1117
+ import { readdir, stat as stat2 } from "fs/promises";
1118
+ import { join as join3 } from "path";
1119
+ var DEFAULT_CP_EXCLUDES = [
1120
+ ".git",
1121
+ "node_modules",
1122
+ "bin",
1123
+ "obj",
1124
+ "packages",
1125
+ "dist",
1126
+ ".next",
1127
+ "target"
1128
+ ];
1129
+ function isBareName(token) {
1130
+ return !token.includes("/") && !token.includes("*") && !token.includes("?");
1131
+ }
1132
+ function toTarExcludes(tokens) {
1133
+ const out = [];
1134
+ for (const t of tokens) {
1135
+ if (isBareName(t)) {
1136
+ out.push(`*/${t}`, t);
1137
+ } else {
1138
+ out.push(t);
1139
+ }
1140
+ }
1141
+ return out;
1142
+ }
1143
+ function effectiveExcludes(userTokens, useDefaults) {
1144
+ const seen = /* @__PURE__ */ new Set();
1145
+ const out = [];
1146
+ const add = (t) => {
1147
+ if (t && !seen.has(t)) {
1148
+ seen.add(t);
1149
+ out.push(t);
1150
+ }
1151
+ };
1152
+ if (useDefaults) DEFAULT_CP_EXCLUDES.forEach(add);
1153
+ userTokens.forEach(add);
1154
+ return out;
1155
+ }
1156
+ function globToRegExp(glob) {
1157
+ const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
1158
+ return new RegExp(`^${escaped}$`);
1159
+ }
1160
+ function isPathExcluded(relPath, tokens) {
1161
+ const segs = relPath.split("/");
1162
+ for (const t of tokens) {
1163
+ if (isBareName(t)) {
1164
+ if (segs.includes(t)) return true;
1165
+ } else if (globToRegExp(t).test(relPath)) {
1166
+ return true;
1167
+ }
1168
+ }
1169
+ return false;
1170
+ }
1171
+ async function buildNode(abs, rel, tokens, seen) {
1172
+ let bytes = 0;
1173
+ const children = [];
1174
+ let entries;
1175
+ try {
1176
+ entries = await readdir(abs);
1177
+ } catch {
1178
+ return { path: rel || ".", bytes: 0, children };
1179
+ }
1180
+ for (const name of entries) {
1181
+ const childRel = rel ? `${rel}/${name}` : name;
1182
+ if (isPathExcluded(childRel, tokens)) continue;
1183
+ const full = join3(abs, name);
1184
+ let st;
1185
+ try {
1186
+ st = await stat2(full);
1187
+ } catch {
1188
+ continue;
1189
+ }
1190
+ const key = `${String(st.dev)}:${String(st.ino)}`;
1191
+ if (seen.has(key)) continue;
1192
+ seen.add(key);
1193
+ if (st.isDirectory()) {
1194
+ const child = await buildNode(full, childRel, tokens, seen);
1195
+ bytes += child.bytes;
1196
+ children.push(child);
1197
+ } else if (st.isFile()) {
1198
+ bytes += st.size;
1199
+ }
1200
+ }
1201
+ return { path: rel || ".", bytes, children };
1202
+ }
1203
+ async function measureCopy(absSrc, tokens, opts = {}) {
1204
+ const st = await stat2(absSrc);
1205
+ if (!st.isDirectory()) {
1206
+ return { totalBytes: st.size, isDir: false, treeLines: [], topChildren: [] };
1207
+ }
1208
+ const root = await buildNode(absSrc, "", tokens, /* @__PURE__ */ new Set());
1209
+ const maxDepth = opts.maxDepth ?? 3;
1210
+ const floor = opts.floorBytes ?? 10 * 1024 * 1024;
1211
+ const perLevel = opts.perLevel ?? 8;
1212
+ const lines = [];
1213
+ const render = (node, depth, label) => {
1214
+ const pad4 = " ".repeat(depth);
1215
+ lines.push(` ${fmtBytes(node.bytes).padStart(8)} ${pad4}${label}`);
1216
+ if (depth >= maxDepth) return;
1217
+ const kids = [...node.children].sort((a, b) => b.bytes - a.bytes).filter((c) => c.bytes >= floor).slice(0, perLevel);
1218
+ for (const kid of kids) {
1219
+ render(kid, depth + 1, `./${kid.path}`);
1220
+ }
1221
+ };
1222
+ render(root, 0, "./");
1223
+ const topChildren = [...root.children].sort((a, b) => b.bytes - a.bytes).map((c) => ({ path: c.path, bytes: c.bytes }));
1224
+ return { totalBytes: root.bytes, isDir: true, treeLines: lines, topChildren };
1225
+ }
1226
+ function fmtBytes(n) {
1227
+ if (n < 1024) return `${String(n)} B`;
1228
+ const units = ["KB", "MB", "GB", "TB"];
1229
+ let v = n / 1024;
1230
+ let i = 0;
1231
+ while (v >= 1024 && i < units.length - 1) {
1232
+ v /= 1024;
1233
+ i += 1;
1234
+ }
1235
+ return `${v >= 10 || Number.isInteger(v) ? v.toFixed(0) : v.toFixed(1)} ${units[i]}`;
1236
+ }
1237
+
1238
+ // src/lib/carry-resolve.ts
737
1239
  var DENYLIST_DEST_PREFIXES = ["/proc", "/sys", "/dev"];
738
1240
  var DENYLIST_DEST_EXACT = /* @__PURE__ */ new Set(["/etc/passwd", "/etc/shadow"]);
739
1241
  async function resolveCarry(items, opts) {
740
1242
  const home = opts.homeDir ?? homedir2();
741
- const cap = opts.maxBytes ?? readMaxBytesFromEnv() ?? DEFAULT_MAX_BYTES;
1243
+ const cap = opts.maxBytes ?? BUILT_IN_DEFAULTS.box.cpMaxBytes;
742
1244
  const projectRoot = opts.projectRoot;
1245
+ const replacements = opts.replacements ?? {};
743
1246
  const entries = [];
744
1247
  const errors = [];
745
1248
  for (const [i, item] of items.entries()) {
746
1249
  const where = `carry[${String(i)}]`;
747
1250
  try {
748
- const entry = await resolveOne(item, { projectRoot, home, cap, where });
1251
+ const entry = await resolveOne(item, { projectRoot, home, cap, where, replacements });
749
1252
  entries.push(entry);
750
1253
  } catch (err) {
751
1254
  errors.push(err instanceof Error ? err.message : String(err));
@@ -753,13 +1256,6 @@ async function resolveCarry(items, opts) {
753
1256
  }
754
1257
  return { entries, errors };
755
1258
  }
756
- function readMaxBytesFromEnv() {
757
- const raw = process.env.AGENTBOX_CARRY_MAX_BYTES;
758
- if (!raw) return void 0;
759
- const n = Number(raw);
760
- if (!Number.isFinite(n) || n <= 0) return void 0;
761
- return Math.floor(n);
762
- }
763
1259
  async function resolveOne(item, ctx) {
764
1260
  const absSrc = expandHostPath(item.src, ctx);
765
1261
  if (containsDotDot(absSrc)) {
@@ -770,9 +1266,18 @@ async function resolveOne(item, ctx) {
770
1266
  const rawSrc = item.src;
771
1267
  const rawDest = item.dest;
772
1268
  const absDest = item.dest;
1269
+ const hasReplaceOpts = !!(item.replaceEnvs || item.replace || item.rules);
1270
+ const replaceRules = [
1271
+ ...resolveRuleRefs(item.rules ?? [], ctx.replacements, `${ctx.where}.rules`),
1272
+ ...item.replace ?? []
1273
+ ];
1274
+ const replaceFields = {
1275
+ ...item.replaceEnvs ? { replaceEnvs: true } : {},
1276
+ ...replaceRules.length > 0 ? { replace: replaceRules } : {}
1277
+ };
773
1278
  let st;
774
1279
  try {
775
- st = await stat2(absSrc);
1280
+ st = await stat3(absSrc);
776
1281
  } catch (err) {
777
1282
  if (err.code === "ENOENT") {
778
1283
  if (optional) {
@@ -806,10 +1311,17 @@ async function resolveOne(item, ctx) {
806
1311
  } catch {
807
1312
  }
808
1313
  if (st.isDirectory()) {
809
- const bytes = await dirSizeCapped(absSrc, ctx.cap);
1314
+ if (hasReplaceOpts) {
1315
+ throw new Error(
1316
+ `${ctx.where}: replaceEnvs/replace/rules are file-only (src "${absSrc}" is a directory)`
1317
+ );
1318
+ }
1319
+ const tokens = effectiveExcludes(item.exclude ?? [], true);
1320
+ const tarPatterns = toTarExcludes(tokens);
1321
+ const bytes = await dirSizeCapped(absSrc, ctx.cap, tokens);
810
1322
  if (bytes > ctx.cap) {
811
1323
  throw new Error(
812
- `${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes (set AGENTBOX_CARRY_MAX_BYTES to raise the cap or narrow the path)`
1324
+ `${ctx.where}: dir "${absSrc}" exceeds ${String(ctx.cap)} bytes after excludes (add carry exclude: patterns, raise box.cpMaxBytes, or narrow the path)`
813
1325
  );
814
1326
  }
815
1327
  return {
@@ -821,6 +1333,7 @@ async function resolveOne(item, ctx) {
821
1333
  bytes,
822
1334
  ...item.mode !== void 0 ? { mode: item.mode } : {},
823
1335
  ...item.user !== void 0 ? { user: item.user } : {},
1336
+ ...tarPatterns.length > 0 ? { exclude: tarPatterns } : {},
824
1337
  optional,
825
1338
  ...symlinkInfo ? { symlinkInfo } : {}
826
1339
  };
@@ -828,7 +1341,7 @@ async function resolveOne(item, ctx) {
828
1341
  if (st.isFile()) {
829
1342
  if (st.size > ctx.cap) {
830
1343
  throw new Error(
831
- `${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (set AGENTBOX_CARRY_MAX_BYTES to raise)`
1344
+ `${ctx.where}: file "${absSrc}" is ${String(st.size)} bytes, exceeds cap ${String(ctx.cap)} (raise box.cpMaxBytes)`
832
1345
  );
833
1346
  }
834
1347
  return {
@@ -841,7 +1354,8 @@ async function resolveOne(item, ctx) {
841
1354
  ...item.mode !== void 0 ? { mode: item.mode } : {},
842
1355
  ...item.user !== void 0 ? { user: item.user } : {},
843
1356
  optional,
844
- ...symlinkInfo ? { symlinkInfo } : {}
1357
+ ...symlinkInfo ? { symlinkInfo } : {},
1358
+ ...replaceFields
845
1359
  };
846
1360
  }
847
1361
  throw new Error(`${ctx.where}: src "${absSrc}" is neither a regular file nor a directory`);
@@ -893,24 +1407,26 @@ async function realpathSafe(p) {
893
1407
  return resolve(p);
894
1408
  }
895
1409
  }
896
- async function dirSizeCapped(dir, cap) {
1410
+ async function dirSizeCapped(dir, cap, exclude = []) {
897
1411
  let total = 0;
898
1412
  const seen = /* @__PURE__ */ new Set();
899
1413
  async function walk(d) {
900
1414
  if (total > cap) return;
901
- const { readdir: readdir3 } = await import("fs/promises");
1415
+ const { readdir: readdir4 } = await import("fs/promises");
902
1416
  let names;
903
1417
  try {
904
- names = await readdir3(d);
1418
+ names = await readdir4(d);
905
1419
  } catch {
906
1420
  return;
907
1421
  }
908
1422
  for (const name of names) {
909
1423
  if (total > cap) return;
910
- const full = join3(d, name);
1424
+ const full = join4(d, name);
1425
+ const rel = relative(dir, full).split(/[\\/]/).join("/");
1426
+ if (exclude.length > 0 && isPathExcluded(rel, exclude)) continue;
911
1427
  let st;
912
1428
  try {
913
- st = await stat2(full);
1429
+ st = await stat3(full);
914
1430
  } catch {
915
1431
  continue;
916
1432
  }
@@ -935,9 +1451,13 @@ async function resyncCarryFiles(args) {
935
1451
  });
936
1452
  const prior = args.box.carry?.entries ?? [];
937
1453
  if (prior.length === 0) return { recopied: 0, skippedNew: 0 };
938
- const items = await loadCarrySection(join4(args.projectRoot, "agentbox.yaml"));
1454
+ const items = await loadCarrySection(join5(args.projectRoot, "agentbox.yaml"));
939
1455
  if (items.length === 0) return { recopied: 0, skippedNew: 0 };
940
- const resolved = await resolveCarry(items, { projectRoot: args.projectRoot });
1456
+ const cfg = await loadEffectiveConfig(args.projectRoot);
1457
+ const resolved = await resolveCarry(items, {
1458
+ projectRoot: args.projectRoot,
1459
+ maxBytes: cfg.effective.box.cpMaxBytes
1460
+ });
941
1461
  if (resolved.errors.length > 0) {
942
1462
  log47(`carry: resync skipped (resolve errors: ${resolved.errors.length})`);
943
1463
  return { recopied: 0, skippedNew: 0 };
@@ -1072,6 +1592,7 @@ async function submitQueueJob(input) {
1072
1592
  createOpts: input.createOpts,
1073
1593
  maxConcurrent: ceiling,
1074
1594
  ...maxWorking !== void 0 ? { maxWorking } : {},
1595
+ ...input.openTerminal !== void 0 ? { openTerminal: input.openTerminal } : {},
1075
1596
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1076
1597
  logPath: queueLogPath(id)
1077
1598
  };
@@ -1126,6 +1647,58 @@ function postEnqueue(id) {
1126
1647
  });
1127
1648
  }
1128
1649
 
1650
+ // src/terminal/queue-open.ts
1651
+ function captureOpenTerminalContext(mode, env = process.env, cwd = process.cwd()) {
1652
+ if (mode === "none") return void 0;
1653
+ const host = detectHostTerminal(env);
1654
+ if (host === "unknown") return void 0;
1655
+ const base = { host, mode, cwd };
1656
+ if (host === "tmux") {
1657
+ return { ...base, host, tmuxSocket: env["TMUX"], tmuxPane: env["TMUX_PANE"] };
1658
+ }
1659
+ if (host === "cmux") {
1660
+ return {
1661
+ ...base,
1662
+ host,
1663
+ cmuxSocket: env["CMUX_SOCKET_PATH"],
1664
+ cmuxBundledCli: cmuxBinary(env),
1665
+ cmuxSurfaceId: env["CMUX_SURFACE_ID"],
1666
+ cmuxWorkspaceId: env["CMUX_WORKSPACE_ID"]
1667
+ };
1668
+ }
1669
+ return { ...base, host };
1670
+ }
1671
+ function spawnQueuedOpenTerminal(ctx, argv2, title) {
1672
+ if (ctx.host === "tmux") {
1673
+ return spawnInNewTerminal({
1674
+ host: "tmux",
1675
+ mode: ctx.mode,
1676
+ argv: argv2,
1677
+ cwd: ctx.cwd,
1678
+ title,
1679
+ env: ctx.tmuxSocket ? { ...process.env, TMUX: ctx.tmuxSocket } : process.env,
1680
+ tmuxTarget: ctx.tmuxPane
1681
+ });
1682
+ }
1683
+ if (ctx.host === "cmux") {
1684
+ const env = { ...process.env };
1685
+ if (ctx.cmuxSocket) env["CMUX_SOCKET_PATH"] = ctx.cmuxSocket;
1686
+ if (ctx.cmuxBundledCli) env["CMUX_BUNDLED_CLI_PATH"] = ctx.cmuxBundledCli;
1687
+ return spawnInNewTerminal({
1688
+ host: "cmux",
1689
+ mode: ctx.mode,
1690
+ argv: argv2,
1691
+ cwd: ctx.cwd,
1692
+ title,
1693
+ env,
1694
+ cmuxTargetSurface: ctx.cmuxSurfaceId,
1695
+ cmuxTargetWorkspace: ctx.cmuxWorkspaceId,
1696
+ cmuxWorkspaceFallback: true
1697
+ });
1698
+ }
1699
+ return spawnInNewTerminal({ host: "iterm2", mode: ctx.mode, argv: argv2, cwd: ctx.cwd, title });
1700
+ }
1701
+
1129
1702
  // src/commands/_attach-in.ts
1130
1703
  var VALUES = ["split", "window", "tab", "same"];
1131
1704
  var ATTACH_IN_HELP = "where to open the attached session: split | window | tab | same (default from attach.openIn, built-in: split). Only effective when running inside tmux or iTerm2; falls back to inline attach otherwise.";
@@ -1366,14 +1939,15 @@ async function cloudAgentCreate(args) {
1366
1939
  }
1367
1940
 
1368
1941
  // src/lib/carry-gate.ts
1369
- import { join as join5 } from "path";
1942
+ import { readFile as readFile2 } from "fs/promises";
1943
+ import { join as join6 } from "path";
1370
1944
  import { log as log5 } from "@clack/prompts";
1371
1945
 
1372
1946
  // src/carry-prompt.ts
1373
1947
  import { isCancel, log as log4, select } from "@clack/prompts";
1374
1948
 
1375
1949
  // src/fmt.ts
1376
- function fmtBytes(n) {
1950
+ function fmtBytes2(n) {
1377
1951
  if (n === null || n === void 0) return "n/a";
1378
1952
  if (n < 1024) return `${String(n)} B`;
1379
1953
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
@@ -1449,7 +2023,7 @@ function printSummary(entries) {
1449
2023
  if (e.mode !== void 0) flags.push(`mode ${e.mode.toString(8).padStart(4, "0")}`);
1450
2024
  if (e.user !== void 0 && e.user !== 1e3) flags.push(`user ${String(e.user)}`);
1451
2025
  if (e.symlinkInfo === "outside-home") flags.push("symlink \u2192 outside $HOME!");
1452
- const size = e.kind === "missing" ? "\u2014" : fmtBytes(e.bytes ?? 0);
2026
+ const size = e.kind === "missing" ? "\u2014" : fmtBytes2(e.bytes ?? 0);
1453
2027
  rows.push(
1454
2028
  ` ${pad(e.rawSrc, srcW)} \u2192 ${pad(e.rawDest, destW)} ${pad(size, 9)} ${flags.join(", ")}`
1455
2029
  );
@@ -1465,10 +2039,22 @@ function pad(s, w) {
1465
2039
  async function runCarryGate(args) {
1466
2040
  const emit = args.onLog ?? (() => {
1467
2041
  });
1468
- const yamlPath = join5(args.projectRoot, "agentbox.yaml");
1469
- const items = await loadCarrySection(yamlPath);
2042
+ const yamlPath = join6(args.projectRoot, "agentbox.yaml");
2043
+ let yamlText = "";
2044
+ try {
2045
+ yamlText = await readFile2(yamlPath, "utf8");
2046
+ } catch (err) {
2047
+ if (err.code !== "ENOENT") throw err;
2048
+ }
2049
+ const items = parseCarrySection(yamlText);
1470
2050
  if (items.length === 0) return { decision: "approve", entries: [] };
1471
- const resolved = await resolveCarry(items, { projectRoot: args.projectRoot });
2051
+ const cfg = await loadEffectiveConfig(args.projectRoot);
2052
+ const replacements = parseReplacementsSection(yamlText);
2053
+ const resolved = await resolveCarry(items, {
2054
+ projectRoot: args.projectRoot,
2055
+ maxBytes: cfg.effective.box.cpMaxBytes,
2056
+ replacements
2057
+ });
1472
2058
  if (resolved.errors.length > 0) {
1473
2059
  const msg = ["carry: refused to proceed:", ...resolved.errors.map((e) => ` - ${e}`)].join("\n");
1474
2060
  throw new Error(msg);
@@ -1513,10 +2099,10 @@ async function runQueuedCarryGate(args) {
1513
2099
  }
1514
2100
 
1515
2101
  // src/session-teleport/claude.ts
1516
- import { mkdir, mkdtemp, readdir, readFile as readFile2, stat as stat3, writeFile } from "fs/promises";
2102
+ import { mkdir, mkdtemp, readdir as readdir2, readFile as readFile3, stat as stat4, writeFile } from "fs/promises";
1517
2103
  import { existsSync } from "fs";
1518
2104
  import { homedir as homedir4, tmpdir } from "os";
1519
- import { join as join6 } from "path";
2105
+ import { join as join7 } from "path";
1520
2106
 
1521
2107
  // src/session-teleport/cwd-encoding.ts
1522
2108
  function encodeClaudeProjectsDir(absPath) {
@@ -1537,7 +2123,7 @@ var TeleportError = class extends Error {
1537
2123
  var BOX_CLAUDE_PROJECTS_DIR = `/home/vscode/.claude/projects/${BOX_WORKSPACE_ENCODED}`;
1538
2124
  async function resolveClaudeTeleport(opts) {
1539
2125
  const hostHome = opts.hostHome ?? homedir4();
1540
- const projectDir = join6(
2126
+ const projectDir = join7(
1541
2127
  hostHome,
1542
2128
  ".claude",
1543
2129
  "projects",
@@ -1550,8 +2136,8 @@ async function resolveClaudeTeleport(opts) {
1550
2136
  }
1551
2137
  const sessionPath = await pickSessionFile(projectDir, opts.mode);
1552
2138
  const sessionId = sessionPath.replace(/\.jsonl$/u, "").split("/").pop() ?? "";
1553
- const stage = await mkdtemp(join6(tmpdir(), "agentbox-teleport-claude-"));
1554
- const stagedFile = join6(stage, `${sessionId}.jsonl`);
2139
+ const stage = await mkdtemp(join7(tmpdir(), "agentbox-teleport-claude-"));
2140
+ const stagedFile = join7(stage, `${sessionId}.jsonl`);
1555
2141
  await rewriteSessionFile(sessionPath, stagedFile, opts.hostCwd);
1556
2142
  opts.log?.(`teleport: claude session ${sessionId} staged for upload`);
1557
2143
  return {
@@ -1565,7 +2151,7 @@ async function resolveClaudeTeleport(opts) {
1565
2151
  }
1566
2152
  async function pickSessionFile(projectDir, mode) {
1567
2153
  if (mode.kind === "resume") {
1568
- const target = join6(projectDir, `${mode.id}.jsonl`);
2154
+ const target = join7(projectDir, `${mode.id}.jsonl`);
1569
2155
  if (!existsSync(target)) {
1570
2156
  throw new TeleportError(
1571
2157
  `Claude session "${mode.id}" not found in ${projectDir}. List available sessions with: ls "${projectDir}"`
@@ -1573,7 +2159,7 @@ async function pickSessionFile(projectDir, mode) {
1573
2159
  }
1574
2160
  return target;
1575
2161
  }
1576
- const entries = await readdir(projectDir);
2162
+ const entries = await readdir2(projectDir);
1577
2163
  const jsonl = entries.filter((e) => e.endsWith(".jsonl"));
1578
2164
  if (jsonl.length === 0) {
1579
2165
  throw new TeleportError(
@@ -1582,8 +2168,8 @@ async function pickSessionFile(projectDir, mode) {
1582
2168
  }
1583
2169
  const stats = await Promise.all(
1584
2170
  jsonl.map(async (name) => {
1585
- const full = join6(projectDir, name);
1586
- const s = await stat3(full);
2171
+ const full = join7(projectDir, name);
2172
+ const s = await stat4(full);
1587
2173
  return { full, mtimeMs: s.mtimeMs };
1588
2174
  })
1589
2175
  );
@@ -1591,7 +2177,7 @@ async function pickSessionFile(projectDir, mode) {
1591
2177
  return stats[0].full;
1592
2178
  }
1593
2179
  async function rewriteSessionFile(src, dst, hostCwd) {
1594
- const raw = await readFile2(src, "utf8");
2180
+ const raw = await readFile3(src, "utf8");
1595
2181
  const lines = raw.split("\n");
1596
2182
  const out = [];
1597
2183
  for (const line of lines) {
@@ -1616,18 +2202,18 @@ async function rewriteSessionFile(src, dst, hostCwd) {
1616
2202
  }
1617
2203
  out.push(line);
1618
2204
  }
1619
- await mkdir(join6(dst, ".."), { recursive: true });
2205
+ await mkdir(join7(dst, ".."), { recursive: true });
1620
2206
  await writeFile(dst, out.join("\n"), "utf8");
1621
2207
  }
1622
2208
 
1623
2209
  // src/session-teleport/codex.ts
1624
- import { mkdtemp as mkdtemp2, readdir as readdir2, readFile as readFile3, stat as stat4, writeFile as writeFile2 } from "fs/promises";
2210
+ import { mkdtemp as mkdtemp2, readdir as readdir3, readFile as readFile4, stat as stat5, writeFile as writeFile2 } from "fs/promises";
1625
2211
  import { existsSync as existsSync2 } from "fs";
1626
2212
  import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
1627
- import { join as join7 } from "path";
2213
+ import { join as join8 } from "path";
1628
2214
  async function resolveCodexTeleport(opts) {
1629
2215
  const hostHome = opts.hostHome ?? homedir5();
1630
- const sessionsRoot = join7(hostHome, ".codex", "sessions");
2216
+ const sessionsRoot = join8(hostHome, ".codex", "sessions");
1631
2217
  if (!existsSync2(sessionsRoot)) {
1632
2218
  throw new TeleportError(
1633
2219
  `no Codex session history found on the host (expected at ${sessionsRoot}). Run \`codex\` at least once before using -c / --resume.`
@@ -1669,8 +2255,8 @@ async function resolveCodexTeleport(opts) {
1669
2255
  matching.sort((a, b) => b.mtimeMs - a.mtimeMs);
1670
2256
  picked = matching[0];
1671
2257
  }
1672
- const stage = await mkdtemp2(join7(tmpdir2(), "agentbox-teleport-codex-"));
1673
- const stagedFile = join7(stage, posixBasename(picked.relPath));
2258
+ const stage = await mkdtemp2(join8(tmpdir2(), "agentbox-teleport-codex-"));
2259
+ const stagedFile = join8(stage, posixBasename(picked.relPath));
1674
2260
  await rewriteCodexSession(picked.hostPath, stagedFile, opts.hostCwd);
1675
2261
  opts.log?.(`teleport: codex session ${picked.uuid} staged for upload`);
1676
2262
  const boxParentDir = `/home/vscode/.codex/sessions/${posixDirname(picked.relPath)}`;
@@ -1696,24 +2282,24 @@ async function listCodexSessions(sessionsRoot) {
1696
2282
  const years = await safeReaddir(sessionsRoot);
1697
2283
  for (const y of years) {
1698
2284
  if (!/^\d{4}$/u.test(y)) continue;
1699
- const yDir = join7(sessionsRoot, y);
2285
+ const yDir = join8(sessionsRoot, y);
1700
2286
  const months = await safeReaddir(yDir);
1701
2287
  for (const m of months) {
1702
2288
  if (!/^\d{2}$/u.test(m)) continue;
1703
- const mDir = join7(yDir, m);
2289
+ const mDir = join8(yDir, m);
1704
2290
  const days = await safeReaddir(mDir);
1705
2291
  for (const d of days) {
1706
2292
  if (!/^\d{2}$/u.test(d)) continue;
1707
- const dDir = join7(mDir, d);
2293
+ const dDir = join8(mDir, d);
1708
2294
  const files = await safeReaddir(dDir);
1709
2295
  for (const name of files) {
1710
2296
  if (!name.startsWith("rollout-") || !name.endsWith(".jsonl")) continue;
1711
2297
  const uuid = extractCodexUuid(name);
1712
2298
  if (uuid === null) continue;
1713
- const hostPath = join7(dDir, name);
2299
+ const hostPath = join8(dDir, name);
1714
2300
  let mtimeMs = 0;
1715
2301
  try {
1716
- mtimeMs = (await stat4(hostPath)).mtimeMs;
2302
+ mtimeMs = (await stat5(hostPath)).mtimeMs;
1717
2303
  } catch {
1718
2304
  continue;
1719
2305
  }
@@ -1733,7 +2319,7 @@ async function listCodexSessions(sessionsRoot) {
1733
2319
  }
1734
2320
  async function safeReaddir(dir) {
1735
2321
  try {
1736
- return await readdir2(dir);
2322
+ return await readdir3(dir);
1737
2323
  } catch {
1738
2324
  return [];
1739
2325
  }
@@ -1745,16 +2331,16 @@ function extractCodexUuid(filename) {
1745
2331
  return m ? m[1] : null;
1746
2332
  }
1747
2333
  async function peekCodexCwd(file) {
1748
- let firstLine2;
2334
+ let firstLine3;
1749
2335
  try {
1750
- const buf = await readFile3(file, "utf8");
2336
+ const buf = await readFile4(file, "utf8");
1751
2337
  const nl = buf.indexOf("\n");
1752
- firstLine2 = nl === -1 ? buf : buf.slice(0, nl);
2338
+ firstLine3 = nl === -1 ? buf : buf.slice(0, nl);
1753
2339
  } catch {
1754
2340
  return null;
1755
2341
  }
1756
2342
  try {
1757
- const parsed = JSON.parse(firstLine2);
2343
+ const parsed = JSON.parse(firstLine3);
1758
2344
  if (parsed.type === "session_meta" && typeof parsed.payload?.cwd === "string") {
1759
2345
  return parsed.payload.cwd;
1760
2346
  }
@@ -1763,7 +2349,7 @@ async function peekCodexCwd(file) {
1763
2349
  return null;
1764
2350
  }
1765
2351
  async function rewriteCodexSession(src, dst, hostCwd) {
1766
- const raw = await readFile3(src, "utf8");
2352
+ const raw = await readFile4(src, "utf8");
1767
2353
  const lines = raw.split("\n");
1768
2354
  const out = [];
1769
2355
  for (const line of lines) {
@@ -1833,14 +2419,14 @@ async function uploadTeleport(input) {
1833
2419
  }
1834
2420
 
1835
2421
  // src/session-teleport/plan.ts
1836
- import { mkdtemp as mkdtemp3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
2422
+ import { mkdtemp as mkdtemp3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1837
2423
  import { existsSync as existsSync3 } from "fs";
1838
2424
  import { homedir as homedir6, tmpdir as tmpdir3 } from "os";
1839
- import { basename, isAbsolute as isAbsolute2, join as join8, resolve as resolve2 } from "path";
2425
+ import { basename, isAbsolute as isAbsolute2, join as join9, resolve as resolve2 } from "path";
1840
2426
  var BOX_CLAUDE_PLANS_DIR = "/home/vscode/.claude/plans";
1841
2427
  function expandHome(p, hostHome) {
1842
2428
  if (p === "~") return hostHome;
1843
- if (p.startsWith("~/")) return join8(hostHome, p.slice(2));
2429
+ if (p.startsWith("~/")) return join9(hostHome, p.slice(2));
1844
2430
  return isAbsolute2(p) ? p : resolve2(p);
1845
2431
  }
1846
2432
  async function resolvePlanTeleport(opts) {
@@ -1852,9 +2438,9 @@ async function resolvePlanTeleport(opts) {
1852
2438
  );
1853
2439
  }
1854
2440
  const name = basename(planFile);
1855
- const stage = await mkdtemp3(join8(tmpdir3(), "agentbox-teleport-plan-"));
1856
- const stagedFile = join8(stage, name);
1857
- const raw = await readFile4(planFile, "utf8");
2441
+ const stage = await mkdtemp3(join9(tmpdir3(), "agentbox-teleport-plan-"));
2442
+ const stagedFile = join9(stage, name);
2443
+ const raw = await readFile5(planFile, "utf8");
1858
2444
  const absCwd = opts.hostCwd ? resolve2(expandHome(opts.hostCwd, hostHome)) : "";
1859
2445
  const rewritten = absCwd ? raw.split(absCwd).join(BOX_WORKSPACE) : raw;
1860
2446
  await writeFile3(stagedFile, rewritten, "utf8");
@@ -1873,14 +2459,14 @@ async function resolvePlanTeleport(opts) {
1873
2459
  import { log as log6 } from "@clack/prompts";
1874
2460
  import { existsSync as existsSync4, mkdirSync, writeFileSync } from "fs";
1875
2461
  import { homedir as homedir7 } from "os";
1876
- import { join as join9 } from "path";
2462
+ import { join as join10 } from "path";
1877
2463
  function maybeShowInstallHint() {
1878
2464
  try {
1879
- const skill = join9(homedir7(), ".claude", "skills", "agentbox", "SKILL.md");
2465
+ const skill = join10(homedir7(), ".claude", "skills", "agentbox", "SKILL.md");
1880
2466
  if (existsSync4(skill)) return;
1881
- const marker = join9(homedir7(), ".agentbox", "install-hint-shown");
2467
+ const marker = join10(homedir7(), ".agentbox", "install-hint-shown");
1882
2468
  if (existsSync4(marker)) return;
1883
- mkdirSync(join9(homedir7(), ".agentbox"), { recursive: true });
2469
+ mkdirSync(join10(homedir7(), ".agentbox"), { recursive: true });
1884
2470
  writeFileSync(marker, "");
1885
2471
  log6.info("tip: run 'agentbox install' to enable the /agentbox fork command in host Claude");
1886
2472
  } catch {
@@ -1890,18 +2476,18 @@ function maybeShowInstallHint() {
1890
2476
  // src/lib/log-file.ts
1891
2477
  import { closeSync, mkdirSync as mkdirSync2, openSync, renameSync, symlinkSync, unlinkSync, writeFileSync as writeFileSync2, writeSync } from "fs";
1892
2478
  import { homedir as homedir8 } from "os";
1893
- import { join as join10 } from "path";
2479
+ import { join as join11 } from "path";
1894
2480
  function stateDir() {
1895
- return process.env.AGENTBOX_HOME ?? join10(homedir8(), ".agentbox");
2481
+ return process.env.AGENTBOX_HOME ?? join11(homedir8(), ".agentbox");
1896
2482
  }
1897
2483
  function logsDir() {
1898
- return join10(stateDir(), "logs");
2484
+ return join11(stateDir(), "logs");
1899
2485
  }
1900
2486
  function openCommandLog(command) {
1901
2487
  const dir = logsDir();
1902
2488
  mkdirSync2(dir, { recursive: true });
1903
- const path = join10(dir, `${command}.log`);
1904
- const prev = join10(dir, `${command}.log.prev`);
2489
+ const path = join11(dir, `${command}.log`);
2490
+ const prev = join11(dir, `${command}.log.prev`);
1905
2491
  try {
1906
2492
  renameSync(path, prev);
1907
2493
  } catch {
@@ -1961,7 +2547,7 @@ function openCommandLog(command) {
1961
2547
  };
1962
2548
  }
1963
2549
  function updateLatestSymlink(dir, target) {
1964
- const link = join10(dir, "latest.log");
2550
+ const link = join11(dir, "latest.log");
1965
2551
  try {
1966
2552
  unlinkSync(link);
1967
2553
  } catch {
@@ -2075,13 +2661,13 @@ import { basename as basename2 } from "path";
2075
2661
  async function cloudBackendForProvider(provider) {
2076
2662
  switch (provider) {
2077
2663
  case "daytona":
2078
- return (await import("./dist-3IMQNTTV.js")).daytonaBackend;
2664
+ return (await import("./dist-SL2QSMBE.js")).daytonaBackend;
2079
2665
  case "hetzner":
2080
- return (await import("./dist-J2IHD5T7.js")).hetznerBackend;
2666
+ return (await import("./dist-VHI5QOSQ.js")).hetznerBackend;
2081
2667
  case "vercel":
2082
- return (await import("./dist-57M6ZA7H.js")).vercelBackend;
2668
+ return (await import("./dist-XC47DSCR.js")).vercelBackend;
2083
2669
  case "e2b":
2084
- return (await import("./dist-34RKQ74M.js")).e2bBackend;
2670
+ return (await import("./dist-4IQFJJQI.js")).e2bBackend;
2085
2671
  default:
2086
2672
  return null;
2087
2673
  }
@@ -2089,13 +2675,13 @@ async function cloudBackendForProvider(provider) {
2089
2675
  async function currentCloudBaseFingerprintLive(provider) {
2090
2676
  switch (provider) {
2091
2677
  case "daytona":
2092
- return (await import("./dist-3IMQNTTV.js")).currentDaytonaBaseFingerprintLive();
2678
+ return (await import("./dist-SL2QSMBE.js")).currentDaytonaBaseFingerprintLive();
2093
2679
  case "hetzner":
2094
- return (await import("./dist-J2IHD5T7.js")).currentHetznerBaseFingerprintLive();
2680
+ return (await import("./dist-VHI5QOSQ.js")).currentHetznerBaseFingerprintLive();
2095
2681
  case "vercel":
2096
- return (await import("./dist-57M6ZA7H.js")).currentVercelBaseFingerprintLive();
2682
+ return (await import("./dist-XC47DSCR.js")).currentVercelBaseFingerprintLive();
2097
2683
  case "e2b":
2098
- return (await import("./dist-34RKQ74M.js")).currentE2bBaseFingerprintLive();
2684
+ return (await import("./dist-4IQFJJQI.js")).currentE2bBaseFingerprintLive();
2099
2685
  default:
2100
2686
  return void 0;
2101
2687
  }
@@ -2173,18 +2759,30 @@ async function evaluateBaseFreshness(provider) {
2173
2759
 
2174
2760
  // src/wizard.ts
2175
2761
  var IN_BOX_SETUP_GUIDE_PATH = "/usr/local/share/agentbox/setup-guide.md";
2176
- function buildSetupInitialPrompt(workspace) {
2762
+ function buildSetupInitialPrompt(workspace, hasAgentboxYaml = false) {
2177
2763
  const name = basename2(workspace);
2764
+ if (hasAgentboxYaml) {
2765
+ return `The user reopened the agentbox sandbox for "${name}". The workspace already has a /workspace/agentbox.yaml, but the previous snapshot was stale so the box was rebuilt from a fresh base image \u2014 its dependencies and system setup are NOT installed yet. Please run the /agentbox-setup skill (or read ${IN_BOX_SETUP_GUIDE_PATH} if the skill is not loaded), then reuse the existing /workspace/agentbox.yaml: verify the declared tasks and services still fit the project, (re)install dependencies and any system packages the setup needs on this fresh base, and update agentbox.yaml only if something is missing or wrong \u2014 do not regenerate it from scratch. Then run \`agentbox-ctl reload\` from inside the box so the already-running supervisor re-applies the config and immediately runs the declared tasks and autostarts the services (no box restart needed). When the box is warm, capture it with \`agentbox checkpoint --set-default\` so future boxes skip this rebuild. Finally, summarise what you verified and changed, and remind the user how to land any agentbox.yaml change on the host (commit through the bind-mounted .git, or "agentbox download env" on the host).`;
2766
+ }
2178
2767
  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).`;
2179
2768
  }
2180
2769
  var WIZARD_AUTOLAUNCH_ENV = "AGENTBOX_WIZARD_AUTOLAUNCH";
2181
2770
  var WIZARD_ENV_FILES_ENV = "AGENTBOX_WIZARD_ENV_FILES";
2771
+ var WIZARD_RECREATE_ENV = "AGENTBOX_WIZARD_RECREATE";
2182
2772
  var WIZARD_ENV_SCAN_PATTERNS = DEFAULT_ENV_PATTERNS.filter((p) => p !== "agentbox.yaml");
2183
2773
  async function maybeRunSetupWizard(args) {
2184
2774
  if (process.env[WIZARD_AUTOLAUNCH_ENV] === "1") {
2185
2775
  if (args.command !== "claude") return { action: "proceed" };
2186
2776
  const envFiles = parseEnvFilesFromEnv(process.env[WIZARD_ENV_FILES_ENV]);
2187
2777
  const proj2 = await findProjectRoot(args.workspace);
2778
+ if (process.env[WIZARD_RECREATE_ENV] === "1") {
2779
+ return {
2780
+ action: "launch-with-prompt",
2781
+ initialPrompt: buildSetupInitialPrompt(proj2.root, proj2.hasAgentboxYaml),
2782
+ envFilesToImport: envFiles,
2783
+ discardCheckpoint: true
2784
+ };
2785
+ }
2188
2786
  return nonInteractiveOutcome(args, proj2, await checkpointStatus(args, proj2.root), envFiles);
2189
2787
  }
2190
2788
  const proj = await findProjectRoot(args.workspace);
@@ -2218,13 +2816,6 @@ async function maybeRunSetupWizard(args) {
2218
2816
  };
2219
2817
  }
2220
2818
  }
2221
- if (proj.hasAgentboxYaml) {
2222
- return { action: "proceed", discardCheckpoint: discardOnMissing(status, fromDefault) };
2223
- }
2224
- if (!rebuildBase && (status.state === "fresh" || status.state === "stale" && !fromDefault)) {
2225
- log8.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
2226
- return { action: "proceed" };
2227
- }
2228
2819
  let discardCheckpoint = rebuildBase;
2229
2820
  let recreateChosen = rebuildBase;
2230
2821
  if (!rebuildBase && status.state === "stale" && fromDefault) {
@@ -2237,7 +2828,15 @@ async function maybeRunSetupWizard(args) {
2237
2828
  }
2238
2829
  discardCheckpoint = true;
2239
2830
  recreateChosen = true;
2240
- } else if (!rebuildBase && status.state === "missing") {
2831
+ }
2832
+ if (proj.hasAgentboxYaml && !recreateChosen) {
2833
+ return { action: "proceed", discardCheckpoint: discardOnMissing(status, fromDefault) };
2834
+ }
2835
+ if (!rebuildBase && !recreateChosen && (status.state === "fresh" || status.state === "stale" && !fromDefault)) {
2836
+ log8.info(`starting from checkpoint "${args.checkpointRef}"; skipping agentbox.yaml setup`);
2837
+ return { action: "proceed" };
2838
+ }
2839
+ if (!rebuildBase && !recreateChosen && status.state === "missing") {
2241
2840
  discardCheckpoint = discardOnMissing(status, fromDefault) ?? false;
2242
2841
  }
2243
2842
  let envFilesToImport;
@@ -2274,15 +2873,17 @@ async function maybeRunSetupWizard(args) {
2274
2873
  action: "switch-to-claude",
2275
2874
  envFilesToImport,
2276
2875
  discardCheckpoint: discardCheckpoint || void 0,
2277
- rebuildBase: rebuildBase || void 0
2876
+ rebuildBase: rebuildBase || void 0,
2877
+ recreate: recreateChosen || void 0
2278
2878
  };
2279
2879
  }
2280
2880
  return {
2281
2881
  action: "launch-with-prompt",
2282
- initialPrompt: buildSetupInitialPrompt(proj.root),
2882
+ initialPrompt: buildSetupInitialPrompt(proj.root, proj.hasAgentboxYaml),
2283
2883
  envFilesToImport,
2284
2884
  discardCheckpoint: discardCheckpoint || void 0,
2285
- rebuildBase: rebuildBase || void 0
2885
+ rebuildBase: rebuildBase || void 0,
2886
+ recreate: recreateChosen || void 0
2286
2887
  };
2287
2888
  }
2288
2889
  function rebuildMinutesFor(provider) {
@@ -2292,9 +2893,9 @@ function rebuildMinutesFor(provider) {
2292
2893
  case "daytona":
2293
2894
  return "7";
2294
2895
  case "vercel":
2295
- return "25";
2896
+ return "5-10";
2296
2897
  case "hetzner":
2297
- return "35\u201350";
2898
+ return "7-10";
2298
2899
  default:
2299
2900
  return "1";
2300
2901
  }
@@ -2323,7 +2924,7 @@ function nonInteractiveOutcome(args, proj, status, envFilesToImport) {
2323
2924
  if (args.command === "claude") {
2324
2925
  return {
2325
2926
  action: "launch-with-prompt",
2326
- initialPrompt: buildSetupInitialPrompt(proj.root),
2927
+ initialPrompt: buildSetupInitialPrompt(proj.root, proj.hasAgentboxYaml),
2327
2928
  envFilesToImport,
2328
2929
  discardCheckpoint
2329
2930
  };
@@ -2419,7 +3020,7 @@ async function renderDocker(status) {
2419
3020
  }
2420
3021
  async function daytonaStatus() {
2421
3022
  try {
2422
- const mod = await import("./dist-3IMQNTTV.js");
3023
+ const mod = await import("./dist-SL2QSMBE.js");
2423
3024
  return await mod.getDaytonaStatus();
2424
3025
  } catch (err) {
2425
3026
  return {
@@ -2430,7 +3031,7 @@ async function daytonaStatus() {
2430
3031
  }
2431
3032
  async function e2bStatus() {
2432
3033
  try {
2433
- const mod = await import("./dist-34RKQ74M.js");
3034
+ const mod = await import("./dist-4IQFJJQI.js");
2434
3035
  const cred = mod.readE2bCredStatus();
2435
3036
  if (cred.auth === "none") {
2436
3037
  return { configured: false, reason: "not configured \u2014 run `agentbox e2b login`" };
@@ -2944,7 +3545,8 @@ var claudeCommand = new Command3("claude").description("Create a sandboxed box a
2944
3545
  agentArgs: claudeArgs,
2945
3546
  createOpts: { ...pickCreateOpts(opts), carry: carryForQueue },
2946
3547
  maxRunningOverride,
2947
- maxWorkingOverride
3548
+ maxWorkingOverride,
3549
+ openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
2948
3550
  });
2949
3551
  outro(
2950
3552
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -3572,13 +4174,13 @@ var CLOUD_BACKENDS = ["daytona", "hetzner", "vercel", "e2b"];
3572
4174
  async function cloudProviderFor(backend) {
3573
4175
  switch (backend) {
3574
4176
  case "daytona":
3575
- return (await import("./dist-3IMQNTTV.js")).daytonaProvider;
4177
+ return (await import("./dist-SL2QSMBE.js")).daytonaProvider;
3576
4178
  case "hetzner":
3577
- return (await import("./dist-J2IHD5T7.js")).hetznerProvider;
4179
+ return (await import("./dist-VHI5QOSQ.js")).hetznerProvider;
3578
4180
  case "vercel":
3579
- return (await import("./dist-57M6ZA7H.js")).vercelProvider;
4181
+ return (await import("./dist-XC47DSCR.js")).vercelProvider;
3580
4182
  case "e2b":
3581
- return (await import("./dist-34RKQ74M.js")).e2bProvider;
4183
+ return (await import("./dist-4IQFJJQI.js")).e2bProvider;
3582
4184
  }
3583
4185
  }
3584
4186
  var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
@@ -3948,9 +4550,9 @@ import { Command as Command5, InvalidArgumentError } from "commander";
3948
4550
  // src/ssh-config.ts
3949
4551
  import { promises as fs } from "fs";
3950
4552
  import { homedir as homedir9 } from "os";
3951
- import { join as join11 } from "path";
4553
+ import { join as join12 } from "path";
3952
4554
  function sshConfigPath() {
3953
- return join11(homedir9(), ".ssh", "config");
4555
+ return join12(homedir9(), ".ssh", "config");
3954
4556
  }
3955
4557
  function beginMarker(alias) {
3956
4558
  return `# BEGIN agentbox cloud box ${alias}`;
@@ -4001,7 +4603,7 @@ function buildBlock(opts) {
4001
4603
  }
4002
4604
  async function writeAgentboxSshAlias(opts) {
4003
4605
  const path = sshConfigPath();
4004
- await fs.mkdir(join11(homedir9(), ".ssh"), { recursive: true, mode: 448 });
4606
+ await fs.mkdir(join12(homedir9(), ".ssh"), { recursive: true, mode: 448 });
4005
4607
  const existing = await readConfig();
4006
4608
  const stripped = stripBlock(existing, opts.alias);
4007
4609
  const separator = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
@@ -4231,10 +4833,10 @@ async function launchOne(flavor, folderUri) {
4231
4833
  return { code: fallback, flavor, via: "open" };
4232
4834
  }
4233
4835
  function spawnCommand(cmd, args) {
4234
- return new Promise((resolve4) => {
4836
+ return new Promise((resolve5) => {
4235
4837
  const child = spawn(cmd, args, { stdio: "ignore" });
4236
- child.once("error", () => resolve4(127));
4237
- child.once("exit", (code) => resolve4(code ?? -1));
4838
+ child.once("error", () => resolve5(127));
4839
+ child.once("exit", (code) => resolve5(code ?? -1));
4238
4840
  });
4239
4841
  }
4240
4842
  async function fetchServiceNamesDocker(container) {
@@ -4253,7 +4855,7 @@ async function fetchServiceNamesDocker(container) {
4253
4855
  // src/commands/codex.ts
4254
4856
  import { access } from "fs/promises";
4255
4857
  import { homedir as homedir10 } from "os";
4256
- import { join as join12 } from "path";
4858
+ import { join as join13 } from "path";
4257
4859
  import { confirm as confirm5, intro as intro3, isCancel as isCancel6, log as log13, outro as outro2, spinner as spinner5 } from "@clack/prompts";
4258
4860
  import { Command as Command6 } from "commander";
4259
4861
  function reattachRef2(r) {
@@ -4350,7 +4952,7 @@ async function maybeRunCodexLogin(args) {
4350
4952
  }
4351
4953
  async function cloudCodexCredAvailable(env = process.env) {
4352
4954
  if ((env["OPENAI_API_KEY"] ?? "").length > 0) return true;
4353
- for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join12(homedir10(), ".codex", "auth.json")]) {
4955
+ for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join13(homedir10(), ".codex", "auth.json")]) {
4354
4956
  try {
4355
4957
  await access(p);
4356
4958
  return true;
@@ -4518,7 +5120,8 @@ var codexCommand = new Command6("codex").description("Create a sandboxed box and
4518
5120
  agentArgs: codexArgs,
4519
5121
  createOpts: { ...pickCodexCreateOpts(opts), carry: carryForQueue },
4520
5122
  maxRunningOverride,
4521
- maxWorkingOverride
5123
+ maxWorkingOverride,
5124
+ openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
4522
5125
  });
4523
5126
  outro2(
4524
5127
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -5008,7 +5611,7 @@ codexCommand.addCommand(codexLoginCommand);
5008
5611
  // src/commands/opencode.ts
5009
5612
  import { access as access2 } from "fs/promises";
5010
5613
  import { homedir as homedir11 } from "os";
5011
- import { join as join13 } from "path";
5614
+ import { join as join14 } from "path";
5012
5615
  import { confirm as confirm6, intro as intro4, isCancel as isCancel7, log as log14, outro as outro3, spinner as spinner6 } from "@clack/prompts";
5013
5616
  import { Command as Command7 } from "commander";
5014
5617
  function reattachRef3(r) {
@@ -5107,7 +5710,7 @@ async function cloudOpencodeCredAvailable(env = process.env) {
5107
5710
  for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
5108
5711
  if ((env[k] ?? "").length > 0) return true;
5109
5712
  }
5110
- for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join13(homedir11(), ".local", "share", "opencode", "auth.json")]) {
5713
+ for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join14(homedir11(), ".local", "share", "opencode", "auth.json")]) {
5111
5714
  try {
5112
5715
  await access2(p);
5113
5716
  return true;
@@ -5257,7 +5860,8 @@ var opencodeCommand = new Command7("opencode").description("Create a sandboxed b
5257
5860
  agentArgs: opencodeArgs,
5258
5861
  createOpts: { ...pickOpencodeCreateOpts(opts), carry: carryForQueue },
5259
5862
  maxRunningOverride,
5260
- maxWorkingOverride
5863
+ maxWorkingOverride,
5864
+ openTerminal: captureOpenTerminalContext(cfg.effective.queue.openIn)
5261
5865
  });
5262
5866
  outro3(
5263
5867
  `job ${result.job.id} queued (${String(result.runningCount)}/${String(result.maxConcurrent)} running); log: ${result.job.logPath}`
@@ -5653,18 +6257,19 @@ function fail(message) {
5653
6257
  `);
5654
6258
  process.exit(1);
5655
6259
  }
6260
+ function walkKey(obj, key) {
6261
+ let cur = obj;
6262
+ for (const seg of key.split(".")) {
6263
+ if (cur === void 0 || cur === null || typeof cur !== "object") return void 0;
6264
+ cur = cur[seg];
6265
+ }
6266
+ return cur;
6267
+ }
5656
6268
  function leafValue(loaded, key) {
5657
- const idx = key.indexOf(".");
5658
- const branch = key.slice(0, idx);
5659
- const leaf = key.slice(idx + 1);
5660
- return loaded.effective[branch]?.[leaf];
6269
+ return walkKey(loaded.effective, key);
5661
6270
  }
5662
6271
  function rawLeafFromValues(values, key) {
5663
- if (!values) return void 0;
5664
- const idx = key.indexOf(".");
5665
- const b = values[key.slice(0, idx)];
5666
- if (!b || typeof b !== "object") return void 0;
5667
- return b[key.slice(idx + 1)];
6272
+ return walkKey(values, key);
5668
6273
  }
5669
6274
  function describeSource(source, loaded) {
5670
6275
  switch (source) {
@@ -5756,11 +6361,19 @@ var setCommand = new Command8("set").description("Set a config key in the global
5756
6361
  } else {
5757
6362
  process.stdout.write(`${key} = ${fmtValue(r.coerced)} (wrote ${r.path})
5758
6363
  `);
6364
+ warnOnSet(key, r.coerced);
5759
6365
  }
5760
6366
  } catch (err) {
5761
6367
  handleError(err);
5762
6368
  }
5763
6369
  });
6370
+ function warnOnSet(key, value) {
6371
+ if (key === "queue.openIn" && value !== "none") {
6372
+ process.stderr.write(
6373
+ "note: queue.openIn only opens a terminal when you submit the `-i` job from inside tmux, cmux, or iTerm2.\n cmux: the box is opened by the relay's queue worker, a cmux-external process, so cmux's default\n `socketControlMode: cmuxOnly` blocks it. Set `socketControlMode` to `automation` (or `password`)\n in ~/.config/cmux/cmux.json and run `cmux reload-config` for cmux opens to work.\n"
6374
+ );
6375
+ }
6376
+ }
5764
6377
  var unsetCommand = new Command8("unset").description("Remove a config key from the global or per-project file (default: --project)").argument("<key>", "dot-path key (e.g. box.hostSnapshot)").option("--global", "edit ~/.agentbox/config.yaml").option("--project", "edit ~/.agentbox/projects/<hash>/config.yaml (default)").action(async (key, opts) => {
5765
6378
  const scope = resolveWriteScope(opts);
5766
6379
  try {
@@ -5898,8 +6511,51 @@ function handleError(err) {
5898
6511
  var configCommand = new Command8("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);
5899
6512
 
5900
6513
  // src/commands/cp.ts
6514
+ import { resolve as resolve3 } from "path";
5901
6515
  import { log as log15 } from "@clack/prompts";
5902
6516
  import { Command as Command9 } from "commander";
6517
+ function collectExclude(val, acc) {
6518
+ acc.push(val);
6519
+ return acc;
6520
+ }
6521
+ async function guardUploadSize(hostSrc, boxDst, opts) {
6522
+ const tokens = effectiveExcludes(opts.exclude, opts.defaultExcludes);
6523
+ const tarPatterns = toTarExcludes(tokens);
6524
+ const cfg = await loadEffectiveConfig(process.cwd());
6525
+ const maxBytes = cfg.effective.box.cpMaxBytes;
6526
+ const absSrc = resolve3(hostSrc);
6527
+ const measured = await measureCopy(absSrc, tokens);
6528
+ if (opts.yes || measured.totalBytes <= maxBytes) {
6529
+ return tarPatterns;
6530
+ }
6531
+ const dropped = measured.isDir && opts.defaultExcludes ? ` after default excludes (${effectiveExcludes([], true).join(", ")})` : "";
6532
+ const lines = [
6533
+ `${hostSrc} is ${fmtBytes(measured.totalBytes)}${dropped}, over the ${fmtBytes(maxBytes)} per-copy limit.`
6534
+ ];
6535
+ if (measured.isDir) {
6536
+ lines.push(
6537
+ "Biggest remaining folders/subfolders:",
6538
+ ...measured.treeLines,
6539
+ "Each copy must be under the limit. To proceed, EITHER:",
6540
+ " - copy the heavy folders one at a time, e.g.:"
6541
+ );
6542
+ for (const child of measured.topChildren.slice(0, 3)) {
6543
+ lines.push(` agentbox cp ${hostSrc}/${child.path} ${boxDst}`);
6544
+ }
6545
+ lines.push(
6546
+ " - or drop what you do not need:",
6547
+ ` agentbox cp ${hostSrc} ${boxDst} --exclude=<dir>`,
6548
+ " - or copy the whole thing anyway:",
6549
+ ` agentbox cp ${hostSrc} ${boxDst} --yes`
6550
+ );
6551
+ } else {
6552
+ lines.push(
6553
+ "A single file cannot be split or trimmed. To copy it anyway:",
6554
+ ` agentbox cp ${hostSrc} ${boxDst} --yes`
6555
+ );
6556
+ }
6557
+ throw new Error(lines.join("\n"));
6558
+ }
5903
6559
  function parseBoxArg(arg) {
5904
6560
  const idx = arg.indexOf(":");
5905
6561
  if (idx === -1) return null;
@@ -5944,7 +6600,15 @@ function parseArgs(src, dst) {
5944
6600
  var cpCommand = new Command9("cp").description("Copy files between host and box (like `docker cp`; direction picked by `name:` prefix)").argument("<src>", "`box:/path` (download) or host path (upload)").argument(
5945
6601
  "[dst]",
5946
6602
  "`box:/path` (upload) or host path (download); defaults to cwd when downloading"
5947
- ).addHelpText(
6603
+ ).option(
6604
+ "--exclude <pattern>",
6605
+ 'exclude paths matching <pattern> (repeatable; tar glob like "*/foo" or a bare dir name)',
6606
+ collectExclude,
6607
+ []
6608
+ ).option(
6609
+ "--no-default-excludes",
6610
+ `keep the heavy dirs cp drops by default (${effectiveExcludes([], true).join(", ")})`
6611
+ ).option("-y, --yes", "copy even if the source is over the box.cpMaxBytes size limit").addHelpText(
5948
6612
  "after",
5949
6613
  [
5950
6614
  "",
@@ -5952,27 +6616,30 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
5952
6616
  " agentbox cp mybox:/etc/foo ./foo # download (host path optional)",
5953
6617
  " agentbox cp mybox:/workspace/.env # download into cwd",
5954
6618
  " agentbox cp ./local.txt mybox:/workspace/ # upload (host path required)",
5955
- " agentbox cp ./dir mybox:/workspace/ # upload directory (recursive)"
6619
+ " agentbox cp ./dir mybox:/workspace/ # upload directory (recursive)",
6620
+ ' agentbox cp ./dir mybox:/workspace/ --exclude=.git --exclude="*/cache"'
5956
6621
  ].join("\n")
5957
- ).action(async (src, dst) => {
6622
+ ).action(async (src, dst, opts) => {
5958
6623
  try {
5959
6624
  const parsed = parseArgs(src, dst);
5960
6625
  const box = await resolveBoxOrExit(parsed.boxRef);
5961
6626
  const isCloud = (box.provider ?? "docker") !== "docker";
6627
+ const tarPatterns = parsed.direction === "upload" ? await guardUploadSize(parsed.hostPath, `${parsed.boxRef}:${parsed.boxPath}`, opts) : toTarExcludes(effectiveExcludes(opts.exclude, opts.defaultExcludes));
5962
6628
  if (isCloud) {
5963
6629
  const provider = await providerForBox(box);
5964
6630
  if (!provider.uploadPath || !provider.downloadPath) {
5965
6631
  throw new Error(`provider '${provider.name}' does not support cp`);
5966
6632
  }
5967
6633
  if (parsed.direction === "upload") {
5968
- const result = await provider.uploadPath(box, parsed.hostPath, parsed.boxPath);
6634
+ const result = await provider.uploadPath(box, parsed.hostPath, parsed.boxPath, tarPatterns);
5969
6635
  process.stdout.write(`copied to ${box.name}:${result.finalPath}
5970
6636
  `);
5971
6637
  } else {
5972
6638
  const result = await provider.downloadPath(
5973
6639
  box,
5974
6640
  parsed.boxPath,
5975
- parsed.hostPath ?? process.cwd()
6641
+ parsed.hostPath ?? process.cwd(),
6642
+ tarPatterns
5976
6643
  );
5977
6644
  process.stdout.write(`copied to ${result.finalPath}
5978
6645
  `);
@@ -5990,7 +6657,7 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
5990
6657
  throw new Error(`box ${box.name} has no container; was it destroyed?`);
5991
6658
  }
5992
6659
  if (parsed.direction === "upload") {
5993
- const result = await uploadToBox(box, parsed.hostPath, parsed.boxPath);
6660
+ const result = await uploadToBox(box, parsed.hostPath, parsed.boxPath, tarPatterns);
5994
6661
  if (result.warn) {
5995
6662
  log15.warn(`copied to ${box.name}:${result.finalPath}, but ${result.warn}`);
5996
6663
  } else {
@@ -5998,7 +6665,12 @@ var cpCommand = new Command9("cp").description("Copy files between host and box
5998
6665
  `);
5999
6666
  }
6000
6667
  } else {
6001
- const result = await downloadFromBox(box, parsed.boxPath, parsed.hostPath ?? process.cwd());
6668
+ const result = await downloadFromBox(
6669
+ box,
6670
+ parsed.boxPath,
6671
+ parsed.hostPath ?? process.cwd(),
6672
+ tarPatterns
6673
+ );
6002
6674
  process.stdout.write(`copied to ${result.finalPath}
6003
6675
  `);
6004
6676
  }
@@ -6189,12 +6861,14 @@ var createCommand = new Command10("create").description(
6189
6861
  const effectiveCheckpointRef = wiz.discardCheckpoint ? void 0 : checkpointRef;
6190
6862
  if (wiz.action === "switch-to-claude" && isDocker) {
6191
6863
  process.env[WIZARD_AUTOLAUNCH_ENV] = "1";
6864
+ if (wiz.recreate) process.env[WIZARD_RECREATE_ENV] = "1";
6192
6865
  const serialized = serializeEnvFilesForEnv(wiz.envFilesToImport);
6193
6866
  if (serialized !== void 0) process.env[WIZARD_ENV_FILES_ENV] = serialized;
6194
6867
  try {
6195
6868
  await claudeCommand.parseAsync(passthroughFlags(opts), { from: "user" });
6196
6869
  } finally {
6197
6870
  delete process.env[WIZARD_AUTOLAUNCH_ENV];
6871
+ delete process.env[WIZARD_RECREATE_ENV];
6198
6872
  delete process.env[WIZARD_ENV_FILES_ENV];
6199
6873
  }
6200
6874
  return;
@@ -6320,7 +6994,7 @@ var createCommand = new Command10("create").description(
6320
6994
  }
6321
6995
  outro4("done");
6322
6996
  if (attachClaudeAfter) {
6323
- const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-GUBB5RH2.js");
6997
+ const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-5KJWOASL.js");
6324
6998
  await cloudAgentAttach2({
6325
6999
  box: result.record,
6326
7000
  binary: "claude",
@@ -6953,8 +7627,8 @@ var Compositor = class {
6953
7627
  this.drawChrome();
6954
7628
  this.scheduleRender();
6955
7629
  this.pollTimer = setInterval(() => void this.poll(), POLL_MS);
6956
- await new Promise((resolve4) => {
6957
- this.resolveDone = resolve4;
7630
+ await new Promise((resolve5) => {
7631
+ this.resolveDone = resolve5;
6958
7632
  });
6959
7633
  }
6960
7634
  async refreshBoxes() {
@@ -9332,114 +10006,6 @@ function resolveToken(raw) {
9332
10006
  return `<${raw}>`;
9333
10007
  }
9334
10008
 
9335
- // src/lib/drive/tmux.ts
9336
- var TMUX_USER = "vscode";
9337
- async function captureSession(provider, box, session, opts = {}) {
9338
- const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
9339
- if (opts.rows) {
9340
- argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
9341
- }
9342
- const res = await provider.exec(box, argv2, { user: TMUX_USER });
9343
- if (res.exitCode !== 0) {
9344
- throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
9345
- }
9346
- return res.stdout.replace(/\n$/, "");
9347
- }
9348
- async function paneInfo(provider, box, session) {
9349
- const fmt = "#{pane_width},#{pane_height},#{cursor_x},#{cursor_y}";
9350
- const res = await provider.exec(box, ["tmux", "display-message", "-p", "-t", session, fmt], {
9351
- user: TMUX_USER
9352
- });
9353
- if (res.exitCode !== 0) {
9354
- throw new Error(failure("display-message", session, res.stderr || res.stdout));
9355
- }
9356
- const m = /^(\d+),(\d+),(\d+),(\d+)/.exec(res.stdout.trim());
9357
- if (!m) throw new Error(`tmux display-message returned unexpected output: ${res.stdout}`);
9358
- return {
9359
- cols: Number(m[1]),
9360
- rows: Number(m[2]),
9361
- cursor: { x: Number(m[3]), y: Number(m[4]) }
9362
- };
9363
- }
9364
- async function sendLiteral(provider, box, session, literal) {
9365
- if (literal.length === 0) return;
9366
- const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, "-l", "--", literal], {
9367
- user: TMUX_USER
9368
- });
9369
- if (res.exitCode !== 0) {
9370
- throw new Error(failure("send-keys -l", session, res.stderr || res.stdout));
9371
- }
9372
- }
9373
- async function sendKey(provider, box, session, key) {
9374
- const res = await provider.exec(box, ["tmux", "send-keys", "-t", session, key], {
9375
- user: TMUX_USER
9376
- });
9377
- if (res.exitCode !== 0) {
9378
- throw new Error(failure("send-keys", session, res.stderr || res.stdout));
9379
- }
9380
- }
9381
- async function resizeWindow(provider, box, session, cols, rows) {
9382
- const res = await provider.exec(
9383
- box,
9384
- ["tmux", "resize-window", "-t", session, "-x", String(cols), "-y", String(rows)],
9385
- { user: TMUX_USER }
9386
- );
9387
- if (res.exitCode !== 0) {
9388
- throw new Error(failure("resize-window", session, res.stderr || res.stdout));
9389
- }
9390
- }
9391
- async function listSessions(provider, box) {
9392
- const res = await provider.exec(
9393
- box,
9394
- ["tmux", "list-sessions", "-F", "#{session_name}"],
9395
- { user: TMUX_USER }
9396
- );
9397
- if (res.exitCode !== 0) return [];
9398
- return res.stdout.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
9399
- }
9400
- function failure(op, session, detail) {
9401
- const tail = detail.trim();
9402
- return `tmux ${op} failed for session '${session}'${tail ? `: ${tail}` : ""}`;
9403
- }
9404
-
9405
- // src/lib/drive/session.ts
9406
- var AGENT_SESSION_PRIORITY = ["claude", "codex", "opencode"];
9407
- async function resolveDriveSession(provider, box, explicit) {
9408
- if (await provider.probeState(box) === "paused") {
9409
- process.stderr.write(`drive: box ${box.name} is paused; unpausing
9410
- `);
9411
- await provider.resume(box);
9412
- }
9413
- const sessions = await listSessions(provider, box);
9414
- if (explicit !== void 0 && explicit !== "") {
9415
- if (!sessions.includes(explicit)) {
9416
- throw new SessionNotFoundError(explicit, sessions);
9417
- }
9418
- return { name: explicit, available: sessions };
9419
- }
9420
- for (const candidate of AGENT_SESSION_PRIORITY) {
9421
- if (sessions.includes(candidate)) {
9422
- return { name: candidate, available: sessions };
9423
- }
9424
- }
9425
- if (sessions.length === 1 && sessions[0]) {
9426
- return { name: sessions[0], available: sessions };
9427
- }
9428
- throw new SessionNotFoundError(void 0, sessions);
9429
- }
9430
- var SessionNotFoundError = class extends Error {
9431
- wanted;
9432
- available;
9433
- constructor(wanted, available) {
9434
- const head = wanted ? `no tmux session '${wanted}' in this box` : "no agent tmux session running in this box";
9435
- const tail = available.length ? ` (running: ${available.join(", ")})` : " (tmux server not running or no sessions)";
9436
- super(head + tail);
9437
- this.name = "SessionNotFoundError";
9438
- this.wanted = wanted;
9439
- this.available = available;
9440
- }
9441
- };
9442
-
9443
10009
  // src/commands/drive.ts
9444
10010
  var PROMPT_ENTER_DELAY_MS = 200;
9445
10011
  var POLL_INTERVAL_MS2 = 250;
@@ -9507,7 +10073,7 @@ var drivePromptCommand = new Command24("prompt").description('Type text into the
9507
10073
  const session = await resolveDriveSession(provider, box, opts.session);
9508
10074
  const delay = opts.delay !== void 0 ? parsePositiveInt2(opts.delay, "--delay") : PROMPT_ENTER_DELAY_MS;
9509
10075
  await sendLiteral(provider, box, session.name, text);
9510
- if (delay > 0) await sleep2(delay);
10076
+ if (delay > 0) await sleep3(delay);
9511
10077
  await sendKey(provider, box, session.name, "Enter");
9512
10078
  } catch (err) {
9513
10079
  handleDriveError(err);
@@ -9533,7 +10099,7 @@ var driveWaitCommand = new Command24("wait").description("Block until --text app
9533
10099
  }
9534
10100
  return;
9535
10101
  }
9536
- await sleep2(POLL_INTERVAL_MS2);
10102
+ await sleep3(POLL_INTERVAL_MS2);
9537
10103
  }
9538
10104
  const elapsedMs = Date.now() - start;
9539
10105
  if (opts.json === true) {
@@ -9593,7 +10159,7 @@ function parsePositiveInt2(raw, label) {
9593
10159
  }
9594
10160
  return n;
9595
10161
  }
9596
- function sleep2(ms) {
10162
+ function sleep3(ms) {
9597
10163
  return new Promise((r) => setTimeout(r, ms));
9598
10164
  }
9599
10165
 
@@ -9602,7 +10168,7 @@ import { log as log30 } from "@clack/prompts";
9602
10168
  import { Command as Command25 } from "commander";
9603
10169
  import { existsSync as existsSync5, readdirSync, statSync } from "fs";
9604
10170
  import { homedir as homedir12 } from "os";
9605
- import { join as join14 } from "path";
10171
+ import { join as join15 } from "path";
9606
10172
  var FORK_AGENTS = ["claude", "codex", "opencode"];
9607
10173
  var AGENT_COMMAND = {
9608
10174
  claude: claudeCommand,
@@ -9622,12 +10188,12 @@ function resolveSessionArgs(agent, opts) {
9622
10188
  }
9623
10189
  if (opts.session) return ["--resume", opts.session];
9624
10190
  if (agent === "codex") return ["--continue"];
9625
- const dir = join14(homedir12(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
10191
+ const dir = join15(homedir12(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
9626
10192
  if (!existsSync5(dir)) return ["--continue"];
9627
10193
  const now = Date.now();
9628
10194
  const recent = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
9629
10195
  try {
9630
- return statSync(join14(dir, f)).mtimeMs;
10196
+ return statSync(join15(dir, f)).mtimeMs;
9631
10197
  } catch {
9632
10198
  return 0;
9633
10199
  }
@@ -9738,13 +10304,13 @@ import {
9738
10304
  writeFileSync as writeFileSync5
9739
10305
  } from "fs";
9740
10306
  import { homedir as homedir16 } from "os";
9741
- import { dirname as dirname3, join as join18, resolve as resolve3, sep } from "path";
10307
+ import { dirname as dirname3, join as join19, resolve as resolve4, sep } from "path";
9742
10308
  import { fileURLToPath } from "url";
9743
10309
 
9744
10310
  // src/lib/doctor-checks.ts
9745
10311
  import { accessSync, constants as fsConstants, mkdirSync as mkdirSync3 } from "fs";
9746
10312
  import { homedir as homedir13 } from "os";
9747
- import { join as join15 } from "path";
10313
+ import { join as join16 } from "path";
9748
10314
  import { execa as execa3 } from "execa";
9749
10315
  var ALL_PROVIDERS = ["docker", "daytona", "hetzner", "vercel", "e2b"];
9750
10316
  var NODE_MIN_MAJOR = 20;
@@ -9764,12 +10330,12 @@ function parseNodeMajorMinor(v) {
9764
10330
  if (!m) return [0, 0];
9765
10331
  return [Number(m[1]), Number(m[2])];
9766
10332
  }
9767
- function firstLine(s) {
10333
+ function firstLine2(s) {
9768
10334
  const i = s.indexOf("\n");
9769
10335
  return i === -1 ? s : s.slice(0, i);
9770
10336
  }
9771
10337
  function errSummary(err) {
9772
- return err instanceof Error ? firstLine(err.message) : String(err);
10338
+ return err instanceof Error ? firstLine2(err.message) : String(err);
9773
10339
  }
9774
10340
  function checkNode() {
9775
10341
  const v = process.versions.node;
@@ -9792,7 +10358,7 @@ function checkPlatform() {
9792
10358
  };
9793
10359
  }
9794
10360
  function checkAgentboxHome() {
9795
- const dir = join15(homedir13(), ".agentbox");
10361
+ const dir = join16(homedir13(), ".agentbox");
9796
10362
  try {
9797
10363
  mkdirSync3(dir, { recursive: true });
9798
10364
  accessSync(dir, fsConstants.W_OK);
@@ -9864,7 +10430,7 @@ async function dockerChecks() {
9864
10430
  ];
9865
10431
  }
9866
10432
  const daemonRes = { label: "docker daemon", status: "ok", detail: "reachable" };
9867
- const mod = await import("./dist-4DPOL5A7.js");
10433
+ const mod = await import("./dist-7YB7BMNG.js");
9868
10434
  let imgRes;
9869
10435
  try {
9870
10436
  const img = await mod.imageInfo(mod.DEFAULT_BOX_IMAGE);
@@ -9895,7 +10461,7 @@ async function dockerChecks() {
9895
10461
  }
9896
10462
  async function daytonaChecks() {
9897
10463
  try {
9898
- const mod = await import("./dist-3IMQNTTV.js");
10464
+ const mod = await import("./dist-SL2QSMBE.js");
9899
10465
  const status = await mod.getDaytonaStatus();
9900
10466
  if (!status.configured) {
9901
10467
  return [
@@ -9931,7 +10497,7 @@ async function daytonaChecks() {
9931
10497
  }
9932
10498
  async function hetznerChecks() {
9933
10499
  try {
9934
- const mod = await import("./dist-J2IHD5T7.js");
10500
+ const mod = await import("./dist-VHI5QOSQ.js");
9935
10501
  const cred = mod.readHetznerCredStatus();
9936
10502
  const credRes = cred.source === "none" ? {
9937
10503
  label: "credentials",
@@ -9963,7 +10529,7 @@ async function hetznerChecks() {
9963
10529
  }
9964
10530
  async function vercelChecks() {
9965
10531
  try {
9966
- const mod = await import("./dist-57M6ZA7H.js");
10532
+ const mod = await import("./dist-XC47DSCR.js");
9967
10533
  const cred = mod.readVercelCredStatus();
9968
10534
  const credRes = cred.auth === "none" ? {
9969
10535
  label: "credentials",
@@ -9999,7 +10565,7 @@ async function vercelChecks() {
9999
10565
  }
10000
10566
  async function e2bChecks() {
10001
10567
  try {
10002
- const mod = await import("./dist-34RKQ74M.js");
10568
+ const mod = await import("./dist-4IQFJJQI.js");
10003
10569
  const cred = mod.readE2bCredStatus();
10004
10570
  const credRes = cred.auth === "none" ? {
10005
10571
  label: "credentials",
@@ -10033,6 +10599,103 @@ async function e2bChecks() {
10033
10599
  ];
10034
10600
  }
10035
10601
  }
10602
+ var INTEGRATION_PROBE_TIMEOUT_MS = 1e4;
10603
+ async function probeIntegrationBin(bin, args) {
10604
+ try {
10605
+ const r = await execa3(bin, [...args], {
10606
+ reject: false,
10607
+ timeout: INTEGRATION_PROBE_TIMEOUT_MS,
10608
+ stdin: "ignore"
10609
+ });
10610
+ const code = r.code;
10611
+ if (code === "ENOENT") {
10612
+ return { exitCode: 127, stdout: "", stderr: r.stderr ?? "", missing: true };
10613
+ }
10614
+ if (r.timedOut) {
10615
+ return {
10616
+ exitCode: 124,
10617
+ stdout: "",
10618
+ stderr: `timed out after ${String(INTEGRATION_PROBE_TIMEOUT_MS)}ms`,
10619
+ missing: false
10620
+ };
10621
+ }
10622
+ return {
10623
+ exitCode: r.exitCode ?? 1,
10624
+ stdout: typeof r.stdout === "string" ? r.stdout : "",
10625
+ stderr: typeof r.stderr === "string" ? r.stderr : "",
10626
+ missing: false
10627
+ };
10628
+ } catch (err) {
10629
+ const code = err.code;
10630
+ return {
10631
+ exitCode: code === "ENOENT" ? 127 : 1,
10632
+ stdout: "",
10633
+ stderr: errSummary(err),
10634
+ missing: code === "ENOENT"
10635
+ };
10636
+ }
10637
+ }
10638
+ async function integrationsChecks(loader = loadEffectiveConfig) {
10639
+ let cfg;
10640
+ try {
10641
+ cfg = await loader(process.cwd());
10642
+ } catch {
10643
+ cfg = { effective: {} };
10644
+ }
10645
+ return Promise.all(
10646
+ ALL_CONNECTORS.map((connector) => checkOneIntegration(connector, cfg.effective.integrations))
10647
+ );
10648
+ }
10649
+ async function checkOneIntegration(connector, integrations) {
10650
+ const svc = connector.service;
10651
+ const enabled = integrations?.[svc]?.enabled === true;
10652
+ if (!enabled) {
10653
+ return {
10654
+ label: svc,
10655
+ status: "info",
10656
+ detail: "disabled",
10657
+ hint: `enable with \`agentbox config set --project integrations.${svc}.enabled true\``
10658
+ };
10659
+ }
10660
+ const version = await probeIntegrationBin(connector.hostBin, connector.detect.versionArgs);
10661
+ if (version.missing || version.exitCode === 127) {
10662
+ return {
10663
+ label: svc,
10664
+ status: "warn",
10665
+ detail: `${connector.hostBin} not installed`,
10666
+ hint: connector.detect.installHint ?? `install the ${svc} CLI (\`${connector.hostBin}\`) on the host`
10667
+ };
10668
+ }
10669
+ if (version.exitCode !== 0) {
10670
+ const tail = firstLine2((version.stderr || version.stdout).trim());
10671
+ return {
10672
+ label: svc,
10673
+ status: "warn",
10674
+ detail: `${connector.hostBin} ${connector.detect.versionArgs.join(" ")} failed${tail ? `: ${tail}` : ""}`
10675
+ };
10676
+ }
10677
+ const versionLine = firstLine2((version.stdout || version.stderr).trim()) || connector.hostBin;
10678
+ if (!connector.detect.authArgs || connector.detect.authArgs.length === 0) {
10679
+ return { label: svc, status: "ok", detail: versionLine };
10680
+ }
10681
+ const auth = await probeIntegrationBin(connector.hostBin, connector.detect.authArgs);
10682
+ if (auth.exitCode === 124) {
10683
+ return {
10684
+ label: svc,
10685
+ status: "warn",
10686
+ detail: `auth check timed out after ${String(INTEGRATION_PROBE_TIMEOUT_MS / 1e3)}s`
10687
+ };
10688
+ }
10689
+ if (auth.exitCode !== 0) {
10690
+ return {
10691
+ label: svc,
10692
+ status: "warn",
10693
+ detail: "not logged in",
10694
+ hint: connector.detect.loginHint ?? `run \`${connector.hostBin} login\``
10695
+ };
10696
+ }
10697
+ return { label: svc, status: "ok", detail: `${versionLine} \xB7 authed` };
10698
+ }
10036
10699
  async function runProviderChecks(name) {
10037
10700
  let results;
10038
10701
  switch (name) {
@@ -10057,7 +10720,8 @@ async function runProviderChecks(name) {
10057
10720
  async function runAllChecks() {
10058
10721
  const sys = { title: "system", results: await runSystemChecks() };
10059
10722
  const providerGroups = await Promise.all(ALL_PROVIDERS.map((n) => runProviderChecks(n)));
10060
- return [sys, ...providerGroups];
10723
+ const integrations = { title: "integrations", results: await integrationsChecks() };
10724
+ return [sys, ...providerGroups, integrations];
10061
10725
  }
10062
10726
  function worstInResults(results) {
10063
10727
  let worst = "ok";
@@ -10083,6 +10747,12 @@ function summaryToken(group) {
10083
10747
  if (worst === "warn") return "system warn";
10084
10748
  return "system ok";
10085
10749
  }
10750
+ if (group.title === "integrations") {
10751
+ if (worst === "fail") return "integrations FAIL";
10752
+ if (worst === "warn") return "integrations check";
10753
+ const anyEnabled = group.results.some((r) => r.status === "ok");
10754
+ return anyEnabled ? "integrations ready" : "integrations off";
10755
+ }
10086
10756
  if (worst === "fail") return `${group.title} FAIL`;
10087
10757
  if (worst === "warn") {
10088
10758
  const cred = group.results.find((r) => r.label === "credentials");
@@ -10094,12 +10764,13 @@ function summaryToken(group) {
10094
10764
  var C_GREEN = "\x1B[32m";
10095
10765
  var C_YELLOW = "\x1B[33m";
10096
10766
  var C_RED = "\x1B[31m";
10767
+ var C_DIM = "\x1B[2m";
10097
10768
  var C_RESET = "\x1B[0m";
10098
10769
  var COLOR = !process.env.NO_COLOR;
10099
10770
  function statusMarker(s) {
10100
- const glyph = s === "ok" ? "\u2713" : s === "warn" ? "\u26A0" : "\u2717";
10771
+ const glyph = s === "ok" ? "\u2713" : s === "info" ? "\xB7" : s === "warn" ? "\u26A0" : "\u2717";
10101
10772
  if (!COLOR) return glyph;
10102
- const color = s === "ok" ? C_GREEN : s === "warn" ? C_YELLOW : C_RED;
10773
+ const color = s === "ok" ? C_GREEN : s === "info" ? C_DIM : s === "warn" ? C_YELLOW : C_RED;
10103
10774
  return `${color}${glyph}${C_RESET}`;
10104
10775
  }
10105
10776
  function formatCompact(groups) {
@@ -10110,6 +10781,7 @@ function pad3(s, width) {
10110
10781
  }
10111
10782
  function statusBadge(s) {
10112
10783
  if (s === "ok") return "[ ok ]";
10784
+ if (s === "info") return "[info]";
10113
10785
  if (s === "warn") return "[warn]";
10114
10786
  return "[FAIL]";
10115
10787
  }
@@ -10130,10 +10802,10 @@ function formatDetailed(groups) {
10130
10802
  // src/lib/first-run.ts
10131
10803
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
10132
10804
  import { homedir as homedir14 } from "os";
10133
- import { dirname, join as join16 } from "path";
10805
+ import { dirname, join as join17 } from "path";
10134
10806
  var MARKER_VERSION = 1;
10135
10807
  function setupMarkerPath() {
10136
- return join16(homedir14(), ".agentbox", "setup-complete.json");
10808
+ return join17(homedir14(), ".agentbox", "setup-complete.json");
10137
10809
  }
10138
10810
  function isFirstRun() {
10139
10811
  return !existsSync6(setupMarkerPath());
@@ -10154,12 +10826,12 @@ import { intro as intro6, log as log31, note as note2, outro as outro5 } from "@
10154
10826
  import { Command as Command26 } from "commander";
10155
10827
  import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
10156
10828
  import { homedir as homedir15 } from "os";
10157
- import { dirname as dirname2, join as join17 } from "path";
10829
+ import { dirname as dirname2, join as join18 } from "path";
10158
10830
  var CONTROL_ID = "agentbox";
10159
10831
  function cmuxDockPath(env = process.env) {
10160
10832
  const xdg = env["XDG_CONFIG_HOME"];
10161
- const base = xdg && xdg.length > 0 ? xdg : join17(homedir15(), ".config");
10162
- return join17(base, "cmux", "dock.json");
10833
+ const base = xdg && xdg.length > 0 ? xdg : join18(homedir15(), ".config");
10834
+ return join18(base, "cmux", "dock.json");
10163
10835
  }
10164
10836
  function upsertAgentboxControl(doc, opts) {
10165
10837
  const controls = Array.isArray(doc.controls) ? doc.controls : [];
@@ -10251,7 +10923,7 @@ var SYNC_END2 = "\x1B[?2026l";
10251
10923
  var HIDE_CURSOR = "\x1B[?25l";
10252
10924
  var SHOW_CURSOR = "\x1B[?25h";
10253
10925
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
10254
- var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
10926
+ var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
10255
10927
  function shineColor(dist) {
10256
10928
  const d = Math.abs(dist);
10257
10929
  if (d === 0) return 231;
@@ -10301,11 +10973,11 @@ ${HIDE_CURSOR}`);
10301
10973
  statusLine2(spin) + "\x1B[3A\r" + // back up to the logo's first row
10302
10974
  SYNC_END2;
10303
10975
  process.stdout.write(frame);
10304
- await sleep3(frameMs);
10976
+ await sleep4(frameMs);
10305
10977
  }
10306
10978
  process.stdout.write(SYNC_BEGIN2 + `\x1B[38;5;39m${LOGO_L1}
10307
10979
  ${LOGO_L2}\x1B[0m` + SYNC_END2);
10308
- await sleep3(250);
10980
+ await sleep4(250);
10309
10981
  process.stdout.write(SYNC_BEGIN2 + "\n\x1B[2K\n\x1B[2K" + SHOW_CURSOR + SYNC_END2);
10310
10982
  process.removeListener("exit", restoreCursor);
10311
10983
  process.removeListener("SIGINT", onSigint);
@@ -10313,30 +10985,30 @@ ${LOGO_L2}\x1B[0m` + SYNC_END2);
10313
10985
  var LEGACY_INFO_MARKER = "Drive AgentBox from the host:";
10314
10986
  function installTargets() {
10315
10987
  const home = homedir16();
10316
- const claudeSkills = join18(home, ".claude", "skills");
10988
+ const claudeSkills = join19(home, ".claude", "skills");
10317
10989
  return [
10318
- { src: join18("agentbox", "SKILL.md"), dest: join18(claudeSkills, "agentbox", "SKILL.md") },
10990
+ { src: join19("agentbox", "SKILL.md"), dest: join19(claudeSkills, "agentbox", "SKILL.md") },
10319
10991
  {
10320
- src: join18("agentbox-info", "SKILL.md"),
10321
- dest: join18(claudeSkills, "agentbox-info", "SKILL.md")
10992
+ src: join19("agentbox-info", "SKILL.md"),
10993
+ dest: join19(claudeSkills, "agentbox-info", "SKILL.md")
10322
10994
  },
10323
10995
  {
10324
- src: join18("codex", "agentbox.md"),
10325
- dest: join18(home, ".codex", "prompts", "agentbox.md"),
10326
- gateDir: join18(home, ".codex")
10996
+ src: join19("codex", "agentbox.md"),
10997
+ dest: join19(home, ".codex", "prompts", "agentbox.md"),
10998
+ gateDir: join19(home, ".codex")
10327
10999
  },
10328
11000
  {
10329
- src: join18("opencode", "agentbox.md"),
10330
- dest: join18(home, ".config", "opencode", "commands", "agentbox.md"),
10331
- gateDir: join18(home, ".config", "opencode")
11001
+ src: join19("opencode", "agentbox.md"),
11002
+ dest: join19(home, ".config", "opencode", "commands", "agentbox.md"),
11003
+ gateDir: join19(home, ".config", "opencode")
10332
11004
  }
10333
11005
  ];
10334
11006
  }
10335
11007
  function resolveHostSkillsDir() {
10336
11008
  const here = dirname3(fileURLToPath(import.meta.url));
10337
11009
  const candidates = [
10338
- resolve3(here, "..", "share", "host-skills"),
10339
- resolve3(here, "..", "..", "share", "host-skills")
11010
+ resolve4(here, "..", "share", "host-skills"),
11011
+ resolve4(here, "..", "..", "share", "host-skills")
10340
11012
  ];
10341
11013
  for (const c of candidates) {
10342
11014
  if (existsSync8(c)) return c;
@@ -10375,7 +11047,7 @@ function installHostSkills(opts = {}) {
10375
11047
  const blocked = [];
10376
11048
  let skipped = 0;
10377
11049
  for (const t of installTargets()) {
10378
- const src = join18(srcDir, t.src);
11050
+ const src = join19(srcDir, t.src);
10379
11051
  if (!existsSync8(src)) {
10380
11052
  if (!quiet) log32.warn(`bundled file missing (skipped): ${src}`);
10381
11053
  skipped++;
@@ -10397,7 +11069,7 @@ function installHostSkills(opts = {}) {
10397
11069
  mkdirSync6(dirname3(t.dest), { recursive: true });
10398
11070
  if (link) {
10399
11071
  rmSync(t.dest, { force: true });
10400
- symlinkSync2(resolve3(srcDir, t.src), t.dest);
11072
+ symlinkSync2(resolve4(srcDir, t.src), t.dest);
10401
11073
  } else {
10402
11074
  if (isSymlink(t.dest)) rmSync(t.dest, { force: true });
10403
11075
  writeFileSync5(t.dest, readFileSync2(src, "utf8"));
@@ -10430,7 +11102,7 @@ function ensureTty() {
10430
11102
  async function runProviderLogin(name) {
10431
11103
  if (name === "docker") return true;
10432
11104
  if (name === "daytona") {
10433
- const mod2 = await import("./dist-3IMQNTTV.js");
11105
+ const mod2 = await import("./dist-SL2QSMBE.js");
10434
11106
  const status2 = await mod2.getDaytonaStatus();
10435
11107
  if (status2.configured) {
10436
11108
  log32.info("daytona: already configured");
@@ -10443,7 +11115,7 @@ async function runProviderLogin(name) {
10443
11115
  return true;
10444
11116
  }
10445
11117
  if (name === "hetzner") {
10446
- const mod2 = await import("./dist-J2IHD5T7.js");
11118
+ const mod2 = await import("./dist-VHI5QOSQ.js");
10447
11119
  const status2 = mod2.readHetznerCredStatus();
10448
11120
  if (status2.source !== "none") {
10449
11121
  log32.info("hetzner: already configured");
@@ -10456,7 +11128,7 @@ async function runProviderLogin(name) {
10456
11128
  return true;
10457
11129
  }
10458
11130
  if (name === "vercel") {
10459
- const mod2 = await import("./dist-57M6ZA7H.js");
11131
+ const mod2 = await import("./dist-XC47DSCR.js");
10460
11132
  const status2 = mod2.readVercelCredStatus();
10461
11133
  if (status2.auth !== "none") {
10462
11134
  log32.info(`vercel: already configured (${status2.auth})`);
@@ -10468,7 +11140,7 @@ async function runProviderLogin(name) {
10468
11140
  await mod2.ensureVercelCredentials();
10469
11141
  return true;
10470
11142
  }
10471
- const mod = await import("./dist-34RKQ74M.js");
11143
+ const mod = await import("./dist-4IQFJJQI.js");
10472
11144
  const status = mod.readE2bCredStatus();
10473
11145
  if (status.auth !== "none") {
10474
11146
  log32.info(`e2b: already configured (${status.auth})`);
@@ -10653,9 +11325,15 @@ var doctorCommand = new Command28("doctor").description(
10653
11325
  );
10654
11326
  process.exit(1);
10655
11327
  }
11328
+ const [sys, prov, integrations] = await Promise.all([
11329
+ runSystemChecks(),
11330
+ runProviderChecks(name),
11331
+ integrationsChecks()
11332
+ ]);
10656
11333
  groups = [
10657
- { title: "system", results: await runSystemChecks() },
10658
- await runProviderChecks(name)
11334
+ { title: "system", results: sys },
11335
+ prov,
11336
+ { title: "integrations", results: integrations }
10659
11337
  ];
10660
11338
  } else {
10661
11339
  groups = await runAllChecks();
@@ -10868,17 +11546,17 @@ async function applyLiveCloudStates(boxes) {
10868
11546
  );
10869
11547
  }
10870
11548
  function withTimeout(p, ms) {
10871
- return new Promise((resolve4) => {
10872
- const t = setTimeout(() => resolve4(null), ms);
11549
+ return new Promise((resolve5) => {
11550
+ const t = setTimeout(() => resolve5(null), ms);
10873
11551
  if (typeof t.unref === "function") t.unref();
10874
11552
  p.then(
10875
11553
  (v) => {
10876
11554
  clearTimeout(t);
10877
- resolve4(v);
11555
+ resolve5(v);
10878
11556
  },
10879
11557
  () => {
10880
11558
  clearTimeout(t);
10881
- resolve4(null);
11559
+ resolve5(null);
10882
11560
  }
10883
11561
  );
10884
11562
  });
@@ -10932,7 +11610,7 @@ async function watchRender(produce, rawInterval, opts = {}) {
10932
11610
  process.once("SIGINT", () => process.exit(0));
10933
11611
  }
10934
11612
  const hint = interactive ? "q or Ctrl-C to exit" : "Ctrl-C to exit";
10935
- const sleep5 = (d) => new Promise((r) => {
11613
+ const sleep6 = (d) => new Promise((r) => {
10936
11614
  const t = setTimeout(() => {
10937
11615
  wake = null;
10938
11616
  r();
@@ -10963,7 +11641,7 @@ async function watchRender(produce, rawInterval, opts = {}) {
10963
11641
  ${trimmed}
10964
11642
  `);
10965
11643
  }
10966
- await sleep5(ms);
11644
+ await sleep6(ms);
10967
11645
  if (exiting) {
10968
11646
  restore();
10969
11647
  process.exit(0);
@@ -11338,7 +12016,7 @@ import { log as log35 } from "@clack/prompts";
11338
12016
  import { execa as execa4 } from "execa";
11339
12017
  import { existsSync as existsSync9, mkdirSync as mkdirSync7 } from "fs";
11340
12018
  import { homedir as homedir17 } from "os";
11341
- import { join as join19 } from "path";
12019
+ import { join as join20 } from "path";
11342
12020
  import { Command as Command32 } from "commander";
11343
12021
 
11344
12022
  // src/commands/path.ts
@@ -11400,7 +12078,7 @@ var openCommand = new Command32("open").description("Open a box's /workspace in
11400
12078
  }
11401
12079
  });
11402
12080
  async function runCloudOpen(box, provider, opts) {
11403
- const mountRoot = join19(homedir17(), ".agentbox", "mounts", box.name);
12081
+ const mountRoot = join20(homedir17(), ".agentbox", "mounts", box.name);
11404
12082
  if (opts.unmount) {
11405
12083
  const ok = await tryUnmount(mountRoot);
11406
12084
  if (ok) process.stdout.write(`unmounted ${mountRoot}
@@ -11674,7 +12352,7 @@ async function pruneCloud(provider, opts) {
11674
12352
  }
11675
12353
 
11676
12354
  // src/commands/queue.ts
11677
- import { readFile as readFile5, stat as stat5 } from "fs/promises";
12355
+ import { readFile as readFile6, stat as stat6 } from "fs/promises";
11678
12356
  import { intro as intro8, log as log37, outro as outro7 } from "@clack/prompts";
11679
12357
  import { Command as Command35 } from "commander";
11680
12358
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
@@ -11721,8 +12399,8 @@ var queueShowCommand = new Command35("show").description("Dump a job manifest an
11721
12399
  process.stdout.write(JSON.stringify(job, null, 2) + "\n");
11722
12400
  const tailN = Number.parseInt(opts.tail, 10) || 50;
11723
12401
  try {
11724
- await stat5(job.logPath);
11725
- const text = await readFile5(job.logPath, "utf8");
12402
+ await stat6(job.logPath);
12403
+ const text = await readFile6(job.logPath, "utf8");
11726
12404
  const lines = text.split(/\r?\n/);
11727
12405
  const slice = lines.slice(Math.max(0, lines.length - tailN - 1));
11728
12406
  process.stdout.write(`
@@ -11873,11 +12551,11 @@ async function pollUntil(deadline, probe) {
11873
12551
  if (result !== void 0) return result;
11874
12552
  const remaining = deadline - Date.now();
11875
12553
  if (remaining <= 0) break;
11876
- await sleep4(Math.min(QUEUE_POLL_INTERVAL_MS, remaining));
12554
+ await sleep5(Math.min(QUEUE_POLL_INTERVAL_MS, remaining));
11877
12555
  }
11878
12556
  throw new QueueWaitTimeout();
11879
12557
  }
11880
- function sleep4(ms) {
12558
+ function sleep5(ms) {
11881
12559
  return new Promise((r) => setTimeout(r, ms));
11882
12560
  }
11883
12561
  function parsePositiveInt3(raw, label) {
@@ -11927,7 +12605,8 @@ async function rehydrateFromState() {
11927
12605
  cloudBackend: b.cloud?.backend,
11928
12606
  relayPreviewUrl: b.cloud?.relayPreviewUrl,
11929
12607
  relayPreviewToken: b.cloud?.relayPreviewToken,
11930
- bridgeToken: b.cloud?.bridgeToken
12608
+ bridgeToken: b.cloud?.bridgeToken,
12609
+ autoApproveHostActions: b.autoApproveHostActions
11931
12610
  }))
11932
12611
  );
11933
12612
  }
@@ -12149,6 +12828,36 @@ async function runDockerJob(job, log47, onBoxCreated) {
12149
12828
  } else {
12150
12829
  throw new Error(`unknown agent kind: ${String(job.agent)}`);
12151
12830
  }
12831
+ await maybeOpenQueuedTerminal(job, result.record.name, log47);
12832
+ }
12833
+ function agentBinaryName(agent) {
12834
+ return agent === "claude-code" ? "claude" : agent;
12835
+ }
12836
+ async function maybeOpenQueuedTerminal(job, boxName, log47) {
12837
+ const ctx = job.openTerminal;
12838
+ if (!ctx) return;
12839
+ const cliEntry = process.env["AGENTBOX_CLI_ENTRY"];
12840
+ if (!cliEntry) {
12841
+ log47.write("queue.openIn: AGENTBOX_CLI_ENTRY unset; cannot open terminal");
12842
+ return;
12843
+ }
12844
+ const argv2 = [
12845
+ process.execPath,
12846
+ cliEntry,
12847
+ agentBinaryName(job.agent),
12848
+ "attach",
12849
+ boxName,
12850
+ "--attach-in",
12851
+ "same"
12852
+ ];
12853
+ try {
12854
+ const r = await spawnQueuedOpenTerminal(ctx, argv2, boxName);
12855
+ log47.write(
12856
+ r.launched ? `queue.openIn: ${r.note}` : `queue.openIn: open failed: ${r.error ?? ""}`
12857
+ );
12858
+ } catch (err) {
12859
+ log47.write(`queue.openIn: open threw: ${err instanceof Error ? err.message : String(err)}`);
12860
+ }
12152
12861
  }
12153
12862
  async function runCloudJob(job, log47, onBoxCreated) {
12154
12863
  const opts = job.createOpts;
@@ -12206,6 +12915,7 @@ async function runCloudJob(job, log47, onBoxCreated) {
12206
12915
  sessionName,
12207
12916
  extraArgs
12208
12917
  });
12918
+ await maybeOpenQueuedTerminal(job, result.record.name, log47);
12209
12919
  }
12210
12920
  function buildOverridesFromJob(job) {
12211
12921
  const opts = job.createOpts;
@@ -12757,12 +13467,12 @@ async function renderText(i) {
12757
13467
  `env files ${i.record.withEnv ? "yes" : "no"}`,
12758
13468
  "endpoints",
12759
13469
  ...renderEndpoints(i),
12760
- `mem limit ${lim?.memoryBytes ? fmtBytes(lim.memoryBytes) : "unlimited"}`,
13470
+ `mem limit ${lim?.memoryBytes ? fmtBytes2(lim.memoryBytes) : "unlimited"}`,
12761
13471
  `cpu limit ${fmtLimit(lim?.cpus, "")}`,
12762
13472
  `pids limit ${fmtLimit(lim?.pidsLimit, "")}`,
12763
13473
  `disk limit ${lim?.disk ? `${lim.disk} (best-effort; no-op on overlay2/macOS)` : "unlimited"}`,
12764
13474
  `snapshot dir ${i.record.snapshotDir ?? "(none)"}`,
12765
- `snapshot size ${fmtBytes(i.snapshotSizeBytes)}`,
13475
+ `snapshot size ${fmtBytes2(i.snapshotSizeBytes)}`,
12766
13476
  `checkpoint ${renderCheckpoint(i, ckptBytes)}`,
12767
13477
  `host export ${i.hostPaths.mergedExport} (run \`agentbox open\` to refresh)`,
12768
13478
  `created ${i.record.createdAt}`
@@ -12772,7 +13482,7 @@ async function renderText(i) {
12772
13482
  function renderCheckpoint(i, sizeBytes) {
12773
13483
  const src = i.record.checkpointSource;
12774
13484
  if (!src || !i.record.checkpointImage) return "(none)";
12775
- const sizePart = sizeBytes !== null ? ` ${fmtBytes(sizeBytes)}` : "";
13485
+ const sizePart = sizeBytes !== null ? ` ${fmtBytes2(sizeBytes)}` : "";
12776
13486
  return `${src.ref} (${src.type}, chain ${src.chain.length}) \u2192 ${i.record.checkpointImage}${sizePart}`;
12777
13487
  }
12778
13488
  function renderClaudeSession(i) {
@@ -12843,7 +13553,7 @@ async function renderCloudText(box) {
12843
13553
  `bridge token ${box.cloud?.bridgeToken ? "(set)" : "(unset)"}`,
12844
13554
  `playwright ${box.withPlaywright ? "yes" : "no"}`,
12845
13555
  `env files ${box.withEnv ? "yes" : "no"}`,
12846
- `mem limit ${lim?.memoryBytes ? fmtBytes(lim.memoryBytes) : "unlimited"}`,
13556
+ `mem limit ${lim?.memoryBytes ? fmtBytes2(lim.memoryBytes) : "unlimited"}`,
12847
13557
  `cpu limit ${fmtLimit(lim?.cpus, "")}`,
12848
13558
  `pids limit ${fmtLimit(lim?.pidsLimit, "")}`,
12849
13559
  `persisted ${persisted ? `${persisted.timestamp} (${String(persisted.services.length)} svc, ${String(persisted.tasks.length)} tasks, ${String(persisted.ports.length)} ports)` : "(none)"}`,
@@ -13004,20 +13714,20 @@ function renderResources(s) {
13004
13714
  if (s.live) {
13005
13715
  seg.push(`cpu ${fmtPercent(s.cpuPercent)}${lim(s.limits.cpus)}`);
13006
13716
  seg.push(
13007
- `mem ${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memLimitBytes)} (${fmtPercent(s.memPercent)})${lim(s.limits.memoryBytes ? fmtBytes(s.limits.memoryBytes) : null)}`
13717
+ `mem ${fmtBytes2(s.memUsedBytes)} / ${fmtBytes2(s.memLimitBytes)} (${fmtPercent(s.memPercent)})${lim(s.limits.memoryBytes ? fmtBytes2(s.limits.memoryBytes) : null)}`
13008
13718
  );
13009
13719
  seg.push(`pids ${s.pids === null ? "\u2014" : String(s.pids)}${lim(s.limits.pidsLimit)}`);
13010
13720
  } else {
13011
13721
  seg.push("not running");
13012
- if (s.limits.memoryBytes) seg.push(`mem limit ${fmtBytes(s.limits.memoryBytes)}`);
13722
+ if (s.limits.memoryBytes) seg.push(`mem limit ${fmtBytes2(s.limits.memoryBytes)}`);
13013
13723
  if (s.limits.cpus) seg.push(`cpu limit ${String(s.limits.cpus)}`);
13014
13724
  if (s.limits.pidsLimit) seg.push(`pids limit ${String(s.limits.pidsLimit)}`);
13015
13725
  }
13016
13726
  seg.push(
13017
- `disk ${fmtBytes(s.diskUsedBytes)}${s.limits.disk ? ` (limit ${s.limits.disk}, no-op on overlay2/macOS)` : ""}`
13727
+ `disk ${fmtBytes2(s.diskUsedBytes)}${s.limits.disk ? ` (limit ${s.limits.disk}, no-op on overlay2/macOS)` : ""}`
13018
13728
  );
13019
- if (s.snapshotDiskBytes !== null) seg.push(`snapshot ${fmtBytes(s.snapshotDiskBytes)}`);
13020
- if (s.checkpointVolumeBytes !== null) seg.push(`ckpt ${fmtBytes(s.checkpointVolumeBytes)}`);
13729
+ if (s.snapshotDiskBytes !== null) seg.push(`snapshot ${fmtBytes2(s.snapshotDiskBytes)}`);
13730
+ if (s.checkpointVolumeBytes !== null) seg.push(`ckpt ${fmtBytes2(s.checkpointVolumeBytes)}`);
13021
13731
  let line = ` ${seg.join(" ")}`;
13022
13732
  for (const w of s.warnings) line += `
13023
13733
  note: ${w}`;
@@ -13114,8 +13824,8 @@ restart with: agentbox start ${box.name}
13114
13824
  import { Command as Command43 } from "commander";
13115
13825
  var COLS = ["BOX", "STATE", "CPU%", "MEM USAGE / LIMIT", "MEM%", "PIDS", "DISK", "NET I/O"];
13116
13826
  function row(name, state, s) {
13117
- const mem = `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memLimitBytes)}`;
13118
- const net = s.netRxBytes === null && s.netTxBytes === null ? "\u2014" : `${fmtBytes(s.netRxBytes)} / ${fmtBytes(s.netTxBytes)}`;
13827
+ const mem = `${fmtBytes2(s.memUsedBytes)} / ${fmtBytes2(s.memLimitBytes)}`;
13828
+ const net = s.netRxBytes === null && s.netTxBytes === null ? "\u2014" : `${fmtBytes2(s.netRxBytes)} / ${fmtBytes2(s.netTxBytes)}`;
13119
13829
  return [
13120
13830
  name,
13121
13831
  state,
@@ -13123,7 +13833,7 @@ function row(name, state, s) {
13123
13833
  s.live ? mem : "\u2014",
13124
13834
  fmtPercent(s.memPercent),
13125
13835
  s.pids === null ? "\u2014" : String(s.pids),
13126
- fmtBytes(s.diskUsedBytes),
13836
+ fmtBytes2(s.diskUsedBytes),
13127
13837
  s.live ? net : "\u2014"
13128
13838
  ];
13129
13839
  }
@@ -13179,8 +13889,8 @@ async function renderProjectFooters() {
13179
13889
  allCheckpointImagesBytes(),
13180
13890
  agentboxHomeBytes()
13181
13891
  ]);
13182
- if (home !== null) parts.push(`~/.agentbox: ${fmtBytes(home)}`);
13183
- if (ckpt !== null) parts.push(`checkpoints: ${fmtBytes(ckpt)}`);
13892
+ if (home !== null) parts.push(`~/.agentbox: ${fmtBytes2(home)}`);
13893
+ if (ckpt !== null) parts.push(`checkpoints: ${fmtBytes2(ckpt)}`);
13184
13894
  return parts.length > 0 ? `
13185
13895
 
13186
13896
  SYSTEM: ${parts.join(" - ")}` : "";