@madarco/agentbox 0.6.0 → 0.8.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 (75) hide show
  1. package/dist/_cloud-attach-T727ZPRV.js +13 -0
  2. package/dist/chunk-67N47KUS.js +1640 -0
  3. package/dist/chunk-67N47KUS.js.map +1 -0
  4. package/dist/chunk-6OZDFNBF.js +8114 -0
  5. package/dist/chunk-6OZDFNBF.js.map +1 -0
  6. package/dist/chunk-BGK32PZE.js +455 -0
  7. package/dist/chunk-BGK32PZE.js.map +1 -0
  8. package/dist/chunk-FODMEHD3.js +1200 -0
  9. package/dist/chunk-FODMEHD3.js.map +1 -0
  10. package/dist/chunk-G3H2L3O2.js +288 -0
  11. package/dist/chunk-G3H2L3O2.js.map +1 -0
  12. package/dist/chunk-I24B6AXR.js +600 -0
  13. package/dist/chunk-I24B6AXR.js.map +1 -0
  14. package/dist/chunk-LEV3KICD.js +738 -0
  15. package/dist/chunk-LEV3KICD.js.map +1 -0
  16. package/dist/cloud-poller-SUNA6ZQC-2RG5WPRN.js +10 -0
  17. package/dist/dist-L4LCG5SJ.js +293 -0
  18. package/dist/dist-L4LCG5SJ.js.map +1 -0
  19. package/dist/dist-LOZBWMBF.js +447 -0
  20. package/dist/dist-ZODPD2I6.js +1407 -0
  21. package/dist/dist-ZODPD2I6.js.map +1 -0
  22. package/dist/index.js +7281 -2134
  23. package/dist/index.js.map +1 -1
  24. package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
  25. package/package.json +8 -3
  26. package/runtime/daytona/custom-system-CLAUDE.md +39 -0
  27. package/runtime/docker/Dockerfile.box +120 -14
  28. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +15 -8
  29. package/runtime/docker/packages/ctl/dist/bin.cjs +11310 -816
  30. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +68 -0
  31. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +9 -9
  32. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
  33. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
  34. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
  35. package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
  36. package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
  37. package/runtime/hetzner/agentbox-checkpoint-cleanup +52 -0
  38. package/runtime/hetzner/agentbox-codex-hooks.json +68 -0
  39. package/runtime/hetzner/agentbox-dockerd-start +132 -0
  40. package/runtime/hetzner/agentbox-open +28 -0
  41. package/runtime/hetzner/agentbox-setup-skill.md +196 -0
  42. package/runtime/hetzner/agentbox-vnc-start +77 -0
  43. package/runtime/hetzner/claude-managed-settings.json +115 -0
  44. package/runtime/hetzner/ctl.cjs +23397 -0
  45. package/runtime/hetzner/custom-system-CLAUDE.md +39 -0
  46. package/runtime/hetzner/gh-shim +263 -0
  47. package/runtime/hetzner/git-shim +131 -0
  48. package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
  49. package/runtime/hetzner/scripts/install-box.sh +374 -0
  50. package/runtime/relay/bin.cjs +10017 -817
  51. package/share/agentbox-setup/SKILL.md +15 -8
  52. package/share/host-skills/agentbox/SKILL.md +29 -0
  53. package/share/host-skills/agentbox-info/SKILL.md +211 -0
  54. package/share/host-skills/codex/agentbox.md +35 -0
  55. package/share/host-skills/opencode/agentbox.md +26 -0
  56. package/dist/chunk-BBZMA2K6.js +0 -238
  57. package/dist/chunk-BBZMA2K6.js.map +0 -1
  58. package/dist/chunk-HHMWQNLF.js +0 -1709
  59. package/dist/chunk-HHMWQNLF.js.map +0 -1
  60. package/dist/chunk-HPZMD5DE.js +0 -106
  61. package/dist/chunk-HPZMD5DE.js.map +0 -1
  62. package/dist/chunk-HTTKML3C.js +0 -2655
  63. package/dist/chunk-HTTKML3C.js.map +0 -1
  64. package/dist/chunk-KJNZP6I3.js +0 -586
  65. package/dist/chunk-KJNZP6I3.js.map +0 -1
  66. package/dist/chunk-M7I247BK.js +0 -525
  67. package/dist/chunk-M7I247BK.js.map +0 -1
  68. package/dist/create-6PWXI6HO-OWAMHBAK.js +0 -15
  69. package/dist/lifecycle-EMXR46DI-DUVBXNTV.js +0 -38
  70. package/dist/state-KD7M46ZP-KHFTHFUS.js +0 -26
  71. package/dist/stats-SZXOJE3D-N7OODCHW.js +0 -19
  72. /package/dist/{create-6PWXI6HO-OWAMHBAK.js.map → _cloud-attach-T727ZPRV.js.map} +0 -0
  73. /package/dist/{lifecycle-EMXR46DI-DUVBXNTV.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
  74. /package/dist/{state-KD7M46ZP-KHFTHFUS.js.map → dist-LOZBWMBF.js.map} +0 -0
  75. /package/dist/{stats-SZXOJE3D-N7OODCHW.js.map → prepared-state-CL4CWXQA-ME4HSKDE.js.map} +0 -0
@@ -0,0 +1,1200 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_RELAY_PORT,
4
+ readBoxStatus
5
+ } from "./chunk-6OZDFNBF.js";
6
+
7
+ // src/commands/_cloud-attach.ts
8
+ import { spawn as spawn3 } from "child_process";
9
+
10
+ // src/provider/registry.ts
11
+ var KNOWN = ["docker", "daytona", "hetzner"];
12
+ function isKnownProvider(name) {
13
+ return KNOWN.includes(name);
14
+ }
15
+ async function getProvider(name) {
16
+ switch (name) {
17
+ case "docker": {
18
+ const mod = await import("./dist-LOZBWMBF.js");
19
+ return mod.dockerProvider;
20
+ }
21
+ case "daytona": {
22
+ const mod = await import("./dist-L4LCG5SJ.js");
23
+ await mod.ensureDaytonaCredentials();
24
+ return mod.daytonaProvider;
25
+ }
26
+ case "hetzner": {
27
+ const mod = await import("./dist-ZODPD2I6.js");
28
+ await mod.ensureHetznerCredentials();
29
+ return mod.hetznerProvider;
30
+ }
31
+ default:
32
+ throw new Error(`unknown sandbox provider: ${String(name)}`);
33
+ }
34
+ }
35
+ async function providerForBox(box) {
36
+ return getProvider(box.provider ?? "docker");
37
+ }
38
+ async function providerForCreate(choice) {
39
+ const flag = choice.flag?.trim();
40
+ const name = flag && flag.length > 0 ? flag : choice.config.box.provider;
41
+ if (typeof name !== "string" || name.length === 0 || !isKnownProvider(name)) {
42
+ throw new Error(
43
+ `unknown sandbox provider "${String(name)}" (known: ${KNOWN.join(", ")})`
44
+ );
45
+ }
46
+ return getProvider(name);
47
+ }
48
+
49
+ // src/wrapped-pty/run.ts
50
+ import { spawn as spawn2, spawnSync } from "child_process";
51
+
52
+ // src/pty/pty-backend.ts
53
+ async function loadPtyBackend() {
54
+ try {
55
+ const ptyMod = await import("@homebridge/node-pty-prebuilt-multiarch");
56
+ const xtermMod = await import("@xterm/headless");
57
+ const spawn4 = ptyMod["spawn"] ?? ptyMod["default"]?.["spawn"];
58
+ const Terminal = xtermMod["Terminal"] ?? xtermMod["default"]?.["Terminal"];
59
+ if (typeof spawn4 !== "function" || typeof Terminal !== "function") {
60
+ return null;
61
+ }
62
+ return {
63
+ ptySpawn: spawn4,
64
+ termCtor: Terminal
65
+ };
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ // src/terminal/host.ts
72
+ import { spawn } from "child_process";
73
+ function detectHostTerminal(env = process.env) {
74
+ const tmux = env["TMUX"];
75
+ if (tmux && tmux.length > 0) return "tmux";
76
+ const termProgram = env["TERM_PROGRAM"];
77
+ if (termProgram === "iTerm.app") return "iterm2";
78
+ return "unknown";
79
+ }
80
+ function shellQuote(s) {
81
+ if (s.length === 0) return "''";
82
+ return "'" + s.replace(/'/g, "'\\''") + "'";
83
+ }
84
+ function appleScriptEscape(s) {
85
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
86
+ }
87
+ function shellJoin(argv) {
88
+ return argv.map(shellQuote).join(" ");
89
+ }
90
+ async function spawnInNewTerminal(args) {
91
+ if (args.host === "tmux") return spawnInTmux(args);
92
+ return spawnInITerm2(args);
93
+ }
94
+ async function spawnInTmux(args) {
95
+ const cmdStr = shellJoin(args.argv);
96
+ let tmuxArgv;
97
+ let noteKind;
98
+ if (args.mode === "split") {
99
+ tmuxArgv = ["split-window", "-h", "-c", args.cwd, "--", cmdStr];
100
+ noteKind = "tmux split";
101
+ } else {
102
+ tmuxArgv = ["new-window", "-n", args.title, "-c", args.cwd, "--", cmdStr];
103
+ noteKind = "tmux window";
104
+ }
105
+ const r = await runQuiet("tmux", tmuxArgv);
106
+ if (r.code !== 0) {
107
+ return {
108
+ launched: false,
109
+ note: "",
110
+ error: `tmux ${tmuxArgv.join(" ")} exited ${String(r.code)}: ${r.stderr.trim()}`
111
+ };
112
+ }
113
+ return {
114
+ launched: true,
115
+ note: `Attached in new ${noteKind} \u2014 Ctrl+a d to detach the box's tmux session.`
116
+ };
117
+ }
118
+ async function spawnInITerm2(args) {
119
+ const inner = shellJoin(args.argv);
120
+ const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;
121
+ const cmdLit = `"${appleScriptEscape(cmdLine)}"`;
122
+ let lines;
123
+ let noteKind;
124
+ switch (args.mode) {
125
+ case "split":
126
+ lines = [
127
+ 'tell application "iTerm"',
128
+ " tell current session of current window to set _s to (split vertically with default profile)",
129
+ ` tell _s to write text ${cmdLit}`,
130
+ "end tell"
131
+ ];
132
+ noteKind = "iTerm2 split";
133
+ break;
134
+ case "tab":
135
+ lines = [
136
+ 'tell application "iTerm"',
137
+ " tell current window to set _t to (create tab with default profile)",
138
+ ` tell current session of _t to write text ${cmdLit}`,
139
+ "end tell"
140
+ ];
141
+ noteKind = "iTerm2 tab";
142
+ break;
143
+ case "window":
144
+ lines = [
145
+ 'tell application "iTerm"',
146
+ " set _w to (create window with default profile)",
147
+ ` tell current session of _w to write text ${cmdLit}`,
148
+ "end tell"
149
+ ];
150
+ noteKind = "iTerm2 window";
151
+ break;
152
+ }
153
+ const r = await runQuiet("osascript", ["-e", lines.join("\n")]);
154
+ if (r.code !== 0) {
155
+ return {
156
+ launched: false,
157
+ note: "",
158
+ error: `osascript exited ${String(r.code)}: ${r.stderr.trim()}`
159
+ };
160
+ }
161
+ return {
162
+ launched: true,
163
+ note: `Attached in new ${noteKind} \u2014 Ctrl+a d to detach the box's tmux session.`
164
+ };
165
+ }
166
+ function runQuiet(cmd, argv) {
167
+ return new Promise((resolve) => {
168
+ const child = spawn(cmd, argv, { stdio: ["ignore", "ignore", "pipe"] });
169
+ let stderr = "";
170
+ child.stderr?.on("data", (chunk) => {
171
+ stderr += chunk.toString("utf8");
172
+ });
173
+ child.on("error", (err) => {
174
+ resolve({ code: 127, stderr: err.message });
175
+ });
176
+ child.on("exit", (code) => {
177
+ resolve({ code: typeof code === "number" ? code : 1, stderr });
178
+ });
179
+ });
180
+ }
181
+
182
+ // src/wrapped-pty/input-router.ts
183
+ var KEY_ENTER = 13;
184
+ var KEY_LF = 10;
185
+ var KEY_ESC = 27;
186
+ var KEY_CTRL_C = 3;
187
+ var KEY_Y_LOW = 121;
188
+ var KEY_Y_UP = 89;
189
+ var KEY_N_LOW = 110;
190
+ var KEY_N_UP = 78;
191
+ var KEY_LEADER = 1;
192
+ var DEFAULT_LEADER_TIMEOUT_MS = 2e3;
193
+ function createInputRouter(opts) {
194
+ let active = null;
195
+ let disposed = false;
196
+ const leaderChords = opts.leaderChords ?? {};
197
+ const leaderEnabled = Object.keys(leaderChords).length > 0;
198
+ const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;
199
+ const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms));
200
+ const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
201
+ let leader = false;
202
+ let leaderTimer = null;
203
+ const disarmLeader = () => {
204
+ if (leaderTimer != null) {
205
+ clearTimer(leaderTimer);
206
+ leaderTimer = null;
207
+ }
208
+ };
209
+ const exitLeader = () => {
210
+ if (!leader) return;
211
+ leader = false;
212
+ disarmLeader();
213
+ opts.onLeaderChange?.(false);
214
+ };
215
+ const enterLeader = () => {
216
+ leader = true;
217
+ disarmLeader();
218
+ leaderTimer = setTimer(leaderTimeoutMs, () => {
219
+ leaderTimer = null;
220
+ exitLeader();
221
+ });
222
+ opts.onLeaderChange?.(true);
223
+ };
224
+ const resolveLeaderByte = (b) => {
225
+ if (b === KEY_LEADER) {
226
+ exitLeader();
227
+ opts.onForward(Buffer.from([KEY_LEADER]));
228
+ return;
229
+ }
230
+ if (b === KEY_ESC) {
231
+ exitLeader();
232
+ return;
233
+ }
234
+ const action = leaderChords[String.fromCharCode(b).toLowerCase()];
235
+ if (action) {
236
+ exitLeader();
237
+ opts.onAction?.(action);
238
+ return;
239
+ }
240
+ exitLeader();
241
+ opts.onForward(Buffer.from([b]));
242
+ };
243
+ const settle = (answer, cancelled) => {
244
+ if (!active) return;
245
+ const body = {
246
+ id: active.ev.id,
247
+ answer,
248
+ ...cancelled ? { cancelled: true } : {}
249
+ };
250
+ const p = active;
251
+ active = null;
252
+ p.resolve(body);
253
+ opts.onAnswer(body);
254
+ };
255
+ const handleCapturedByte = (b) => {
256
+ if (!active) return;
257
+ if (b === KEY_Y_LOW || b === KEY_Y_UP) {
258
+ settle("y");
259
+ return;
260
+ }
261
+ if (b === KEY_N_LOW || b === KEY_N_UP) {
262
+ settle("n");
263
+ return;
264
+ }
265
+ if (b === KEY_ESC || b === KEY_CTRL_C) {
266
+ settle("n", true);
267
+ return;
268
+ }
269
+ if (b === KEY_ENTER || b === KEY_LF) {
270
+ const def = active.ev.defaultAnswer ?? "n";
271
+ settle(def);
272
+ return;
273
+ }
274
+ };
275
+ const feedSteady = (buf) => {
276
+ let chunkStart = 0;
277
+ const flushChunk = (end) => {
278
+ if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));
279
+ chunkStart = end;
280
+ };
281
+ for (let i = 0; i < buf.length; i++) {
282
+ const byte = buf[i];
283
+ if (byte === void 0) continue;
284
+ if (leader) {
285
+ resolveLeaderByte(byte);
286
+ chunkStart = i + 1;
287
+ continue;
288
+ }
289
+ if (byte === KEY_LEADER) {
290
+ flushChunk(i);
291
+ chunkStart = i + 1;
292
+ enterLeader();
293
+ }
294
+ }
295
+ flushChunk(buf.length);
296
+ };
297
+ return {
298
+ get capturing() {
299
+ return active !== null;
300
+ },
301
+ feed(buf) {
302
+ if (disposed) return;
303
+ if (active) {
304
+ if (buf.length > 1 && buf[0] === KEY_ESC) return;
305
+ for (let i = 0; i < buf.length; i++) {
306
+ const byte = buf[i];
307
+ if (byte === void 0) continue;
308
+ if (active) {
309
+ handleCapturedByte(byte);
310
+ } else {
311
+ opts.onForward(buf.subarray(i));
312
+ return;
313
+ }
314
+ }
315
+ return;
316
+ }
317
+ if (!leaderEnabled) {
318
+ opts.onForward(buf);
319
+ return;
320
+ }
321
+ feedSteady(buf);
322
+ },
323
+ capture(ev) {
324
+ return new Promise((resolve, reject) => {
325
+ if (leader) exitLeader();
326
+ if (active) {
327
+ settle("n", true);
328
+ }
329
+ active = { ev, resolve, reject };
330
+ });
331
+ },
332
+ abort(reason) {
333
+ if (!active) return;
334
+ const p = active;
335
+ active = null;
336
+ const msg = reason === "pty-exit" ? "pty exited" : "resolved by sibling wrapper";
337
+ p.reject(new Error(msg));
338
+ },
339
+ dispose() {
340
+ if (disposed) return;
341
+ disposed = true;
342
+ disarmLeader();
343
+ if (active) {
344
+ const p = active;
345
+ active = null;
346
+ p.reject(new Error("input router disposed"));
347
+ }
348
+ }
349
+ };
350
+ }
351
+
352
+ // src/dashboard/sidebar.ts
353
+ function ellipsize(s, max) {
354
+ if (max <= 0) return "";
355
+ if (s.length <= max) return s;
356
+ if (max === 1) return "\u2026";
357
+ return s.slice(0, max - 1) + "\u2026";
358
+ }
359
+ function ellipsizeHead(s, max) {
360
+ if (max <= 0) return "";
361
+ if (s.length <= max) return s;
362
+ if (max === 1) return "\u2026";
363
+ return "\u2026" + s.slice(s.length - (max - 1));
364
+ }
365
+ function activityCell(b) {
366
+ if (b.pendingPrompt) return "\u25B2 prompt";
367
+ if (b.checkpointing) return "\u25C6 checkpoint";
368
+ if (b.state !== "running") return `[${b.state}]`;
369
+ switch (b.activity) {
370
+ case "working":
371
+ return "\u25CF working";
372
+ case "idle":
373
+ return "\u25CB idle";
374
+ case "waiting":
375
+ return "\u25D0 waiting";
376
+ default:
377
+ return "? unknown";
378
+ }
379
+ }
380
+ var NEW_BOX_ID = "__agentbox_new__";
381
+ var NEW_BOX_LABEL = "+ New box";
382
+ var SIDEBAR_HEADER = "AgentBox";
383
+ function topBorder(label, w) {
384
+ const lead = `\u2500\u2500\u2500\u2500 ${label} `;
385
+ if (lead.length >= w) return lead.slice(0, w);
386
+ return lead + "\u2500".repeat(w - lead.length);
387
+ }
388
+ function fit(s, w) {
389
+ if (s.length === w) return s;
390
+ if (s.length > w) return s.slice(0, w);
391
+ return s + " ".repeat(w - s.length);
392
+ }
393
+ function center(s, w) {
394
+ if (s.length >= w) return s.slice(0, w);
395
+ const pad = w - s.length;
396
+ const leftPad = Math.floor(pad / 2);
397
+ return " ".repeat(leftPad) + s + " ".repeat(pad - leftPad);
398
+ }
399
+ function projectLabel(project) {
400
+ if (!project) return "(no project)";
401
+ const parts = project.split("/").filter(Boolean);
402
+ return parts[parts.length - 1] ?? project;
403
+ }
404
+ function stripTitleGlyph(s) {
405
+ const t = s.replace(/^[\s\p{S}*·]+/u, "");
406
+ return t.length > 0 ? t : s.trim();
407
+ }
408
+ function boxRow(b, marker, w) {
409
+ const numStr = b.index != null ? `${b.index} ` : "";
410
+ const status = activityCell(b);
411
+ const left = `${marker}${numStr}`;
412
+ const room = w - left.length - status.length - 2;
413
+ if (room <= 0) return fit(`${left}${status}`, w);
414
+ const middle = b.state === "running" && b.sessionTitle ? ellipsize(stripTitleGlyph(b.sessionTitle), room) : ellipsizeHead(b.name, room);
415
+ return fit(`${left}${middle}`, w - status.length - 1) + status + " ";
416
+ }
417
+ function sidebarLines(boxes, selectedId, w, h) {
418
+ const lines = [topBorder(SIDEBAR_HEADER, w), fit("", w)];
419
+ const rowOwner = [null, null];
420
+ const headerRows = [true, false];
421
+ const push = (line, owner, header) => {
422
+ lines.push(fit(line, w));
423
+ rowOwner.push(owner);
424
+ headerRows.push(header);
425
+ };
426
+ let prevProject;
427
+ let seenGroup = false;
428
+ for (const b of boxes) {
429
+ const marker = b.id === selectedId ? "\u25B8" : " ";
430
+ if (b.id === NEW_BOX_ID) {
431
+ push(`${marker}${NEW_BOX_LABEL}`, b.id, false);
432
+ continue;
433
+ }
434
+ if (!seenGroup || b.project !== prevProject) {
435
+ push(center(` \u2500\u2500 ${projectLabel(b.project)} \u2500\u2500 `, w), null, true);
436
+ prevProject = b.project;
437
+ seenGroup = true;
438
+ }
439
+ push(boxRow(b, marker, w), b.id, false);
440
+ }
441
+ if (boxes.length === 0) push(" (no boxes)", null, false);
442
+ while (lines.length < h) push("", null, false);
443
+ return {
444
+ lines: lines.slice(0, h),
445
+ rowOwner: rowOwner.slice(0, h),
446
+ headerRows: headerRows.slice(0, h)
447
+ };
448
+ }
449
+ function menuLines(boxName, w, h) {
450
+ const body = [
451
+ "",
452
+ ` No agent session in ${boxName}.`,
453
+ "",
454
+ " [c] Start Claude",
455
+ " [x] Start Codex",
456
+ " [o] Start OpenCode",
457
+ " [s] Open a shell",
458
+ "",
459
+ " Ctrl+Option+\u2191/\u2193 switch \xB7 Ctrl-a then c/s/u/q (code/screen/url/quit)"
460
+ ];
461
+ const top = Math.max(0, Math.floor((h - body.length) / 2));
462
+ const out = [];
463
+ for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? "", w));
464
+ return out;
465
+ }
466
+ function lifecycleMenuLines(boxName, state, confirmDestroy, w, h) {
467
+ const body = confirmDestroy ? [
468
+ "",
469
+ ` Destroy ${boxName}?`,
470
+ " This removes the container and its volumes.",
471
+ "",
472
+ " [y] Yes, destroy",
473
+ " [any other key] Cancel"
474
+ ] : [
475
+ "",
476
+ ` Box ${boxName} is ${state}.`,
477
+ "",
478
+ state === "paused" ? " [u] Unpause" : " [s] Start",
479
+ " [d] Destroy",
480
+ "",
481
+ " Ctrl+Option+\u2191/\u2193 switch \xB7 Ctrl-a then q quit"
482
+ ];
483
+ const top = Math.max(0, Math.floor((h - body.length) / 2));
484
+ const out = [];
485
+ for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? "", w));
486
+ return out;
487
+ }
488
+ function createMenuLines(where, w, h) {
489
+ const body = [
490
+ "",
491
+ " Create a new box",
492
+ "",
493
+ " [c] Create + launch Claude",
494
+ " [x] Create + launch Codex",
495
+ " [o] Create + launch OpenCode",
496
+ " [n] Create only",
497
+ "",
498
+ ` in ${where}`,
499
+ "",
500
+ " Ctrl+Option+\u2191/\u2193 switch \xB7 Ctrl-a then q quit"
501
+ ];
502
+ const top = Math.max(0, Math.floor((h - body.length) / 2));
503
+ const out = [];
504
+ for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? "", w));
505
+ return out;
506
+ }
507
+ var BAR_BG = "\x1B[48;2;48;48;48m";
508
+ var BAR_BASE = BAR_BG + "\x1B[38;5;250m";
509
+ var BAR_BRAND = "\x1B[48;5;39m\x1B[38;5;16m";
510
+ var BRAND_BOLD = "\x1B[1m";
511
+ var BRAND_NOBOLD = "\x1B[22m";
512
+ var HINT_KEY = "\x1B[38;5;255m";
513
+ var HINT_TXT = "\x1B[38;5;245m";
514
+ var BAR_RESET = "\x1B[0m";
515
+ var SWITCH_HINT = ["Control+Option+\u2191/\u2193", "switch"];
516
+ var HINT_GROUPS = [
517
+ SWITCH_HINT,
518
+ ["Control+a c", "code"],
519
+ ["Control+a s", "screen"],
520
+ ["Control+a u", "url"],
521
+ ["Control+a q", "quit"]
522
+ ];
523
+ var COLLAPSED_HINT_GROUPS = [
524
+ SWITCH_HINT,
525
+ ["Control+a", "more"]
526
+ ];
527
+ var ADVANCED_HINT_GROUPS = [
528
+ ["c", "code"],
529
+ ["s", "screen"],
530
+ ["u", "url"],
531
+ ["t", "stop"],
532
+ ["p", "pause"],
533
+ ["d", "destroy"],
534
+ ["q", "quit"]
535
+ ];
536
+ function statusLine(box, w, stateLabel, groups = HINT_GROUPS, fallbackGroups) {
537
+ const state = stateLabel ?? (box ? box.state === "running" ? box.activity ?? "unknown" : box.state : "");
538
+ const brandPrefix = box ? " agentbox \u25B8 " : " agentbox ";
539
+ const base = box ? `${box.name} (${state})` : "";
540
+ const coreMain = box ? `${base} ` : "";
541
+ const corePlain = brandPrefix + coreMain;
542
+ const SEP = " \u2502 ";
543
+ const renderHints = (g) => ({
544
+ plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + " ",
545
+ styled: g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + " "
546
+ });
547
+ let hints = null;
548
+ for (const g of [groups, fallbackGroups ?? COLLAPSED_HINT_GROUPS]) {
549
+ const h = renderHints(g);
550
+ if (corePlain.length + h.plain.length + 1 <= w) {
551
+ hints = h;
552
+ break;
553
+ }
554
+ }
555
+ if (!hints) {
556
+ return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;
557
+ }
558
+ const room = w - corePlain.length - hints.plain.length - 1;
559
+ let titleSeg = "";
560
+ if (box?.sessionTitle && room >= 7) {
561
+ titleSeg = ` \u2014 ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;
562
+ }
563
+ const leftPlain = brandPrefix + base + titleSeg + (box ? " " : "");
564
+ const leftStyled = BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? " " : "") + BRAND_NOBOLD;
565
+ const gap = w - leftPlain.length - hints.plain.length;
566
+ return BAR_BASE + leftStyled + BAR_BASE + " ".repeat(gap) + hints.styled + BAR_RESET;
567
+ }
568
+
569
+ // src/wrapped-pty/footer.ts
570
+ var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
571
+ var URGENT = "\x1B[38;5;220m\x1B[1m";
572
+ var TXT = "\x1B[38;5;250m";
573
+ var SUBTLE = "\x1B[38;5;245m";
574
+ var RESET = "\x1B[0m";
575
+ var NOTICE_BG = "\x1B[48;5;220m";
576
+ var NOTICE_FG = "\x1B[38;5;16m\x1B[1m";
577
+ var FLASH_FG = "\x1B[38;5;150m\x1B[1m";
578
+ var COLLAPSED_HINTS_PLAIN = [
579
+ ["Control+a", "Actions"]
580
+ ];
581
+ var COLLAPSED_HINTS_DETACHABLE = [
582
+ ["Control+a", "Actions"],
583
+ ["Control+a d", "detach"]
584
+ ];
585
+ var DETACH_PIN_HINTS = [
586
+ ["Control+a d", "detach"]
587
+ ];
588
+ var DETACHABLE_LEADER_HINTS = [
589
+ ["c", "code"],
590
+ ["s", "screen"],
591
+ ["u", "url"],
592
+ ["d", "detach"]
593
+ ];
594
+ var PLAIN_LEADER_HINTS = [
595
+ ["c", "code"],
596
+ ["s", "screen"],
597
+ ["u", "url"]
598
+ ];
599
+ function padTo(visible, width) {
600
+ if (visible.length === width) return visible;
601
+ if (visible.length > width) {
602
+ if (width <= 1) return visible.slice(0, width);
603
+ return visible.slice(0, width - 1) + "\u2026";
604
+ }
605
+ return visible + " ".repeat(width - visible.length);
606
+ }
607
+ function renderFooter(state, cols) {
608
+ if (cols <= 0) return "";
609
+ if (state.kind === "idle") {
610
+ const sidebarBox = {
611
+ id: "",
612
+ // unused by statusLine
613
+ name: state.boxName,
614
+ state: "running",
615
+ // we're attached, so the container is up
616
+ activity: state.claudeActivity,
617
+ sessionTitle: state.sessionTitle
618
+ };
619
+ const isClaude = state.mode === "claude";
620
+ const detachable = state.detachable ?? isClaude;
621
+ const stateLabel = isClaude ? void 0 : state.mode === "shell" ? "shell" : state.mode;
622
+ if (state.leaderActive) {
623
+ const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;
624
+ return statusLine(sidebarBox, cols, stateLabel, leaderHints);
625
+ }
626
+ const collapsed = detachable ? COLLAPSED_HINTS_DETACHABLE : COLLAPSED_HINTS_PLAIN;
627
+ const fallback = detachable ? DETACH_PIN_HINTS : void 0;
628
+ return statusLine(sidebarBox, cols, stateLabel, collapsed, fallback);
629
+ }
630
+ if (state.kind === "flash") {
631
+ const prefix = " \u25B8 ";
632
+ const inner2 = Math.max(0, cols - prefix.length);
633
+ const message2 = padTo(state.message, inner2);
634
+ return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message2}${RESET}`;
635
+ }
636
+ if (state.kind === "notice") {
637
+ const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length];
638
+ const prefix = ` ${spinner} `;
639
+ const inner2 = Math.max(0, cols - prefix.length);
640
+ const message2 = padTo(state.message, inner2);
641
+ return `${NOTICE_BG}${NOTICE_FG}${prefix}${message2}${RESET}`;
642
+ }
643
+ const def = state.prompt.defaultAnswer ?? "n";
644
+ const yn = def === "y" ? "[Y/n]" : "[y/N]";
645
+ const tag = " [!] ";
646
+ const sep = " ";
647
+ const hintW = ` ${yn} `.length;
648
+ const inner = Math.max(0, cols - tag.length - hintW);
649
+ const detailRaw = state.prompt.detail ?? "";
650
+ let message = state.prompt.message;
651
+ let detail = detailRaw;
652
+ const messageBudget = Math.max(8, inner - (detail.length > 0 ? sep.length + 8 : 0));
653
+ if (message.length > messageBudget) {
654
+ message = message.slice(0, Math.max(0, messageBudget - 1)) + "\u2026";
655
+ }
656
+ const usedByMessage = message.length;
657
+ const detailBudget = Math.max(0, inner - usedByMessage - sep.length);
658
+ if (detail.length > detailBudget) {
659
+ detail = detailBudget <= 1 ? "" : detail.slice(0, detailBudget - 1) + "\u2026";
660
+ }
661
+ const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;
662
+ const padded = padTo(middlePlain, inner);
663
+ return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${SUBTLE} ${yn} ${RESET}`;
664
+ }
665
+ function cursorMoveTo(row, col) {
666
+ return `\x1B[${String(row)};${String(col)}H`;
667
+ }
668
+ var CURSOR_SAVE = "\x1B7";
669
+ var CURSOR_RESTORE = "\x1B8";
670
+ var SYNC_BEGIN = "\x1B[?2026h";
671
+ var SYNC_END = "\x1B[?2026l";
672
+
673
+ // src/wrapped-pty/prompt-client.ts
674
+ import { request as httpRequest } from "http";
675
+ import { request as httpsRequest } from "https";
676
+ var INITIAL_BACKOFF_MS = 200;
677
+ var MAX_BACKOFF_MS = 5e3;
678
+ function subscribePrompts(opts) {
679
+ let closed = false;
680
+ let req = null;
681
+ let res = null;
682
+ let reconnectTimer = null;
683
+ let backoffMs = INITIAL_BACKOFF_MS;
684
+ let url;
685
+ try {
686
+ url = new URL(opts.relayBaseUrl);
687
+ } catch (err) {
688
+ if (opts.onError) opts.onError(err instanceof Error ? err : new Error(String(err)));
689
+ return { close: () => {
690
+ } };
691
+ }
692
+ const isHttps = url.protocol === "https:";
693
+ const transport = isHttps ? httpsRequest : httpRequest;
694
+ const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
695
+ function scheduleReconnect() {
696
+ if (closed) return;
697
+ const delay = backoffMs;
698
+ backoffMs = Math.min(MAX_BACKOFF_MS, backoffMs * 2);
699
+ reconnectTimer = setTimeout(() => {
700
+ reconnectTimer = null;
701
+ connect();
702
+ }, delay);
703
+ if (typeof reconnectTimer.unref === "function") reconnectTimer.unref();
704
+ }
705
+ let buffer = "";
706
+ function consumeMessages() {
707
+ let idx = buffer.indexOf("\n\n");
708
+ while (idx !== -1) {
709
+ const raw = buffer.slice(0, idx);
710
+ buffer = buffer.slice(idx + 2);
711
+ idx = buffer.indexOf("\n\n");
712
+ if (raw.startsWith(":")) continue;
713
+ let event = "";
714
+ let dataLine = "";
715
+ for (const line of raw.split("\n")) {
716
+ if (line.startsWith("event:")) event = line.slice("event:".length).trim();
717
+ else if (line.startsWith("data:")) dataLine = line.slice("data:".length).trim();
718
+ }
719
+ if (event === "prompt-ask" && dataLine.length > 0) {
720
+ try {
721
+ const ev = JSON.parse(dataLine);
722
+ if (ev && typeof ev.id === "string") opts.onPrompt(ev);
723
+ } catch {
724
+ }
725
+ } else if (event === "prompt-resolved" && dataLine.length > 0) {
726
+ try {
727
+ const payload = JSON.parse(dataLine);
728
+ if (payload && typeof payload.id === "string") opts.onResolved(payload.id);
729
+ } catch {
730
+ }
731
+ } else if (event === "notice-set" && dataLine.length > 0) {
732
+ try {
733
+ const ev = JSON.parse(dataLine);
734
+ if (ev && typeof ev.id === "string") opts.onNotice?.(ev);
735
+ } catch {
736
+ }
737
+ } else if (event === "notice-clear" && dataLine.length > 0) {
738
+ try {
739
+ const payload = JSON.parse(dataLine);
740
+ if (payload && typeof payload.id === "string") opts.onNoticeCleared?.(payload.id);
741
+ } catch {
742
+ }
743
+ }
744
+ }
745
+ }
746
+ function connect() {
747
+ if (closed) return;
748
+ req = transport({
749
+ host: url.hostname,
750
+ port,
751
+ method: "GET",
752
+ path: `${url.pathname.replace(/\/$/, "")}/admin/prompts/stream?boxId=${encodeURIComponent(opts.boxId)}`,
753
+ headers: { Accept: "text/event-stream" }
754
+ });
755
+ req.on("response", (r) => {
756
+ res = r;
757
+ if (r.statusCode !== 200) {
758
+ if (opts.onError) opts.onError(new Error(`SSE stream returned ${String(r.statusCode)}`));
759
+ r.resume();
760
+ close();
761
+ return;
762
+ }
763
+ backoffMs = INITIAL_BACKOFF_MS;
764
+ r.setEncoding("utf8");
765
+ r.on("data", (chunk) => {
766
+ buffer += chunk;
767
+ consumeMessages();
768
+ });
769
+ r.on("end", () => {
770
+ if (!closed) scheduleReconnect();
771
+ });
772
+ r.on("error", () => {
773
+ if (!closed) scheduleReconnect();
774
+ });
775
+ });
776
+ req.on("error", () => {
777
+ if (!closed) scheduleReconnect();
778
+ });
779
+ req.end();
780
+ }
781
+ function close() {
782
+ if (closed) return;
783
+ closed = true;
784
+ if (reconnectTimer) clearTimeout(reconnectTimer);
785
+ try {
786
+ res?.destroy();
787
+ } catch {
788
+ }
789
+ try {
790
+ req?.destroy();
791
+ } catch {
792
+ }
793
+ }
794
+ connect();
795
+ return { close };
796
+ }
797
+ function postAnswer(opts) {
798
+ return new Promise((resolve) => {
799
+ let url;
800
+ try {
801
+ url = new URL(opts.relayBaseUrl);
802
+ } catch {
803
+ resolve({ ok: false, status: 0 });
804
+ return;
805
+ }
806
+ const isHttps = url.protocol === "https:";
807
+ const transport = isHttps ? httpsRequest : httpRequest;
808
+ const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
809
+ const json = JSON.stringify(opts.body);
810
+ const req = transport(
811
+ {
812
+ host: url.hostname,
813
+ port,
814
+ method: "POST",
815
+ path: `${url.pathname.replace(/\/$/, "")}/admin/prompts/answer`,
816
+ headers: {
817
+ "Content-Type": "application/json",
818
+ "Content-Length": Buffer.byteLength(json).toString()
819
+ },
820
+ timeout: 3e3
821
+ },
822
+ (res) => {
823
+ res.resume();
824
+ const status = res.statusCode ?? 0;
825
+ resolve({ ok: status === 204 || status === 404, status });
826
+ }
827
+ );
828
+ req.on("error", () => resolve({ ok: false, status: 0 }));
829
+ req.on("timeout", () => {
830
+ req.destroy();
831
+ resolve({ ok: false, status: 0 });
832
+ });
833
+ req.write(json);
834
+ req.end();
835
+ });
836
+ }
837
+
838
+ // src/wrapped-pty/run.ts
839
+ var FOOTER_ROWS = 1;
840
+ var STATUS_POLL_INTERVAL_MS = 3e3;
841
+ var SPINNER_INTERVAL_MS = 120;
842
+ var FLASH_DURATION_MS = 2e3;
843
+ var ACTION_FLASH = {
844
+ screen: "Opening noVNC viewer\u2026",
845
+ code: "Launching VS Code / Cursor\u2026",
846
+ url: "Opening box URL\u2026"
847
+ };
848
+ var ACTION_CMD = {
849
+ screen: { sub: "screen", flags: [] },
850
+ // --no-wait: don't block on `wait-ready` — the box is already running.
851
+ code: { sub: "code", flags: ["--no-wait"] },
852
+ url: { sub: "url", flags: [] }
853
+ };
854
+ function buildAgentboxAttachArgv(mode, boxName) {
855
+ if (mode !== "claude" && mode !== "codex" && mode !== "opencode") return null;
856
+ return [mode, "attach", boxName, "--attach-in", "same"];
857
+ }
858
+ async function runWrappedAttach(opts) {
859
+ const command = opts.command ?? "docker";
860
+ const logErr = (msg) => {
861
+ opts.onError?.(msg);
862
+ };
863
+ const openIn = opts.openIn ?? "same";
864
+ if (openIn !== "same") {
865
+ const subArgv = buildAgentboxAttachArgv(opts.mode, opts.boxName);
866
+ const host = subArgv ? detectHostTerminal() : "unknown";
867
+ if (subArgv && host !== "unknown" && process.argv[1]) {
868
+ const r = await spawnInNewTerminal({
869
+ host,
870
+ mode: openIn,
871
+ argv: [process.execPath, process.argv[1], ...subArgv],
872
+ cwd: process.cwd(),
873
+ title: opts.boxName
874
+ });
875
+ if (r.launched) {
876
+ process.stdout.write(r.note + "\n");
877
+ return 0;
878
+ }
879
+ if (r.error) logErr(r.error);
880
+ }
881
+ }
882
+ if (!process.stdout.isTTY || !process.stdin.isTTY) {
883
+ return runFallback(command, opts.dockerArgv);
884
+ }
885
+ const backend = await loadPtyBackend();
886
+ if (!backend) {
887
+ process.stderr.write(
888
+ "agentbox: permission prompts disabled (node-pty backend unavailable)\n"
889
+ );
890
+ return runFallback(command, opts.dockerArgv);
891
+ }
892
+ const cols = process.stdout.columns ?? 80;
893
+ const rows = process.stdout.rows ?? 24;
894
+ const innerRows = Math.max(1, rows - FOOTER_ROWS);
895
+ const pty = backend.ptySpawn(command, opts.dockerArgv, {
896
+ name: "xterm-256color",
897
+ cols,
898
+ rows: innerRows,
899
+ env: process.env
900
+ });
901
+ const detachable = opts.detachable ?? opts.mode === "claude";
902
+ let leaderActive = false;
903
+ const buildIdle = (sessionTitle, claudeActivity) => ({
904
+ kind: "idle",
905
+ boxName: opts.boxName,
906
+ sessionTitle,
907
+ claudeActivity,
908
+ mode: opts.mode,
909
+ detachable,
910
+ leaderActive
911
+ });
912
+ let footerState = buildIdle();
913
+ let lastSessionTitle;
914
+ let lastActivity;
915
+ let capturingPrompt = null;
916
+ let activeNotice = null;
917
+ let noticeFrame = 0;
918
+ let spinnerTimer = null;
919
+ let flashMessage = null;
920
+ let flashTimer = null;
921
+ const redrawFooter = () => {
922
+ const cs = process.stdout.columns ?? cols;
923
+ const rs = process.stdout.rows ?? rows;
924
+ const line = renderFooter(footerState, cs);
925
+ const payload = SYNC_BEGIN + CURSOR_SAVE + cursorMoveTo(rs, 1) + line + CURSOR_RESTORE + SYNC_END;
926
+ process.stdout.write(payload);
927
+ };
928
+ const recomputeFooter = () => {
929
+ if (capturingPrompt) {
930
+ footerState = { kind: "prompt", prompt: capturingPrompt };
931
+ } else if (activeNotice) {
932
+ footerState = { kind: "notice", message: activeNotice.message, frame: noticeFrame };
933
+ } else if (flashMessage) {
934
+ footerState = { kind: "flash", message: flashMessage };
935
+ } else {
936
+ footerState = buildIdle(lastSessionTitle, lastActivity);
937
+ }
938
+ };
939
+ const startSpinner = () => {
940
+ if (spinnerTimer) return;
941
+ spinnerTimer = setInterval(() => {
942
+ noticeFrame++;
943
+ if (footerState.kind === "notice") {
944
+ recomputeFooter();
945
+ redrawFooter();
946
+ }
947
+ }, SPINNER_INTERVAL_MS);
948
+ if (typeof spinnerTimer.unref === "function") spinnerTimer.unref();
949
+ };
950
+ const stopSpinner = () => {
951
+ if (spinnerTimer) {
952
+ clearInterval(spinnerTimer);
953
+ spinnerTimer = null;
954
+ }
955
+ };
956
+ pty.onData((d) => {
957
+ process.stdout.write(d);
958
+ redrawFooter();
959
+ });
960
+ const leaderChords = detachable ? { c: "code", s: "screen", u: "url", d: "detach" } : { c: "code", s: "screen", u: "url" };
961
+ const runAction = (name) => {
962
+ if (name === "detach") {
963
+ pty.write("d");
964
+ return;
965
+ }
966
+ const cliEntry = process.argv[1];
967
+ if (typeof cliEntry === "string" && cliEntry.length > 0) {
968
+ const cmd = ACTION_CMD[name];
969
+ try {
970
+ spawn2(
971
+ process.execPath,
972
+ [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],
973
+ { detached: true, stdio: "ignore" }
974
+ ).unref();
975
+ } catch (e) {
976
+ logErr(`leader-action spawn (${name}) failed: ${e.message}`);
977
+ }
978
+ }
979
+ flashMessage = ACTION_FLASH[name];
980
+ if (flashTimer) clearTimeout(flashTimer);
981
+ flashTimer = setTimeout(() => {
982
+ flashTimer = null;
983
+ flashMessage = null;
984
+ recomputeFooter();
985
+ redrawFooter();
986
+ }, FLASH_DURATION_MS);
987
+ if (typeof flashTimer.unref === "function") flashTimer.unref();
988
+ recomputeFooter();
989
+ redrawFooter();
990
+ };
991
+ const router = createInputRouter({
992
+ onForward: (b) => {
993
+ pty.write(b.toString("utf8"));
994
+ },
995
+ onAnswer: (body) => {
996
+ void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
997
+ capturingPrompt = null;
998
+ recomputeFooter();
999
+ redrawFooter();
1000
+ },
1001
+ leaderChords,
1002
+ onLeaderChange: (open) => {
1003
+ leaderActive = open;
1004
+ recomputeFooter();
1005
+ redrawFooter();
1006
+ },
1007
+ onAction: (name) => {
1008
+ runAction(name);
1009
+ }
1010
+ });
1011
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
1012
+ process.stdin.resume();
1013
+ const onStdinData = (chunk) => {
1014
+ router.feed(chunk);
1015
+ };
1016
+ process.stdin.on("data", onStdinData);
1017
+ const onResize = () => {
1018
+ const cs = process.stdout.columns ?? cols;
1019
+ const rs = process.stdout.rows ?? rows;
1020
+ const inner = Math.max(1, rs - FOOTER_ROWS);
1021
+ pty.resize(cs, inner);
1022
+ process.stdout.write(`\x1B[1;${String(inner)}r`);
1023
+ redrawFooter();
1024
+ };
1025
+ process.stdout.on("resize", onResize);
1026
+ const stream = subscribePrompts({
1027
+ relayBaseUrl: opts.relayBaseUrl,
1028
+ boxId: opts.boxId,
1029
+ onPrompt: (ev) => {
1030
+ capturingPrompt = ev;
1031
+ recomputeFooter();
1032
+ redrawFooter();
1033
+ router.capture(ev).catch((e) => {
1034
+ const msg = e instanceof Error ? e.message : String(e);
1035
+ if (msg !== "resolved-elsewhere") {
1036
+ logErr(`prompt capture rejected: ${msg}`);
1037
+ }
1038
+ });
1039
+ },
1040
+ onResolved: (id) => {
1041
+ if (capturingPrompt && capturingPrompt.id === id) {
1042
+ capturingPrompt = null;
1043
+ router.abort("resolved-elsewhere");
1044
+ recomputeFooter();
1045
+ redrawFooter();
1046
+ }
1047
+ },
1048
+ onNotice: (ev) => {
1049
+ activeNotice = ev;
1050
+ startSpinner();
1051
+ recomputeFooter();
1052
+ redrawFooter();
1053
+ },
1054
+ onNoticeCleared: (id) => {
1055
+ if (activeNotice && activeNotice.id === id) {
1056
+ activeNotice = null;
1057
+ stopSpinner();
1058
+ recomputeFooter();
1059
+ redrawFooter();
1060
+ }
1061
+ }
1062
+ });
1063
+ const pollStatus = async () => {
1064
+ try {
1065
+ const status = await readBoxStatus({
1066
+ id: opts.boxId,
1067
+ name: opts.boxName,
1068
+ projectIndex: opts.projectIndex
1069
+ });
1070
+ const nextTitle = status?.claude?.sessionTitle?.trim() || void 0;
1071
+ const nextActivity = status?.claude?.state || void 0;
1072
+ if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;
1073
+ lastSessionTitle = nextTitle;
1074
+ lastActivity = nextActivity;
1075
+ if (footerState.kind === "idle") {
1076
+ recomputeFooter();
1077
+ redrawFooter();
1078
+ }
1079
+ } catch (e) {
1080
+ logErr(`status poll failed: ${e.message}`);
1081
+ }
1082
+ };
1083
+ void pollStatus();
1084
+ const statusTimer = setInterval(() => {
1085
+ void pollStatus();
1086
+ }, STATUS_POLL_INTERVAL_MS);
1087
+ if (typeof statusTimer.unref === "function") statusTimer.unref();
1088
+ process.stdout.write(`\x1B[1;${String(innerRows)}r`);
1089
+ if (opts.mode === "shell" && !detachable) {
1090
+ process.stdout.write("\x1B[H\x1B[2J");
1091
+ }
1092
+ redrawFooter();
1093
+ const exitCode = await new Promise((resolve) => {
1094
+ pty.onExit(({ exitCode: exitCode2 }) => resolve(exitCode2));
1095
+ });
1096
+ process.stdin.off("data", onStdinData);
1097
+ process.stdout.off("resize", onResize);
1098
+ clearInterval(statusTimer);
1099
+ stopSpinner();
1100
+ if (flashTimer) clearTimeout(flashTimer);
1101
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
1102
+ process.stdin.pause();
1103
+ stream.close();
1104
+ router.dispose();
1105
+ const rsFinal = process.stdout.rows ?? rows;
1106
+ const csFinal = process.stdout.columns ?? cols;
1107
+ process.stdout.write(
1108
+ "\x1B[r" + cursorMoveTo(rsFinal, 1) + `\x1B[2K` + cursorMoveTo(rsFinal, csFinal)
1109
+ );
1110
+ if (exitCode === 0 && opts.detachNotice) {
1111
+ process.stdout.write("\x1B[1A\x1B[2K\r" + opts.detachNotice + "\n");
1112
+ }
1113
+ return exitCode;
1114
+ }
1115
+ function runFallback(command, argv) {
1116
+ const child = spawnSync(command, argv, { stdio: "inherit" });
1117
+ return child.status ?? 0;
1118
+ }
1119
+
1120
+ // src/commands/_cloud-attach.ts
1121
+ var RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
1122
+ function buildCloudAttachInnerCommand(binary, extraArgs) {
1123
+ if (!extraArgs || extraArgs.length === 0) {
1124
+ return `bash -lc exec\\ ${binary}`;
1125
+ }
1126
+ const blob = Buffer.from(extraArgs.join("\n"), "utf8").toString("base64");
1127
+ return `bash -lc 'mapfile -t A < <(echo ${blob} | base64 -d); exec ${binary} "\${A[@]}"'`;
1128
+ }
1129
+ async function cloudAgentAttach(args) {
1130
+ const provider = await providerForBox(args.box);
1131
+ if (!provider.buildAttach) {
1132
+ throw new Error(`provider '${provider.name}' does not support interactive attach`);
1133
+ }
1134
+ const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);
1135
+ const safeOpenIn = args.box.provider === "daytona" ? "same" : args.openIn;
1136
+ if (safeOpenIn && safeOpenIn !== "same" && args.extraArgs && args.extraArgs.length > 0) {
1137
+ const pre = await provider.buildAttach(args.box, "agent", {
1138
+ sessionName: args.sessionName,
1139
+ command,
1140
+ detached: true
1141
+ });
1142
+ try {
1143
+ await runDetached(pre.argv);
1144
+ } finally {
1145
+ if (pre.cleanup) await pre.cleanup();
1146
+ }
1147
+ }
1148
+ const spec = await provider.buildAttach(args.box, "agent", {
1149
+ sessionName: args.sessionName,
1150
+ command
1151
+ });
1152
+ try {
1153
+ const code = await runWrappedAttach({
1154
+ container: args.box.name,
1155
+ command: spec.argv[0],
1156
+ dockerArgv: spec.argv.slice(1),
1157
+ relayBaseUrl: RELAY_HOST_URL,
1158
+ boxId: args.box.id,
1159
+ boxName: args.box.name,
1160
+ projectIndex: args.box.projectIndex,
1161
+ mode: args.mode,
1162
+ detachable: true,
1163
+ openIn: safeOpenIn
1164
+ });
1165
+ process.exit(code);
1166
+ } finally {
1167
+ if (spec.cleanup) await spec.cleanup();
1168
+ }
1169
+ }
1170
+ function runDetached(argv) {
1171
+ return new Promise((resolve) => {
1172
+ const child = spawn3(argv[0], argv.slice(1), { stdio: "ignore" });
1173
+ child.on("error", () => resolve());
1174
+ child.on("exit", () => resolve());
1175
+ });
1176
+ }
1177
+
1178
+ export {
1179
+ isKnownProvider,
1180
+ getProvider,
1181
+ providerForBox,
1182
+ providerForCreate,
1183
+ loadPtyBackend,
1184
+ detectHostTerminal,
1185
+ NEW_BOX_ID,
1186
+ NEW_BOX_LABEL,
1187
+ sidebarLines,
1188
+ menuLines,
1189
+ lifecycleMenuLines,
1190
+ createMenuLines,
1191
+ ADVANCED_HINT_GROUPS,
1192
+ statusLine,
1193
+ renderFooter,
1194
+ subscribePrompts,
1195
+ postAnswer,
1196
+ runWrappedAttach,
1197
+ buildCloudAttachInnerCommand,
1198
+ cloudAgentAttach
1199
+ };
1200
+ //# sourceMappingURL=chunk-FODMEHD3.js.map