@madarco/agentbox 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-7J5AJLWG.js → chunk-BBZMA2K6.js} +3 -3
- package/dist/{chunk-RFC5F5HR.js → chunk-HHMWQNLF.js} +8 -8
- package/dist/chunk-HHMWQNLF.js.map +1 -0
- package/dist/{chunk-PXUBE5KS.js → chunk-HTTKML3C.js} +351 -42
- package/dist/chunk-HTTKML3C.js.map +1 -0
- package/dist/{chunk-6VTAPD4H.js → chunk-KJNZP6I3.js} +100 -21
- package/dist/chunk-KJNZP6I3.js.map +1 -0
- package/dist/{chunk-FJNIFTWK.js → chunk-M7I247BK.js} +6 -4
- package/dist/chunk-M7I247BK.js.map +1 -0
- package/dist/{create-AHZ3GVEZ-TGEDL7UX.js → create-6PWXI6HO-OWAMHBAK.js} +4 -4
- package/dist/index.js +310 -102
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-LFOL6YFM-TCHDX3J5.js → lifecycle-EMXR46DI-DUVBXNTV.js} +4 -4
- package/dist/{stats-Z4BVJODD-HEC4TMUZ.js → stats-SZXOJE3D-N7OODCHW.js} +3 -3
- package/package.json +4 -4
- package/runtime/docker/Dockerfile.box +23 -11
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +19 -11
- package/runtime/docker/packages/ctl/dist/bin.cjs +56 -15
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +13 -3
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +4 -9
- package/runtime/relay/bin.cjs +121 -2
- package/share/agentbox-setup/SKILL.md +19 -11
- package/dist/chunk-6VTAPD4H.js.map +0 -1
- package/dist/chunk-FJNIFTWK.js.map +0 -1
- package/dist/chunk-PXUBE5KS.js.map +0 -1
- package/dist/chunk-RFC5F5HR.js.map +0 -1
- /package/dist/{chunk-7J5AJLWG.js.map → chunk-BBZMA2K6.js.map} +0 -0
- /package/dist/{create-AHZ3GVEZ-TGEDL7UX.js.map → create-6PWXI6HO-OWAMHBAK.js.map} +0 -0
- /package/dist/{lifecycle-LFOL6YFM-TCHDX3J5.js.map → lifecycle-EMXR46DI-DUVBXNTV.js.map} +0 -0
- /package/dist/{stats-Z4BVJODD-HEC4TMUZ.js.map → stats-SZXOJE3D-N7OODCHW.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
createBox
|
|
4
|
-
|
|
3
|
+
createBox,
|
|
4
|
+
hostBackupHasCredentials,
|
|
5
|
+
syncClaudeCredentials
|
|
6
|
+
} from "./chunk-KJNZP6I3.js";
|
|
5
7
|
import {
|
|
6
8
|
AmbiguousBoxError,
|
|
7
9
|
BoxNotFoundError,
|
|
@@ -15,16 +17,18 @@ import {
|
|
|
15
17
|
startBox,
|
|
16
18
|
stopBox,
|
|
17
19
|
unpauseBox
|
|
18
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-M7I247BK.js";
|
|
19
21
|
import {
|
|
20
22
|
ClaudeSessionError,
|
|
21
23
|
DEFAULT_RELAY_PORT,
|
|
22
24
|
SHARED_CLAUDE_VOLUME,
|
|
23
25
|
buildClaudeAttachArgv,
|
|
24
26
|
buildClaudeDashboardAttachArgv,
|
|
27
|
+
buildClaudeLoginRunArgv,
|
|
25
28
|
buildShellArgv,
|
|
26
29
|
buildVncUrls,
|
|
27
30
|
claudeSessionInfo,
|
|
31
|
+
clearRelayNotice,
|
|
28
32
|
containerHex,
|
|
29
33
|
ensureAgentboxTasksFile,
|
|
30
34
|
ensureClaudeVolume,
|
|
@@ -38,10 +42,13 @@ import {
|
|
|
38
42
|
renderStatusTable,
|
|
39
43
|
renderTaskTable,
|
|
40
44
|
resolveClaudeVolume,
|
|
45
|
+
runInteractiveClaudeLogin,
|
|
41
46
|
seedSetupSkillIntoVolume,
|
|
47
|
+
setRelayNotice,
|
|
42
48
|
startClaudeSession,
|
|
43
|
-
stopRelay
|
|
44
|
-
|
|
49
|
+
stopRelay,
|
|
50
|
+
warmUpClaudeCredentials
|
|
51
|
+
} from "./chunk-HTTKML3C.js";
|
|
45
52
|
import {
|
|
46
53
|
STATE_DIR,
|
|
47
54
|
readState,
|
|
@@ -52,7 +59,7 @@ import {
|
|
|
52
59
|
allCheckpointImagesBytes,
|
|
53
60
|
boxResourceStats,
|
|
54
61
|
projectCheckpointImageBytes
|
|
55
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-BBZMA2K6.js";
|
|
56
63
|
import {
|
|
57
64
|
DEFAULT_BOX_IMAGE,
|
|
58
65
|
DEFAULT_ENV_PATTERNS,
|
|
@@ -62,6 +69,7 @@ import {
|
|
|
62
69
|
configPathFor,
|
|
63
70
|
createCheckpoint,
|
|
64
71
|
detectEngine,
|
|
72
|
+
ensureImage,
|
|
65
73
|
execInBox,
|
|
66
74
|
findProjectRoot,
|
|
67
75
|
listCheckpoints,
|
|
@@ -78,7 +86,7 @@ import {
|
|
|
78
86
|
setConfigValue,
|
|
79
87
|
setEngineOverride,
|
|
80
88
|
unsetConfigValue
|
|
81
|
-
} from "./chunk-
|
|
89
|
+
} from "./chunk-HHMWQNLF.js";
|
|
82
90
|
|
|
83
91
|
// src/index.ts
|
|
84
92
|
import { Command as Command29 } from "commander";
|
|
@@ -302,13 +310,12 @@ var browserCommand = new Command("browser").description(
|
|
|
302
310
|
});
|
|
303
311
|
|
|
304
312
|
// src/commands/claude.ts
|
|
305
|
-
import { confirm as confirm2, intro, isCancel as isCancel2, log as log5, outro,
|
|
313
|
+
import { confirm as confirm2, intro, isCancel as isCancel2, log as log5, outro, spinner } from "@clack/prompts";
|
|
306
314
|
import { Command as Command2 } from "commander";
|
|
307
315
|
|
|
308
316
|
// src/auth.ts
|
|
309
|
-
import {
|
|
310
|
-
import {
|
|
311
|
-
import { dirname, join } from "path";
|
|
317
|
+
import { readFile } from "fs/promises";
|
|
318
|
+
import { join } from "path";
|
|
312
319
|
var AUTH_FILE = join(STATE_DIR, "auth.json");
|
|
313
320
|
async function resolveClaudeAuth(processEnv, opts = {}) {
|
|
314
321
|
const env = {};
|
|
@@ -340,22 +347,6 @@ async function readAuthFile(path2 = AUTH_FILE) {
|
|
|
340
347
|
return {};
|
|
341
348
|
}
|
|
342
349
|
}
|
|
343
|
-
async function writeAuthFile(next, path2 = AUTH_FILE) {
|
|
344
|
-
await mkdir(dirname(path2), { recursive: true });
|
|
345
|
-
await writeFile(path2, JSON.stringify(next, null, 2) + "\n", { mode: 384, flag: "w" });
|
|
346
|
-
}
|
|
347
|
-
function hostClaudeAvailable() {
|
|
348
|
-
const r = spawnSync2("which", ["claude"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
349
|
-
return r.status === 0 && (r.stdout?.toString().trim().length ?? 0) > 0;
|
|
350
|
-
}
|
|
351
|
-
function runHostSetupToken() {
|
|
352
|
-
const child = spawnSync2("claude", ["setup-token"], { stdio: "inherit" });
|
|
353
|
-
return { exitCode: child.status ?? -1 };
|
|
354
|
-
}
|
|
355
|
-
function isPlausibleOauthToken(s) {
|
|
356
|
-
const t = s.trim();
|
|
357
|
-
return t.startsWith("sk-ant-oat") && t.length >= 40;
|
|
358
|
-
}
|
|
359
350
|
|
|
360
351
|
// ../../packages/core/dist/index.js
|
|
361
352
|
var claudeCodeLauncher = {
|
|
@@ -515,7 +506,7 @@ function passthroughFlags(opts) {
|
|
|
515
506
|
}
|
|
516
507
|
|
|
517
508
|
// src/wrapped-pty/run.ts
|
|
518
|
-
import { spawnSync as
|
|
509
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
519
510
|
|
|
520
511
|
// src/pty/pty-backend.ts
|
|
521
512
|
async function loadPtyBackend() {
|
|
@@ -644,6 +635,7 @@ function ellipsizeHead(s, max) {
|
|
|
644
635
|
}
|
|
645
636
|
function activityCell(b) {
|
|
646
637
|
if (b.pendingPrompt) return "\u25B2 prompt";
|
|
638
|
+
if (b.checkpointing) return "\u25C6 checkpoint";
|
|
647
639
|
if (b.state !== "running") return `[${b.state}]`;
|
|
648
640
|
switch (b.claudeActivity) {
|
|
649
641
|
case "working":
|
|
@@ -842,10 +834,13 @@ function statusLine(box, w, stateLabel, groups = HINT_GROUPS) {
|
|
|
842
834
|
}
|
|
843
835
|
|
|
844
836
|
// src/wrapped-pty/footer.ts
|
|
837
|
+
var SPINNER_FRAMES = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
845
838
|
var URGENT = "\x1B[38;5;220m\x1B[1m";
|
|
846
839
|
var TXT = "\x1B[38;5;250m";
|
|
847
840
|
var SUBTLE = "\x1B[38;5;245m";
|
|
848
841
|
var RESET = "\x1B[0m";
|
|
842
|
+
var NOTICE_BG = "\x1B[48;5;220m";
|
|
843
|
+
var NOTICE_FG = "\x1B[38;5;16m\x1B[1m";
|
|
849
844
|
var CLAUDE_IDLE_HINTS = [
|
|
850
845
|
["Control+a q", "detach"]
|
|
851
846
|
];
|
|
@@ -874,6 +869,13 @@ function renderFooter(state, cols) {
|
|
|
874
869
|
const stateLabel = state.mode === "shell" ? "shell" : void 0;
|
|
875
870
|
return statusLine(sidebarBox, cols, stateLabel, hints);
|
|
876
871
|
}
|
|
872
|
+
if (state.kind === "notice") {
|
|
873
|
+
const spinner5 = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length];
|
|
874
|
+
const prefix = ` ${spinner5} `;
|
|
875
|
+
const inner2 = Math.max(0, cols - prefix.length);
|
|
876
|
+
const message2 = padTo(state.message, inner2);
|
|
877
|
+
return `${NOTICE_BG}${NOTICE_FG}${prefix}${message2}${RESET}`;
|
|
878
|
+
}
|
|
877
879
|
const def = state.prompt.defaultAnswer ?? "n";
|
|
878
880
|
const yn = def === "y" ? "[Y/n]" : "[y/N]";
|
|
879
881
|
const tag2 = " [!] ";
|
|
@@ -962,6 +964,18 @@ function subscribePrompts(opts) {
|
|
|
962
964
|
if (payload && typeof payload.id === "string") opts.onResolved(payload.id);
|
|
963
965
|
} catch {
|
|
964
966
|
}
|
|
967
|
+
} else if (event === "notice-set" && dataLine.length > 0) {
|
|
968
|
+
try {
|
|
969
|
+
const ev = JSON.parse(dataLine);
|
|
970
|
+
if (ev && typeof ev.id === "string") opts.onNotice?.(ev);
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
} else if (event === "notice-clear" && dataLine.length > 0) {
|
|
974
|
+
try {
|
|
975
|
+
const payload = JSON.parse(dataLine);
|
|
976
|
+
if (payload && typeof payload.id === "string") opts.onNoticeCleared?.(payload.id);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
965
979
|
}
|
|
966
980
|
}
|
|
967
981
|
}
|
|
@@ -1060,6 +1074,7 @@ function postAnswer(opts) {
|
|
|
1060
1074
|
// src/wrapped-pty/run.ts
|
|
1061
1075
|
var FOOTER_ROWS = 1;
|
|
1062
1076
|
var STATUS_POLL_INTERVAL_MS = 3e3;
|
|
1077
|
+
var SPINNER_INTERVAL_MS = 120;
|
|
1063
1078
|
async function runWrappedAttach(opts) {
|
|
1064
1079
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
1065
1080
|
return runFallback(opts.dockerArgv);
|
|
@@ -1090,6 +1105,10 @@ async function runWrappedAttach(opts) {
|
|
|
1090
1105
|
let footerState = buildIdle();
|
|
1091
1106
|
let lastSessionTitle;
|
|
1092
1107
|
let lastActivity;
|
|
1108
|
+
let capturingPrompt = null;
|
|
1109
|
+
let activeNotice = null;
|
|
1110
|
+
let noticeFrame = 0;
|
|
1111
|
+
let spinnerTimer = null;
|
|
1093
1112
|
const redrawFooter = () => {
|
|
1094
1113
|
const cs = process.stdout.columns ?? cols;
|
|
1095
1114
|
const rs = process.stdout.rows ?? rows;
|
|
@@ -1097,6 +1116,32 @@ async function runWrappedAttach(opts) {
|
|
|
1097
1116
|
const payload = SYNC_BEGIN + CURSOR_SAVE + cursorMoveTo(rs, 1) + line + CURSOR_RESTORE + SYNC_END;
|
|
1098
1117
|
process.stdout.write(payload);
|
|
1099
1118
|
};
|
|
1119
|
+
const recomputeFooter = () => {
|
|
1120
|
+
if (capturingPrompt) {
|
|
1121
|
+
footerState = { kind: "prompt", prompt: capturingPrompt };
|
|
1122
|
+
} else if (activeNotice) {
|
|
1123
|
+
footerState = { kind: "notice", message: activeNotice.message, frame: noticeFrame };
|
|
1124
|
+
} else {
|
|
1125
|
+
footerState = buildIdle(lastSessionTitle, lastActivity);
|
|
1126
|
+
}
|
|
1127
|
+
};
|
|
1128
|
+
const startSpinner = () => {
|
|
1129
|
+
if (spinnerTimer) return;
|
|
1130
|
+
spinnerTimer = setInterval(() => {
|
|
1131
|
+
noticeFrame++;
|
|
1132
|
+
if (footerState.kind === "notice") {
|
|
1133
|
+
recomputeFooter();
|
|
1134
|
+
redrawFooter();
|
|
1135
|
+
}
|
|
1136
|
+
}, SPINNER_INTERVAL_MS);
|
|
1137
|
+
if (typeof spinnerTimer.unref === "function") spinnerTimer.unref();
|
|
1138
|
+
};
|
|
1139
|
+
const stopSpinner = () => {
|
|
1140
|
+
if (spinnerTimer) {
|
|
1141
|
+
clearInterval(spinnerTimer);
|
|
1142
|
+
spinnerTimer = null;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1100
1145
|
pty.onData((d) => {
|
|
1101
1146
|
process.stdout.write(d);
|
|
1102
1147
|
redrawFooter();
|
|
@@ -1107,7 +1152,8 @@ async function runWrappedAttach(opts) {
|
|
|
1107
1152
|
},
|
|
1108
1153
|
onAnswer: (body) => {
|
|
1109
1154
|
void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });
|
|
1110
|
-
|
|
1155
|
+
capturingPrompt = null;
|
|
1156
|
+
recomputeFooter();
|
|
1111
1157
|
redrawFooter();
|
|
1112
1158
|
}
|
|
1113
1159
|
});
|
|
@@ -1130,15 +1176,31 @@ async function runWrappedAttach(opts) {
|
|
|
1130
1176
|
relayBaseUrl: opts.relayBaseUrl,
|
|
1131
1177
|
boxId: opts.boxId,
|
|
1132
1178
|
onPrompt: (ev) => {
|
|
1133
|
-
|
|
1179
|
+
capturingPrompt = ev;
|
|
1180
|
+
recomputeFooter();
|
|
1134
1181
|
redrawFooter();
|
|
1135
1182
|
router.capture(ev).catch(() => {
|
|
1136
1183
|
});
|
|
1137
1184
|
},
|
|
1138
1185
|
onResolved: (id) => {
|
|
1139
|
-
if (
|
|
1186
|
+
if (capturingPrompt && capturingPrompt.id === id) {
|
|
1187
|
+
capturingPrompt = null;
|
|
1140
1188
|
router.abort("resolved-elsewhere");
|
|
1141
|
-
|
|
1189
|
+
recomputeFooter();
|
|
1190
|
+
redrawFooter();
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
onNotice: (ev) => {
|
|
1194
|
+
activeNotice = ev;
|
|
1195
|
+
startSpinner();
|
|
1196
|
+
recomputeFooter();
|
|
1197
|
+
redrawFooter();
|
|
1198
|
+
},
|
|
1199
|
+
onNoticeCleared: (id) => {
|
|
1200
|
+
if (activeNotice && activeNotice.id === id) {
|
|
1201
|
+
activeNotice = null;
|
|
1202
|
+
stopSpinner();
|
|
1203
|
+
recomputeFooter();
|
|
1142
1204
|
redrawFooter();
|
|
1143
1205
|
}
|
|
1144
1206
|
}
|
|
@@ -1156,7 +1218,7 @@ async function runWrappedAttach(opts) {
|
|
|
1156
1218
|
lastSessionTitle = nextTitle;
|
|
1157
1219
|
lastActivity = nextActivity;
|
|
1158
1220
|
if (footerState.kind === "idle") {
|
|
1159
|
-
|
|
1221
|
+
recomputeFooter();
|
|
1160
1222
|
redrawFooter();
|
|
1161
1223
|
}
|
|
1162
1224
|
} catch {
|
|
@@ -1178,6 +1240,7 @@ async function runWrappedAttach(opts) {
|
|
|
1178
1240
|
process.stdin.off("data", onStdinData);
|
|
1179
1241
|
process.stdout.off("resize", onResize);
|
|
1180
1242
|
clearInterval(statusTimer);
|
|
1243
|
+
stopSpinner();
|
|
1181
1244
|
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
1182
1245
|
process.stdin.pause();
|
|
1183
1246
|
stream.close();
|
|
@@ -1193,7 +1256,7 @@ async function runWrappedAttach(opts) {
|
|
|
1193
1256
|
return exitCode;
|
|
1194
1257
|
}
|
|
1195
1258
|
function runFallback(argv) {
|
|
1196
|
-
const child =
|
|
1259
|
+
const child = spawnSync2("docker", argv, { stdio: "inherit" });
|
|
1197
1260
|
return child.status ?? 0;
|
|
1198
1261
|
}
|
|
1199
1262
|
|
|
@@ -1201,6 +1264,12 @@ function runFallback(argv) {
|
|
|
1201
1264
|
function reattachRef(r) {
|
|
1202
1265
|
return typeof r.projectIndex === "number" ? String(r.projectIndex) : r.name;
|
|
1203
1266
|
}
|
|
1267
|
+
function logPrune(rebuild) {
|
|
1268
|
+
if (rebuild.prunedBytes <= 0) return;
|
|
1269
|
+
const mb = Math.round(rebuild.prunedBytes / 1024 / 1024);
|
|
1270
|
+
const n = rebuild.pruned.length;
|
|
1271
|
+
log5.info(`pruned ${String(n)} stale plugin cache${n === 1 ? "" : "s"} (${String(mb)} MB freed)`);
|
|
1272
|
+
}
|
|
1204
1273
|
var RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
|
|
1205
1274
|
async function attachClaudeWrapped(box, sessionName, reattach) {
|
|
1206
1275
|
const code = await runWrappedAttach({
|
|
@@ -1231,39 +1300,46 @@ function buildClaudeCliOverrides(opts) {
|
|
|
1231
1300
|
if (Object.keys(claude).length > 0) out.claude = claude;
|
|
1232
1301
|
return out;
|
|
1233
1302
|
}
|
|
1234
|
-
async function
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1303
|
+
async function runClaudeLoginContainer(image, extraArgs) {
|
|
1304
|
+
const { exitCode } = runInteractiveClaudeLogin(
|
|
1305
|
+
buildClaudeLoginRunArgv({ volume: SHARED_CLAUDE_VOLUME, image, extraArgs })
|
|
1306
|
+
);
|
|
1307
|
+
if (exitCode === 0) {
|
|
1308
|
+
const s = spinner();
|
|
1309
|
+
s.start("checking credentials");
|
|
1310
|
+
const warm = await warmUpClaudeCredentials(SHARED_CLAUDE_VOLUME, image, {
|
|
1311
|
+
onProgress: (line) => s.message(clampSpinnerLine(line))
|
|
1241
1312
|
});
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
return null;
|
|
1245
|
-
}
|
|
1246
|
-
const { exitCode } = runHostSetupToken();
|
|
1247
|
-
if (exitCode !== 0) {
|
|
1248
|
-
log5.warn(`\`claude setup-token\` exited with code ${String(exitCode)}; you can still paste a token below if you have one.`);
|
|
1249
|
-
}
|
|
1250
|
-
} else {
|
|
1251
|
-
log5.warn(
|
|
1252
|
-
"Claude Code is not installed on the host, so I cannot run `claude setup-token` for you. Run it on a machine that has Claude Code installed, then paste the token below \u2014 or skip and /login inside the box."
|
|
1253
|
-
);
|
|
1313
|
+
s.stop(warm.warmed ? "credentials ready" : "credentials check incomplete \u2014 continuing");
|
|
1314
|
+
await syncClaudeCredentials({ volume: SHARED_CLAUDE_VOLUME }, { image, isolate: false });
|
|
1254
1315
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1316
|
+
return exitCode;
|
|
1317
|
+
}
|
|
1318
|
+
async function maybeRunClaudeLogin(args) {
|
|
1319
|
+
if (!process.stdin.isTTY || args.yes) return;
|
|
1320
|
+
if (args.authSource === "host-env") return;
|
|
1321
|
+
if (await hostBackupHasCredentials()) return;
|
|
1322
|
+
const message = args.authSource === "auth-file" ? "You're on a legacy API token (shows as 'Claude API'). Sign in with your Claude subscription instead?" : "Sign in with your Claude subscription? (saved and reused by every box)";
|
|
1323
|
+
const answer = await confirm2({ message, initialValue: true });
|
|
1324
|
+
if (isCancel2(answer) || !answer) {
|
|
1325
|
+
log5.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
|
|
1326
|
+
return;
|
|
1259
1327
|
}
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1328
|
+
const s = spinner();
|
|
1329
|
+
s.start("preparing sandbox image");
|
|
1330
|
+
await ensureImage(args.image, { onProgress: (line) => s.message(clampSpinnerLine(line)) });
|
|
1331
|
+
s.message("preparing claude config");
|
|
1332
|
+
await ensureClaudeVolume(
|
|
1333
|
+
{ volume: SHARED_CLAUDE_VOLUME },
|
|
1334
|
+
{ syncFromHost: true, image: args.image, hostWorkspace: args.hostWorkspace }
|
|
1335
|
+
);
|
|
1336
|
+
s.stop("image ready");
|
|
1337
|
+
const exitCode = await runClaudeLoginContainer(args.image, ["--claudeai"]);
|
|
1338
|
+
if (exitCode !== 0) {
|
|
1339
|
+
log5.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
|
|
1340
|
+
return;
|
|
1263
1341
|
}
|
|
1264
|
-
|
|
1265
|
-
log5.success(`saved to ${AUTH_FILE} (mode 0600)`);
|
|
1266
|
-
return { env: { CLAUDE_CODE_OAUTH_TOKEN: token }, source: "auth-file" };
|
|
1342
|
+
log5.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
|
|
1267
1343
|
}
|
|
1268
1344
|
var claudeCommand = new Command2("claude").description("Create a sandboxed box and launch Claude Code in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
|
|
1269
1345
|
"--snapshot <ref>",
|
|
@@ -1287,6 +1363,13 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
1287
1363
|
});
|
|
1288
1364
|
const projectRoot = (await findProjectRoot(opts.workspace)).root;
|
|
1289
1365
|
const checkpointRef = opts.snapshot && opts.snapshot.length > 0 ? opts.snapshot : cfg.effective.box.defaultCheckpoint.length > 0 ? cfg.effective.box.defaultCheckpoint : void 0;
|
|
1366
|
+
const resolved = await resolveClaudeAuth(process.env);
|
|
1367
|
+
await maybeRunClaudeLogin({
|
|
1368
|
+
image: cfg.effective.box.image,
|
|
1369
|
+
authSource: resolved.source,
|
|
1370
|
+
yes: !!opts.yes,
|
|
1371
|
+
hostWorkspace: opts.workspace
|
|
1372
|
+
});
|
|
1290
1373
|
const wiz = await maybeRunSetupWizard({
|
|
1291
1374
|
workspace: opts.workspace,
|
|
1292
1375
|
yes: !!opts.yes,
|
|
@@ -1303,11 +1386,6 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
1303
1386
|
}
|
|
1304
1387
|
const useSnapshot = opts.hostSnapshot === false ? false : opts.hostSnapshot === true ? true : cfg.effective.box.hostSnapshot ?? false;
|
|
1305
1388
|
const sessionName = cfg.effective.claude.sessionName;
|
|
1306
|
-
let resolved = await resolveClaudeAuth(process.env);
|
|
1307
|
-
if (resolved.source === "none" && process.stdin.isTTY && !opts.yes) {
|
|
1308
|
-
const next = await offerSetupToken();
|
|
1309
|
-
if (next) resolved = next;
|
|
1310
|
-
}
|
|
1311
1389
|
const s = spinner();
|
|
1312
1390
|
s.start("creating box");
|
|
1313
1391
|
let containerName = "";
|
|
@@ -1345,6 +1423,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
1345
1423
|
});
|
|
1346
1424
|
const nSuffix = typeof result.record.projectIndex === "number" ? ` \xB7 n ${String(result.record.projectIndex)}` : "";
|
|
1347
1425
|
s.stop(`box ${result.record.container} ready${nSuffix}`);
|
|
1426
|
+
logPrune(rebuild);
|
|
1348
1427
|
for (const f of rebuild.failed) {
|
|
1349
1428
|
log5.warn(`plugin install failed for ${f.dir}; claude may still load it. stderr:
|
|
1350
1429
|
${f.stderr.trim()}`);
|
|
@@ -1369,6 +1448,7 @@ async function startOrAttachClaude(box, claudeArgs, opts) {
|
|
|
1369
1448
|
cliOverrides: opts.sessionName ? { claude: { sessionName: opts.sessionName } } : {}
|
|
1370
1449
|
});
|
|
1371
1450
|
const sessionName = cfg.effective.claude.sessionName;
|
|
1451
|
+
const resolved = await resolveClaudeAuth(process.env);
|
|
1372
1452
|
const insp = await inspectBox(box.id);
|
|
1373
1453
|
if (insp.state === "missing") {
|
|
1374
1454
|
throw new Error(`box ${box.name} has no container; was it destroyed?`);
|
|
@@ -1379,6 +1459,12 @@ async function startOrAttachClaude(box, claudeArgs, opts) {
|
|
|
1379
1459
|
await attachClaudeWrapped(box, sessionName, reattachRef(box));
|
|
1380
1460
|
return;
|
|
1381
1461
|
}
|
|
1462
|
+
await maybeRunClaudeLogin({
|
|
1463
|
+
image: box.image,
|
|
1464
|
+
authSource: resolved.source,
|
|
1465
|
+
yes: false,
|
|
1466
|
+
hostWorkspace: box.workspacePath
|
|
1467
|
+
});
|
|
1382
1468
|
const s = spinner();
|
|
1383
1469
|
s.start("preparing box");
|
|
1384
1470
|
if (insp.state === "paused") {
|
|
@@ -1401,7 +1487,12 @@ async function startOrAttachClaude(box, claudeArgs, opts) {
|
|
|
1401
1487
|
}
|
|
1402
1488
|
);
|
|
1403
1489
|
}
|
|
1404
|
-
|
|
1490
|
+
const claudeVolume = box.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME;
|
|
1491
|
+
await seedSetupSkillIntoVolume(claudeVolume, box.image);
|
|
1492
|
+
await syncClaudeCredentials(
|
|
1493
|
+
{ volume: claudeVolume },
|
|
1494
|
+
{ image: box.image, isolate: claudeVolume !== SHARED_CLAUDE_VOLUME }
|
|
1495
|
+
);
|
|
1405
1496
|
s.message("checking plugin native deps");
|
|
1406
1497
|
const rebuild = await rebuildPluginNativeDeps(box.container, {
|
|
1407
1498
|
volume: box.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME,
|
|
@@ -1415,6 +1506,7 @@ async function startOrAttachClaude(box, claudeArgs, opts) {
|
|
|
1415
1506
|
boxName: box.name
|
|
1416
1507
|
});
|
|
1417
1508
|
s.stop(`box ${box.container} ready`);
|
|
1509
|
+
logPrune(rebuild);
|
|
1418
1510
|
for (const f of rebuild.failed) {
|
|
1419
1511
|
log5.warn(`plugin install failed for ${f.dir}; claude may still load it. stderr:
|
|
1420
1512
|
${f.stderr.trim()}`);
|
|
@@ -1423,19 +1515,16 @@ ${f.stderr.trim()}`);
|
|
|
1423
1515
|
await attachClaudeWrapped(box, sessionName, reattachRef(box));
|
|
1424
1516
|
}
|
|
1425
1517
|
var claudeAttachCommand = new Command2("attach").description(
|
|
1426
|
-
"Attach to a Claude Code tmux session in a box, starting one if none is running (auto-unpause/start)"
|
|
1518
|
+
"Attach to a Claude Code tmux session in a box, starting one if none is running (auto-unpause/start; never re-syncs ~/.claude \u2014 use `claude start` for that)"
|
|
1427
1519
|
).argument(
|
|
1428
1520
|
"[box]",
|
|
1429
1521
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
1430
|
-
).option("--session-name <name>", "tmux session name (default from config; built-in: claude)").
|
|
1431
|
-
"--no-sync-config",
|
|
1432
|
-
"when starting a fresh session, skip rsyncing the host's ~/.claude into the box's volume (faster)"
|
|
1433
|
-
).action(async function(idOrName) {
|
|
1522
|
+
).option("--session-name <name>", "tmux session name (default from config; built-in: claude)").action(async function(idOrName) {
|
|
1434
1523
|
const opts = this.optsWithGlobals();
|
|
1435
1524
|
intro("Attaching to Claude session...");
|
|
1436
1525
|
try {
|
|
1437
1526
|
const box = await resolveBoxOrExit(idOrName);
|
|
1438
|
-
await startOrAttachClaude(box, [], opts);
|
|
1527
|
+
await startOrAttachClaude(box, [], { ...opts, syncConfig: false });
|
|
1439
1528
|
} catch (err) {
|
|
1440
1529
|
if (err instanceof ClaudeSessionError) {
|
|
1441
1530
|
log5.error(err.message);
|
|
@@ -1470,12 +1559,43 @@ var claudeStartCommand = new Command2("start").description(
|
|
|
1470
1559
|
handleLifecycleError(err);
|
|
1471
1560
|
}
|
|
1472
1561
|
});
|
|
1562
|
+
var claudeLoginCommand = new Command2("login").description(
|
|
1563
|
+
"Sign in to Claude for use in sandboxes (forwards args to `claude auth login`, e.g. --sso, --console). Runs in a throwaway container against the shared claude-config volume \u2014 usable before the first `agentbox claude`."
|
|
1564
|
+
).argument(
|
|
1565
|
+
"[args...]",
|
|
1566
|
+
"extra args forwarded to `claude auth login`; place after `--`, e.g. `agentbox claude login -- --sso`"
|
|
1567
|
+
).action(async (args) => {
|
|
1568
|
+
intro("Signing in to Claude...");
|
|
1569
|
+
if (!process.stdin.isTTY) {
|
|
1570
|
+
log5.error("`agentbox claude login` needs an interactive terminal.");
|
|
1571
|
+
process.exit(1);
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
const cfg = await loadEffectiveConfig(process.cwd());
|
|
1575
|
+
const image = cfg.effective.box.image;
|
|
1576
|
+
const s = spinner();
|
|
1577
|
+
s.start("preparing sandbox image");
|
|
1578
|
+
await ensureImage(image, { onProgress: (line) => s.message(clampSpinnerLine(line)) });
|
|
1579
|
+
s.stop("image ready");
|
|
1580
|
+
const exitCode = await runClaudeLoginContainer(image, args);
|
|
1581
|
+
if (exitCode !== 0) {
|
|
1582
|
+
log5.warn(`\`claude auth login\` exited with code ${String(exitCode)}`);
|
|
1583
|
+
process.exit(exitCode);
|
|
1584
|
+
}
|
|
1585
|
+
outro("signed in \u2014 credentials saved for future boxes");
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
handleLifecycleError(err);
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1473
1590
|
claudeCommand.addCommand(claudeAttachCommand);
|
|
1474
1591
|
claudeCommand.addCommand(claudeStartCommand);
|
|
1592
|
+
claudeCommand.addCommand(claudeLoginCommand);
|
|
1475
1593
|
|
|
1476
1594
|
// src/commands/checkpoint.ts
|
|
1477
1595
|
import { confirm as confirm3, isCancel as isCancel3, log as log6 } from "@clack/prompts";
|
|
1478
1596
|
import { Command as Command3 } from "commander";
|
|
1597
|
+
var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
|
|
1598
|
+
var CHECKPOINT_NOTICE_TTL_MS = 66e4;
|
|
1479
1599
|
async function projectRootFor(cwd, recordRoot) {
|
|
1480
1600
|
return recordRoot ?? (await findProjectRoot(cwd)).root;
|
|
1481
1601
|
}
|
|
@@ -1500,21 +1620,50 @@ var createSub = new Command3("create").description("Capture a box state as a pro
|
|
|
1500
1620
|
}
|
|
1501
1621
|
const projectRoot = await projectRootFor(box.workspacePath, box.projectRoot);
|
|
1502
1622
|
const cfg = await loadEffectiveConfig(projectRoot);
|
|
1503
|
-
const
|
|
1504
|
-
box,
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
setDefault: opts.setDefault === true,
|
|
1509
|
-
replace: opts.replace === true,
|
|
1510
|
-
maxLayers: cfg.effective.checkpoint.maxLayers,
|
|
1511
|
-
onLog: (line) => log6.info(line)
|
|
1512
|
-
});
|
|
1513
|
-
log6.success(
|
|
1514
|
-
`checkpoint ${info.name} (${info.manifest.type}) -> ${info.dir}` + (opts.setDefault ? " [project default]" : "")
|
|
1623
|
+
const noticeId = await setRelayNotice(
|
|
1624
|
+
box.id,
|
|
1625
|
+
"checkpoint",
|
|
1626
|
+
CHECKPOINT_NOTICE,
|
|
1627
|
+
CHECKPOINT_NOTICE_TTL_MS
|
|
1515
1628
|
);
|
|
1516
|
-
|
|
1517
|
-
|
|
1629
|
+
let signalled = false;
|
|
1630
|
+
const onSignal = () => {
|
|
1631
|
+
if (signalled) return;
|
|
1632
|
+
signalled = true;
|
|
1633
|
+
void (async () => {
|
|
1634
|
+
if (noticeId) await clearRelayNotice(box.id, noticeId);
|
|
1635
|
+
process.exit(130);
|
|
1636
|
+
})();
|
|
1637
|
+
};
|
|
1638
|
+
if (noticeId) {
|
|
1639
|
+
process.once("SIGINT", onSignal);
|
|
1640
|
+
process.once("SIGTERM", onSignal);
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
const info = await createCheckpoint({
|
|
1644
|
+
box,
|
|
1645
|
+
projectRoot,
|
|
1646
|
+
name: opts.name,
|
|
1647
|
+
merged: opts.merged === true,
|
|
1648
|
+
setDefault: opts.setDefault === true,
|
|
1649
|
+
replace: opts.replace === true,
|
|
1650
|
+
maxLayers: cfg.effective.checkpoint.maxLayers,
|
|
1651
|
+
onLog: (line) => log6.info(line)
|
|
1652
|
+
});
|
|
1653
|
+
log6.success(
|
|
1654
|
+
`checkpoint ${info.name} (${info.manifest.type}) -> ${info.dir}` + (opts.setDefault ? " [project default]" : "")
|
|
1655
|
+
);
|
|
1656
|
+
if (!opts.setDefault) {
|
|
1657
|
+
log6.info(
|
|
1658
|
+
`make it the default for new boxes: agentbox checkpoint set-default ${info.name}`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
} finally {
|
|
1662
|
+
if (noticeId) {
|
|
1663
|
+
await clearRelayNotice(box.id, noticeId);
|
|
1664
|
+
process.removeListener("SIGINT", onSignal);
|
|
1665
|
+
process.removeListener("SIGTERM", onSignal);
|
|
1666
|
+
}
|
|
1518
1667
|
}
|
|
1519
1668
|
} catch (err) {
|
|
1520
1669
|
handleLifecycleError(err);
|
|
@@ -1598,7 +1747,7 @@ var rmSub = new Command3("rm").description("Delete a checkpoint").argument("<ref
|
|
|
1598
1747
|
handleLifecycleError(err);
|
|
1599
1748
|
}
|
|
1600
1749
|
});
|
|
1601
|
-
var checkpointCommand = new Command3("checkpoint").description("
|
|
1750
|
+
var checkpointCommand = new Command3("checkpoint").alias("checkpoints").description("List and manage project checkpoints (warm box state new boxes can start from)").addCommand(createSub).addCommand(lsSub, { isDefault: true }).addCommand(setDefaultSub).addCommand(rmSub);
|
|
1602
1751
|
|
|
1603
1752
|
// src/commands/code.ts
|
|
1604
1753
|
import { spawn } from "child_process";
|
|
@@ -1761,7 +1910,7 @@ async function fetchServiceNames(container) {
|
|
|
1761
1910
|
}
|
|
1762
1911
|
|
|
1763
1912
|
// src/commands/config.ts
|
|
1764
|
-
import { spawnSync as
|
|
1913
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1765
1914
|
import { Command as Command5, InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
1766
1915
|
function resolveWriteScope(opts) {
|
|
1767
1916
|
if (opts.global && opts.project) {
|
|
@@ -1986,7 +2135,7 @@ var editCommand = new Command5("edit").description("Open a config file in $EDITO
|
|
|
1986
2135
|
const scope = resolveEditScope(opts);
|
|
1987
2136
|
const path2 = await configPathFor(scope, process.cwd());
|
|
1988
2137
|
const editor = process.env["EDITOR"] || process.env["VISUAL"] || "vi";
|
|
1989
|
-
const child =
|
|
2138
|
+
const child = spawnSync3(editor, [path2], { stdio: "inherit" });
|
|
1990
2139
|
process.exit(child.status ?? 0);
|
|
1991
2140
|
} catch (err) {
|
|
1992
2141
|
handleError(err);
|
|
@@ -2240,7 +2389,7 @@ var cpCommand = new Command6("cp").description("Copy files between host and box
|
|
|
2240
2389
|
// src/commands/create.ts
|
|
2241
2390
|
import { intro as intro2, log as log9, outro as outro2, spinner as spinner2 } from "@clack/prompts";
|
|
2242
2391
|
import { Command as Command7 } from "commander";
|
|
2243
|
-
import { execSync, spawnSync as
|
|
2392
|
+
import { execSync, spawnSync as spawnSync4 } from "child_process";
|
|
2244
2393
|
function buildCliOverrides(opts) {
|
|
2245
2394
|
const box = {};
|
|
2246
2395
|
if (opts.hostSnapshot !== void 0) box.hostSnapshot = opts.hostSnapshot;
|
|
@@ -2264,7 +2413,7 @@ var RELAY_HOST_URL2 = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
|
|
|
2264
2413
|
async function attachShell(record) {
|
|
2265
2414
|
const dockerArgv = ["exec", "-it", record.container, "bash"];
|
|
2266
2415
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
2267
|
-
const child =
|
|
2416
|
+
const child = spawnSync4("docker", dockerArgv, { stdio: "inherit" });
|
|
2268
2417
|
process.exit(child.status ?? 0);
|
|
2269
2418
|
}
|
|
2270
2419
|
const code = await runWrappedAttach({
|
|
@@ -2802,6 +2951,7 @@ var POLL_MS = 1e3;
|
|
|
2802
2951
|
var FRAME_MS = 16;
|
|
2803
2952
|
var RESIZE_DEBOUNCE_MS = 120;
|
|
2804
2953
|
var LEADER_LINGER_MS = 1500;
|
|
2954
|
+
var NOTICE_SPINNER_MS = 120;
|
|
2805
2955
|
var SYNC_BEGIN2 = "\x1B[?2026h";
|
|
2806
2956
|
var SYNC_END2 = "\x1B[?2026l";
|
|
2807
2957
|
function cursorTo2(x, y) {
|
|
@@ -2894,6 +3044,16 @@ var Compositor = class {
|
|
|
2894
3044
|
* we can dispose them when boxes disappear from the list.
|
|
2895
3045
|
*/
|
|
2896
3046
|
activePrompts = /* @__PURE__ */ new Map();
|
|
3047
|
+
/**
|
|
3048
|
+
* Per-box active relay notice (currently: a checkpoint freezing the box).
|
|
3049
|
+
* Drives the `◆ checkpoint` sidebar cell and the animated status-bar
|
|
3050
|
+
* warning. Shares the SSE subscriptions in {@link promptStreams}.
|
|
3051
|
+
*/
|
|
3052
|
+
activeNotices = /* @__PURE__ */ new Map();
|
|
3053
|
+
/** Monotonic spinner counter for the notice status bar. */
|
|
3054
|
+
noticeFrame = 0;
|
|
3055
|
+
/** Drives the spinner animation while {@link activeNotices} is non-empty. */
|
|
3056
|
+
noticeTimer = null;
|
|
2897
3057
|
promptStreams = /* @__PURE__ */ new Map();
|
|
2898
3058
|
activeMode = "claude";
|
|
2899
3059
|
flashMsg = null;
|
|
@@ -2971,7 +3131,10 @@ var Compositor = class {
|
|
|
2971
3131
|
if (!wanted.has(boxId)) {
|
|
2972
3132
|
stream.close();
|
|
2973
3133
|
this.promptStreams.delete(boxId);
|
|
2974
|
-
|
|
3134
|
+
let changed = this.activePrompts.delete(boxId);
|
|
3135
|
+
if (this.activeNotices.delete(boxId)) changed = true;
|
|
3136
|
+
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
3137
|
+
if (changed) this.drawChrome();
|
|
2975
3138
|
}
|
|
2976
3139
|
}
|
|
2977
3140
|
for (const boxId of wanted) {
|
|
@@ -2992,12 +3155,41 @@ var Compositor = class {
|
|
|
2992
3155
|
this.drawChrome();
|
|
2993
3156
|
}
|
|
2994
3157
|
},
|
|
3158
|
+
onNotice: (ev) => {
|
|
3159
|
+
if (this.tornDown) return;
|
|
3160
|
+
this.activeNotices.set(boxId, ev);
|
|
3161
|
+
this.startNoticeSpinner();
|
|
3162
|
+
this.drawChrome();
|
|
3163
|
+
},
|
|
3164
|
+
onNoticeCleared: (id) => {
|
|
3165
|
+
if (this.tornDown) return;
|
|
3166
|
+
const current = this.activeNotices.get(boxId);
|
|
3167
|
+
if (current && current.id === id) {
|
|
3168
|
+
this.activeNotices.delete(boxId);
|
|
3169
|
+
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
3170
|
+
this.drawChrome();
|
|
3171
|
+
}
|
|
3172
|
+
},
|
|
2995
3173
|
onError: () => {
|
|
2996
3174
|
}
|
|
2997
3175
|
});
|
|
2998
3176
|
this.promptStreams.set(boxId, stream);
|
|
2999
3177
|
}
|
|
3000
3178
|
}
|
|
3179
|
+
startNoticeSpinner() {
|
|
3180
|
+
if (this.noticeTimer) return;
|
|
3181
|
+
this.noticeTimer = setInterval(() => {
|
|
3182
|
+
this.noticeFrame++;
|
|
3183
|
+
this.drawChrome();
|
|
3184
|
+
}, NOTICE_SPINNER_MS);
|
|
3185
|
+
if (typeof this.noticeTimer.unref === "function") this.noticeTimer.unref();
|
|
3186
|
+
}
|
|
3187
|
+
stopNoticeSpinner() {
|
|
3188
|
+
if (this.noticeTimer) {
|
|
3189
|
+
clearInterval(this.noticeTimer);
|
|
3190
|
+
this.noticeTimer = null;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3001
3193
|
selectedBox() {
|
|
3002
3194
|
return this.boxes.find((b) => b.id === this.selectedId);
|
|
3003
3195
|
}
|
|
@@ -3469,9 +3661,12 @@ var Compositor = class {
|
|
|
3469
3661
|
drawChrome() {
|
|
3470
3662
|
if (this.tornDown || this.layout.tooSmall) return;
|
|
3471
3663
|
const { sidebar, sepX, statusY } = this.layout;
|
|
3472
|
-
const
|
|
3473
|
-
|
|
3474
|
-
|
|
3664
|
+
const decorate = this.activePrompts.size > 0 || this.activeNotices.size > 0;
|
|
3665
|
+
const boxesWithPrompt = decorate ? this.boxes.map((b) => {
|
|
3666
|
+
const pendingPrompt = this.activePrompts.has(b.id);
|
|
3667
|
+
const checkpointing = this.activeNotices.has(b.id);
|
|
3668
|
+
return pendingPrompt || checkpointing ? { ...b, pendingPrompt, checkpointing } : b;
|
|
3669
|
+
}) : this.boxes;
|
|
3475
3670
|
const { lines, rowOwner, headerRows } = sidebarLines(
|
|
3476
3671
|
boxesWithPrompt,
|
|
3477
3672
|
this.selectedId,
|
|
@@ -3505,6 +3700,12 @@ var Compositor = class {
|
|
|
3505
3700
|
{ kind: "prompt", prompt: activePromptForSelected },
|
|
3506
3701
|
this.layout.cols
|
|
3507
3702
|
);
|
|
3703
|
+
} else if (this.activeNotices.has(this.selectedId)) {
|
|
3704
|
+
const notice = this.activeNotices.get(this.selectedId);
|
|
3705
|
+
status = renderFooter(
|
|
3706
|
+
{ kind: "notice", message: notice.message, frame: this.noticeFrame },
|
|
3707
|
+
this.layout.cols
|
|
3708
|
+
);
|
|
3508
3709
|
} else {
|
|
3509
3710
|
const stateLabel = this.selectedId === NEW_BOX_ID ? "create" : this.menu ? "menu" : this.session && this.activeMode === "shell" ? "shell" : void 0;
|
|
3510
3711
|
status = statusLine(
|
|
@@ -3540,9 +3741,11 @@ var Compositor = class {
|
|
|
3540
3741
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
3541
3742
|
if (this.flashTimer) clearTimeout(this.flashTimer);
|
|
3542
3743
|
if (this.leaderLingerTimer) clearTimeout(this.leaderLingerTimer);
|
|
3744
|
+
if (this.noticeTimer) clearInterval(this.noticeTimer);
|
|
3543
3745
|
for (const stream of this.promptStreams.values()) stream.close();
|
|
3544
3746
|
this.promptStreams.clear();
|
|
3545
3747
|
this.activePrompts.clear();
|
|
3748
|
+
this.activeNotices.clear();
|
|
3546
3749
|
this.parser.dispose();
|
|
3547
3750
|
this.disposeSession();
|
|
3548
3751
|
this.inp.off("data", this.onData);
|
|
@@ -3655,6 +3858,11 @@ var dashboardCommand = new Command8("dashboard").description("Box list + the sel
|
|
|
3655
3858
|
await rebuildPluginNativeDeps(box.container, {
|
|
3656
3859
|
volume: box.claudeConfigVolume
|
|
3657
3860
|
});
|
|
3861
|
+
const claudeVolume = box.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME;
|
|
3862
|
+
await syncClaudeCredentials(
|
|
3863
|
+
{ volume: claudeVolume },
|
|
3864
|
+
{ image: box.image, isolate: claudeVolume !== SHARED_CLAUDE_VOLUME }
|
|
3865
|
+
);
|
|
3658
3866
|
await startClaudeSession({ container: box.container, claudeArgs: [], boxName: box.name });
|
|
3659
3867
|
const info = await claudeSessionInfo(box.container);
|
|
3660
3868
|
return {
|
|
@@ -4609,7 +4817,7 @@ var restartSub = new Command19("restart").description("Stop then start the host
|
|
|
4609
4817
|
var relayCommand = new Command19("relay").description("Manage the host relay process (status / stop / start / restart)").addCommand(statusSub, { isDefault: true }).addCommand(stopSub).addCommand(startSub).addCommand(restartSub);
|
|
4610
4818
|
|
|
4611
4819
|
// src/commands/screen.ts
|
|
4612
|
-
import { spawnSync as
|
|
4820
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
4613
4821
|
import { log as log20 } from "@clack/prompts";
|
|
4614
4822
|
import { Command as Command20 } from "commander";
|
|
4615
4823
|
var screenCommand = new Command20("screen").description("Open a box's VNC (noVNC) viewer in the browser (auto-unpause/start)").argument(
|
|
@@ -4657,7 +4865,7 @@ var screenCommand = new Command20("screen").description("Open a box's VNC (noVNC
|
|
|
4657
4865
|
`);
|
|
4658
4866
|
return;
|
|
4659
4867
|
}
|
|
4660
|
-
const opened =
|
|
4868
|
+
const opened = spawnSync5("open", [url], { stdio: "inherit" });
|
|
4661
4869
|
if (opened.status !== 0) {
|
|
4662
4870
|
throw new Error(`open ${url} failed (exit ${String(opened.status ?? "n/a")})`);
|
|
4663
4871
|
}
|
|
@@ -4669,7 +4877,7 @@ var screenCommand = new Command20("screen").description("Open a box's VNC (noVNC
|
|
|
4669
4877
|
});
|
|
4670
4878
|
|
|
4671
4879
|
// src/commands/shell.ts
|
|
4672
|
-
import { spawnSync as
|
|
4880
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
4673
4881
|
import { log as log21 } from "@clack/prompts";
|
|
4674
4882
|
import { Command as Command21 } from "commander";
|
|
4675
4883
|
var RELAY_HOST_URL3 = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;
|
|
@@ -4722,7 +4930,7 @@ var shellCommand = new Command21("shell").description("Open an interactive bash
|
|
|
4722
4930
|
...bashArgs
|
|
4723
4931
|
];
|
|
4724
4932
|
if (!isInteractive || effectiveCmd.length > 0) {
|
|
4725
|
-
const child =
|
|
4933
|
+
const child = spawnSync6("docker", dockerArgv, { stdio: "inherit" });
|
|
4726
4934
|
process.exit(child.status ?? 0);
|
|
4727
4935
|
}
|
|
4728
4936
|
const code = await runWrappedAttach({
|