@madarco/agentbox 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-ZXBCNWJX.js → _cloud-attach-2DGI6FUA.js} +4 -4
- package/dist/{chunk-NCJP5MTN.js → chunk-CDKVD6UO.js} +239 -61
- package/dist/chunk-CDKVD6UO.js.map +1 -0
- package/dist/{chunk-GU5LW4B5.js → chunk-I7NOGCL4.js} +374 -62
- package/dist/chunk-I7NOGCL4.js.map +1 -0
- package/dist/{chunk-BXQMIEHC.js → chunk-M2UWJKFA.js} +255 -163
- package/dist/chunk-M2UWJKFA.js.map +1 -0
- package/dist/{chunk-KL36BRN4.js → chunk-PWUVHPN6.js} +66 -17
- package/dist/{chunk-KL36BRN4.js.map → chunk-PWUVHPN6.js.map} +1 -1
- package/dist/{dist-CX5CGVEB.js → dist-BD5QJRDC.js} +4 -4
- package/dist/{dist-GDHP34ZK.js → dist-BNI5PQYK.js} +16 -4
- package/dist/dist-BNI5PQYK.js.map +1 -0
- package/dist/{dist-32EZBYG4.js → dist-SBCQVFCE.js} +23 -3
- package/dist/{dist-XML54CNB.js → dist-SJHY3HYN.js} +31 -6
- package/dist/dist-SJHY3HYN.js.map +1 -0
- package/dist/index.js +1787 -528
- package/dist/index.js.map +1 -1
- package/dist/{prepared-state-CL4CWXQA-H5THETIM.js → prepared-state-MQHD3M5F-O5M4NIN4.js} +2 -2
- package/package.json +10 -8
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +10 -9
- package/runtime/docker/packages/ctl/dist/bin.cjs +38 -3
- package/runtime/hetzner/agentbox-setup-skill.md +10 -9
- package/runtime/hetzner/ctl.cjs +38 -3
- package/runtime/relay/bin.cjs +41 -3
- package/runtime/vercel/agentbox-setup-skill.md +10 -9
- package/runtime/vercel/ctl.cjs +38 -3
- package/runtime/vercel/custom-system-CLAUDE.md +1 -4
- package/runtime/vercel/scripts/provision.sh +40 -0
- package/share/agentbox-setup/SKILL.md +10 -9
- package/dist/chunk-BXQMIEHC.js.map +0 -1
- package/dist/chunk-GU5LW4B5.js.map +0 -1
- package/dist/chunk-NCJP5MTN.js.map +0 -1
- package/dist/dist-GDHP34ZK.js.map +0 -1
- package/dist/dist-XML54CNB.js.map +0 -1
- /package/dist/{_cloud-attach-ZXBCNWJX.js.map → _cloud-attach-2DGI6FUA.js.map} +0 -0
- /package/dist/{dist-CX5CGVEB.js.map → dist-BD5QJRDC.js.map} +0 -0
- /package/dist/{dist-32EZBYG4.js.map → dist-SBCQVFCE.js.map} +0 -0
- /package/dist/{prepared-state-CL4CWXQA-H5THETIM.js.map → prepared-state-MQHD3M5F-O5M4NIN4.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -28,11 +28,13 @@ import {
|
|
|
28
28
|
agentSpecsForCloud,
|
|
29
29
|
ensureAgentVolumesForCloud,
|
|
30
30
|
listCloudCheckpoints,
|
|
31
|
+
probeCloudCheckpoint,
|
|
31
32
|
resolveCloudCheckpoint,
|
|
32
33
|
seedAgentVolumesIfFresh
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-M2UWJKFA.js";
|
|
34
35
|
import {
|
|
35
36
|
ADVANCED_HINT_GROUPS,
|
|
37
|
+
ALERT_BAND_ROWS,
|
|
36
38
|
NEW_BOX_ID,
|
|
37
39
|
NEW_BOX_LABEL,
|
|
38
40
|
buildCloudAttachInnerCommand,
|
|
@@ -51,6 +53,7 @@ import {
|
|
|
51
53
|
providerForBox,
|
|
52
54
|
providerForCreate,
|
|
53
55
|
pushTerminalTitle,
|
|
56
|
+
renderAlertBand,
|
|
54
57
|
renderFooter,
|
|
55
58
|
runWrappedAttach,
|
|
56
59
|
setTerminalTitle,
|
|
@@ -58,11 +61,12 @@ import {
|
|
|
58
61
|
statusLine,
|
|
59
62
|
stripTitleGlyph,
|
|
60
63
|
subscribePrompts
|
|
61
|
-
} from "./chunk-
|
|
64
|
+
} from "./chunk-I7NOGCL4.js";
|
|
62
65
|
import {
|
|
63
66
|
AmbiguousBoxError,
|
|
64
67
|
BOX_STATUS_EVENT,
|
|
65
68
|
BoxNotFoundError,
|
|
69
|
+
CODEX_CREDENTIALS_BACKUP_FILE,
|
|
66
70
|
ClaudeSessionError,
|
|
67
71
|
CodexSessionError,
|
|
68
72
|
DEFAULT_CODEX_SESSION,
|
|
@@ -72,6 +76,7 @@ import {
|
|
|
72
76
|
DEFAULT_SHELL_SESSION,
|
|
73
77
|
GH_PR_OPS,
|
|
74
78
|
KEY_REGISTRY,
|
|
79
|
+
OPENCODE_CREDENTIALS_BACKUP_FILE,
|
|
75
80
|
OPENCODE_FORWARDED_ENV_KEYS,
|
|
76
81
|
OpencodeSessionError,
|
|
77
82
|
SHARED_CLAUDE_VOLUME,
|
|
@@ -115,6 +120,8 @@ import {
|
|
|
115
120
|
ensureOpencodeVolume,
|
|
116
121
|
ensureRelay,
|
|
117
122
|
execInBox,
|
|
123
|
+
extractCodexCredentials,
|
|
124
|
+
extractOpencodeCredentials,
|
|
118
125
|
findProjectRoot,
|
|
119
126
|
formatDetachNotice,
|
|
120
127
|
getBoxHostPaths,
|
|
@@ -122,6 +129,7 @@ import {
|
|
|
122
129
|
getRelayStatus,
|
|
123
130
|
hashRpcParams,
|
|
124
131
|
hostBackupHasCredentials,
|
|
132
|
+
hostClaudeBackupExpired,
|
|
125
133
|
ideProfile,
|
|
126
134
|
injectPrCreateHead,
|
|
127
135
|
inspectBox,
|
|
@@ -199,7 +207,7 @@ import {
|
|
|
199
207
|
waitForTmuxPaneContent,
|
|
200
208
|
warmUpClaudeCredentials,
|
|
201
209
|
writeJob
|
|
202
|
-
} from "./chunk-
|
|
210
|
+
} from "./chunk-CDKVD6UO.js";
|
|
203
211
|
import {
|
|
204
212
|
DEFAULT_BOX_IMAGE,
|
|
205
213
|
STATE_DIR,
|
|
@@ -207,15 +215,15 @@ import {
|
|
|
207
215
|
imageInfo,
|
|
208
216
|
readState,
|
|
209
217
|
resolveBoxRef
|
|
210
|
-
} from "./chunk-
|
|
218
|
+
} from "./chunk-PWUVHPN6.js";
|
|
211
219
|
import "./chunk-G3H2L3O2.js";
|
|
212
220
|
|
|
213
221
|
// src/version.ts
|
|
214
|
-
var AGENTBOX_VERSION = true ? "0.
|
|
215
|
-
var AGENTBOX_COMMIT = true ? "
|
|
222
|
+
var AGENTBOX_VERSION = true ? "0.10.1" : "0.0.0-dev";
|
|
223
|
+
var AGENTBOX_COMMIT = true ? "1957cb4a" : "dev";
|
|
216
224
|
|
|
217
225
|
// src/index.ts
|
|
218
|
-
import { Command as
|
|
226
|
+
import { Command as Command46 } from "commander";
|
|
219
227
|
|
|
220
228
|
// src/engine-override.ts
|
|
221
229
|
async function applyEngineOverrideAtStartup() {
|
|
@@ -275,7 +283,7 @@ function buildGroupedHelp(program2) {
|
|
|
275
283
|
if (cmd) terms.push(term(cmd));
|
|
276
284
|
}
|
|
277
285
|
}
|
|
278
|
-
const
|
|
286
|
+
const pad4 = Math.max(0, ...terms.map((t) => t.length)) + 2;
|
|
279
287
|
const lines = ["Commands:"];
|
|
280
288
|
for (const g of groups) {
|
|
281
289
|
const title = g.hint ? `${g.title} (${g.hint})` : g.title;
|
|
@@ -283,7 +291,7 @@ function buildGroupedHelp(program2) {
|
|
|
283
291
|
for (const name of g.commands) {
|
|
284
292
|
const cmd = byName.get(name);
|
|
285
293
|
if (!cmd) continue;
|
|
286
|
-
lines.push(` ${term(cmd).padEnd(
|
|
294
|
+
lines.push(` ${term(cmd).padEnd(pad4)}${cmd.description()}`);
|
|
287
295
|
}
|
|
288
296
|
}
|
|
289
297
|
lines.push("", "Run `agentbox <command> --help` for command-specific options.");
|
|
@@ -681,6 +689,36 @@ function buildPromptArgs(agentKind, prompt, userArgs) {
|
|
|
681
689
|
return resolveAgentLauncher(agentKind).buildArgs(prompt, userArgs);
|
|
682
690
|
}
|
|
683
691
|
|
|
692
|
+
// src/lib/skip-permissions.ts
|
|
693
|
+
var CLAUDE_SKIP_PERMISSIONS_FLAG = "--dangerously-skip-permissions";
|
|
694
|
+
var CODEX_SKIP_PERMISSIONS_FLAG = "--dangerously-bypass-approvals-and-sandbox";
|
|
695
|
+
var CLAUDE_CONFLICTING = /* @__PURE__ */ new Set([CLAUDE_SKIP_PERMISSIONS_FLAG, "--permission-mode"]);
|
|
696
|
+
var CODEX_CONFLICTING = /* @__PURE__ */ new Set([
|
|
697
|
+
CODEX_SKIP_PERMISSIONS_FLAG,
|
|
698
|
+
"--yolo",
|
|
699
|
+
"--full-auto",
|
|
700
|
+
"-a",
|
|
701
|
+
"--ask-for-approval",
|
|
702
|
+
"-s",
|
|
703
|
+
"--sandbox"
|
|
704
|
+
]);
|
|
705
|
+
function inject(args, flag, conflicting) {
|
|
706
|
+
const hasConflict = args.some((a) => {
|
|
707
|
+
const eq = a.indexOf("=");
|
|
708
|
+
return conflicting.has(eq === -1 ? a : a.slice(0, eq));
|
|
709
|
+
});
|
|
710
|
+
if (hasConflict) return args;
|
|
711
|
+
return [flag, ...args];
|
|
712
|
+
}
|
|
713
|
+
function applyClaudeSkipPermissions(args, cfg) {
|
|
714
|
+
if (!cfg.claude.dangerouslySkipPermissions) return args;
|
|
715
|
+
return inject(args, CLAUDE_SKIP_PERMISSIONS_FLAG, CLAUDE_CONFLICTING);
|
|
716
|
+
}
|
|
717
|
+
function applyCodexSkipPermissions(args, cfg) {
|
|
718
|
+
if (!cfg.codex.dangerouslySkipPermissions) return args;
|
|
719
|
+
return inject(args, CODEX_SKIP_PERMISSIONS_FLAG, CODEX_CONFLICTING);
|
|
720
|
+
}
|
|
721
|
+
|
|
684
722
|
// src/lib/queue/parse-max-option.ts
|
|
685
723
|
function parseMaxOption(flag, raw) {
|
|
686
724
|
if (raw === void 0) return void 0;
|
|
@@ -1523,16 +1561,16 @@ function extractCodexUuid(filename) {
|
|
|
1523
1561
|
return m ? m[1] : null;
|
|
1524
1562
|
}
|
|
1525
1563
|
async function peekCodexCwd(file) {
|
|
1526
|
-
let
|
|
1564
|
+
let firstLine2;
|
|
1527
1565
|
try {
|
|
1528
1566
|
const buf = await readFile3(file, "utf8");
|
|
1529
1567
|
const nl = buf.indexOf("\n");
|
|
1530
|
-
|
|
1568
|
+
firstLine2 = nl === -1 ? buf : buf.slice(0, nl);
|
|
1531
1569
|
} catch {
|
|
1532
1570
|
return null;
|
|
1533
1571
|
}
|
|
1534
1572
|
try {
|
|
1535
|
-
const parsed = JSON.parse(
|
|
1573
|
+
const parsed = JSON.parse(firstLine2);
|
|
1536
1574
|
if (parsed.type === "session_meta" && typeof parsed.payload?.cwd === "string") {
|
|
1537
1575
|
return parsed.payload.cwd;
|
|
1538
1576
|
}
|
|
@@ -1812,12 +1850,34 @@ async function maybePromptPortless(args) {
|
|
|
1812
1850
|
import { confirm as confirm2, isCancel as isCancel3, log as log8, multiselect } from "@clack/prompts";
|
|
1813
1851
|
import { basename } from "path";
|
|
1814
1852
|
|
|
1853
|
+
// src/provider/cloud-backend.ts
|
|
1854
|
+
async function cloudBackendForProvider(provider) {
|
|
1855
|
+
switch (provider) {
|
|
1856
|
+
case "daytona":
|
|
1857
|
+
return (await import("./dist-BD5QJRDC.js")).daytonaBackend;
|
|
1858
|
+
case "hetzner":
|
|
1859
|
+
return (await import("./dist-BNI5PQYK.js")).hetznerBackend;
|
|
1860
|
+
case "vercel":
|
|
1861
|
+
return (await import("./dist-SJHY3HYN.js")).vercelBackend;
|
|
1862
|
+
default:
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1815
1867
|
// src/checkpoint-lookup.ts
|
|
1816
1868
|
async function checkpointExistsForProvider(provider, projectRoot, ref) {
|
|
1817
1869
|
if (provider === "docker") {
|
|
1818
1870
|
return await resolveCheckpoint(projectRoot, ref) !== null;
|
|
1819
1871
|
}
|
|
1820
|
-
|
|
1872
|
+
if (await resolveCloudCheckpoint(projectRoot, provider, ref) === null) return false;
|
|
1873
|
+
try {
|
|
1874
|
+
const backend = await cloudBackendForProvider(provider);
|
|
1875
|
+
if (!backend) return true;
|
|
1876
|
+
const { live } = await probeCloudCheckpoint(backend, projectRoot, ref);
|
|
1877
|
+
return live;
|
|
1878
|
+
} catch {
|
|
1879
|
+
return true;
|
|
1880
|
+
}
|
|
1821
1881
|
}
|
|
1822
1882
|
|
|
1823
1883
|
// src/wizard.ts
|
|
@@ -1927,6 +1987,7 @@ function pickCreateOpts(opts) {
|
|
|
1927
1987
|
sharedDockerCache: opts.sharedDockerCache,
|
|
1928
1988
|
portless: opts.portless,
|
|
1929
1989
|
sessionName: opts.sessionName,
|
|
1990
|
+
dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
|
|
1930
1991
|
memory: opts.memory,
|
|
1931
1992
|
cpus: opts.cpus,
|
|
1932
1993
|
pidsLimit: opts.pidsLimit,
|
|
@@ -1969,6 +2030,8 @@ function buildClaudeCliOverrides(opts) {
|
|
|
1969
2030
|
if (opts.sharedDockerCache === true) box.dockerCacheShared = true;
|
|
1970
2031
|
const claude = {};
|
|
1971
2032
|
if (opts.sessionName !== void 0) claude.sessionName = opts.sessionName;
|
|
2033
|
+
if (opts.dangerouslySkipPermissions !== void 0)
|
|
2034
|
+
claude.dangerouslySkipPermissions = opts.dangerouslySkipPermissions;
|
|
1972
2035
|
const out = {};
|
|
1973
2036
|
if (Object.keys(box).length > 0) out.box = box;
|
|
1974
2037
|
if (Object.keys(claude).length > 0) out.claude = claude;
|
|
@@ -2018,6 +2081,34 @@ async function maybeRunClaudeLogin(args) {
|
|
|
2018
2081
|
}
|
|
2019
2082
|
log9.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
|
|
2020
2083
|
}
|
|
2084
|
+
async function maybeRunCloudClaudeLogin(args) {
|
|
2085
|
+
if (!process.stdin.isTTY || args.yes) return;
|
|
2086
|
+
if (args.authSource === "host-env") return;
|
|
2087
|
+
const hasCreds = await hostBackupHasCredentials();
|
|
2088
|
+
const expired = hasCreds && await hostClaudeBackupExpired();
|
|
2089
|
+
if (hasCreds && !expired) return;
|
|
2090
|
+
const message = expired ? "Your saved Claude login looks expired. Sign in again? (saved and reused by every box)" : "Sign in with your Claude subscription? (saved and reused by every box)";
|
|
2091
|
+
const answer = await confirm3({ message, initialValue: true });
|
|
2092
|
+
if (isCancel4(answer) || !answer) {
|
|
2093
|
+
log9.info("Skipped sign-in \u2014 claude will prompt you to /login inside the box.");
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
const s = spinner3();
|
|
2097
|
+
s.start("preparing sandbox image");
|
|
2098
|
+
await ensureImage(args.image, { onProgress: (line) => s.message(clampSpinnerLine(line)) });
|
|
2099
|
+
s.message("preparing claude config");
|
|
2100
|
+
await ensureClaudeVolume(
|
|
2101
|
+
{ volume: SHARED_CLAUDE_VOLUME },
|
|
2102
|
+
{ syncFromHost: true, image: args.image, hostWorkspace: args.hostWorkspace }
|
|
2103
|
+
);
|
|
2104
|
+
s.stop("image ready");
|
|
2105
|
+
const exitCode = await runClaudeLoginContainer(args.image, ["--claudeai"]);
|
|
2106
|
+
if (exitCode !== 0) {
|
|
2107
|
+
log9.warn("Claude login did not complete; continuing \u2014 run `agentbox claude login` to retry.");
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
log9.success("Signed in with your Claude subscription \u2014 saved for future boxes.");
|
|
2111
|
+
}
|
|
2021
2112
|
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(
|
|
2022
2113
|
"--snapshot <ref>",
|
|
2023
2114
|
"start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
|
|
@@ -2032,6 +2123,12 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
2032
2123
|
"--isolate-claude-config",
|
|
2033
2124
|
"use a per-box ~/.claude volume instead of the shared agentbox-claude-config"
|
|
2034
2125
|
).option("--with-playwright", "also install @playwright/cli@latest globally inside the box").option(
|
|
2126
|
+
"--dangerously-skip-permissions",
|
|
2127
|
+
"launch claude with --dangerously-skip-permissions (auto-accept tool use); on by default since boxes are isolated"
|
|
2128
|
+
).option(
|
|
2129
|
+
"--no-dangerously-skip-permissions",
|
|
2130
|
+
"do not pass --dangerously-skip-permissions to claude in this box"
|
|
2131
|
+
).option(
|
|
2035
2132
|
"--with-env",
|
|
2036
2133
|
"copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
|
|
2037
2134
|
).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
|
|
@@ -2159,6 +2256,13 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
2159
2256
|
yes: !!opts.yes,
|
|
2160
2257
|
hostWorkspace: opts.workspace
|
|
2161
2258
|
});
|
|
2259
|
+
} else {
|
|
2260
|
+
await maybeRunCloudClaudeLogin({
|
|
2261
|
+
image: DEFAULT_BOX_IMAGE,
|
|
2262
|
+
authSource: resolved.source,
|
|
2263
|
+
yes: !!opts.yes,
|
|
2264
|
+
hostWorkspace: opts.workspace
|
|
2265
|
+
});
|
|
2162
2266
|
}
|
|
2163
2267
|
const portlessEnabled = isCloud ? void 0 : await maybePromptPortless({
|
|
2164
2268
|
engine: await detectEngine(),
|
|
@@ -2198,6 +2302,7 @@ var claudeCommand = new Command2("claude").description("Create a sandboxed box a
|
|
|
2198
2302
|
if (wiz.action === "launch-with-prompt" && wiz.initialPrompt) {
|
|
2199
2303
|
effectiveClaudeArgs = buildPromptArgs("claude-code", wiz.initialPrompt, claudeArgs);
|
|
2200
2304
|
}
|
|
2305
|
+
effectiveClaudeArgs = applyClaudeSkipPermissions(effectiveClaudeArgs, cfg.effective);
|
|
2201
2306
|
let fromBranch;
|
|
2202
2307
|
let useBranch;
|
|
2203
2308
|
try {
|
|
@@ -2385,6 +2490,12 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
|
|
|
2385
2490
|
const attachIn = resolveAttachInOption(opts);
|
|
2386
2491
|
const cliOverrides = {};
|
|
2387
2492
|
if (opts.sessionName) cliOverrides.claude = { sessionName: opts.sessionName };
|
|
2493
|
+
if (opts.dangerouslySkipPermissions !== void 0) {
|
|
2494
|
+
cliOverrides.claude = {
|
|
2495
|
+
...cliOverrides.claude,
|
|
2496
|
+
dangerouslySkipPermissions: opts.dangerouslySkipPermissions
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2388
2499
|
if (attachIn !== void 0) cliOverrides.attach = { openIn: attachIn };
|
|
2389
2500
|
const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides });
|
|
2390
2501
|
const sessionName = cfg.effective.claude.sessionName;
|
|
@@ -2451,7 +2562,7 @@ async function startOrAttachClaude(box, claudeArgs, opts, resumePrepared) {
|
|
|
2451
2562
|
volume: box.claudeConfigVolume ?? SHARED_CLAUDE_VOLUME,
|
|
2452
2563
|
onProgress: (line) => s.message(clampSpinnerLine(line))
|
|
2453
2564
|
});
|
|
2454
|
-
let effectiveArgs = claudeArgs;
|
|
2565
|
+
let effectiveArgs = applyClaudeSkipPermissions(claudeArgs, cfg.effective);
|
|
2455
2566
|
if (resumePrepared) {
|
|
2456
2567
|
s.message("uploading claude session into box");
|
|
2457
2568
|
try {
|
|
@@ -2582,8 +2693,12 @@ var claudeStartCommand = new Command2("start").description(
|
|
|
2582
2693
|
return;
|
|
2583
2694
|
}
|
|
2584
2695
|
const cfg = await loadEffectiveConfig(box.workspacePath, {
|
|
2585
|
-
cliOverrides:
|
|
2696
|
+
cliOverrides: {
|
|
2697
|
+
...attachIn ? { attach: { openIn: attachIn } } : {},
|
|
2698
|
+
...opts.dangerouslySkipPermissions !== void 0 ? { claude: { dangerouslySkipPermissions: opts.dangerouslySkipPermissions } } : {}
|
|
2699
|
+
}
|
|
2586
2700
|
});
|
|
2701
|
+
effectiveClaudeArgs = applyClaudeSkipPermissions(effectiveClaudeArgs, cfg.effective);
|
|
2587
2702
|
if (resumePrepared) {
|
|
2588
2703
|
try {
|
|
2589
2704
|
const provider = await providerForBox(box);
|
|
@@ -2655,11 +2770,11 @@ var CLOUD_BACKENDS = ["daytona", "hetzner", "vercel"];
|
|
|
2655
2770
|
async function cloudProviderFor(backend) {
|
|
2656
2771
|
switch (backend) {
|
|
2657
2772
|
case "daytona":
|
|
2658
|
-
return (await import("./dist-
|
|
2773
|
+
return (await import("./dist-BD5QJRDC.js")).daytonaProvider;
|
|
2659
2774
|
case "hetzner":
|
|
2660
|
-
return (await import("./dist-
|
|
2775
|
+
return (await import("./dist-BNI5PQYK.js")).hetznerProvider;
|
|
2661
2776
|
case "vercel":
|
|
2662
|
-
return (await import("./dist-
|
|
2777
|
+
return (await import("./dist-SJHY3HYN.js")).vercelProvider;
|
|
2663
2778
|
}
|
|
2664
2779
|
}
|
|
2665
2780
|
var CHECKPOINT_NOTICE = "Checkpoint in progress \u2014 the box will be unresponsive for a moment";
|
|
@@ -2673,7 +2788,7 @@ var createSub = new Command3("create").description("Capture a box state as a pro
|
|
|
2673
2788
|
).option("--name <name>", "checkpoint name (default: <box-name>-<next>)").option("--merged", "flatten lower+upper into one tree instead of a layered delta").option("--set-default", "mark this checkpoint as the project default for new boxes").option(
|
|
2674
2789
|
"--replace",
|
|
2675
2790
|
"if a checkpoint with the same name exists, rm it first (idempotent recapture; safe to retry when the previous run's stdout was lost)"
|
|
2676
|
-
).action(async (idOrName, opts) => {
|
|
2791
|
+
).option("-y, --yes", 'skip the vercel "box will reboot" confirmation prompt').action(async (idOrName, opts) => {
|
|
2677
2792
|
try {
|
|
2678
2793
|
const box = await resolveBoxOrExit(idOrName);
|
|
2679
2794
|
const providerName = box.provider ?? "docker";
|
|
@@ -2912,6 +3027,16 @@ async function runCloudCheckpointCreate(box, opts) {
|
|
|
2912
3027
|
if (!provider.checkpoint) {
|
|
2913
3028
|
throw new Error(`provider '${box.provider ?? "docker"}' doesn't support checkpoints`);
|
|
2914
3029
|
}
|
|
3030
|
+
if ((box.provider ?? "docker") === "vercel" && !opts.yes && process.stdin.isTTY) {
|
|
3031
|
+
const ok = await confirm4({
|
|
3032
|
+
message: "Create checkpoint? The vercel box will stop and reboot.",
|
|
3033
|
+
initialValue: false
|
|
3034
|
+
});
|
|
3035
|
+
if (isCancel5(ok) || !ok) {
|
|
3036
|
+
log10.info("cancelled");
|
|
3037
|
+
return;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
2915
3040
|
const noticeId = await setRelayNotice(
|
|
2916
3041
|
box.id,
|
|
2917
3042
|
"checkpoint",
|
|
@@ -3016,10 +3141,10 @@ async function writeAgentboxSshAlias(opts) {
|
|
|
3016
3141
|
await fs.writeFile(path, next, { mode: 384 });
|
|
3017
3142
|
await fs.chmod(path, 384);
|
|
3018
3143
|
}
|
|
3019
|
-
function parseSshTarget(
|
|
3144
|
+
function parseSshTarget(argv2) {
|
|
3020
3145
|
let target;
|
|
3021
|
-
for (let i =
|
|
3022
|
-
const v =
|
|
3146
|
+
for (let i = argv2.length - 1; i >= 0; i--) {
|
|
3147
|
+
const v = argv2[i];
|
|
3023
3148
|
if (!v || v.startsWith("-")) continue;
|
|
3024
3149
|
const at = v.indexOf("@");
|
|
3025
3150
|
if (at <= 0) continue;
|
|
@@ -3028,9 +3153,9 @@ function parseSshTarget(argv) {
|
|
|
3028
3153
|
}
|
|
3029
3154
|
if (!target) return void 0;
|
|
3030
3155
|
let identityFile;
|
|
3031
|
-
for (let i = 0; i <
|
|
3032
|
-
if (
|
|
3033
|
-
identityFile =
|
|
3156
|
+
for (let i = 0; i < argv2.length - 1; i++) {
|
|
3157
|
+
if (argv2[i] === "-i") {
|
|
3158
|
+
identityFile = argv2[i + 1];
|
|
3034
3159
|
break;
|
|
3035
3160
|
}
|
|
3036
3161
|
}
|
|
@@ -3258,6 +3383,9 @@ async function fetchServiceNamesDocker(container) {
|
|
|
3258
3383
|
}
|
|
3259
3384
|
|
|
3260
3385
|
// src/commands/codex.ts
|
|
3386
|
+
import { access } from "fs/promises";
|
|
3387
|
+
import { homedir as homedir8 } from "os";
|
|
3388
|
+
import { join as join10 } from "path";
|
|
3261
3389
|
import { confirm as confirm5, intro as intro2, isCancel as isCancel6, log as log12, outro as outro3, spinner as spinner4 } from "@clack/prompts";
|
|
3262
3390
|
import { Command as Command5 } from "commander";
|
|
3263
3391
|
function reattachRef2(r) {
|
|
@@ -3276,6 +3404,7 @@ function pickCodexCreateOpts(opts) {
|
|
|
3276
3404
|
sharedDockerCache: opts.sharedDockerCache,
|
|
3277
3405
|
portless: opts.portless,
|
|
3278
3406
|
sessionName: opts.sessionName,
|
|
3407
|
+
dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
|
|
3279
3408
|
memory: opts.memory,
|
|
3280
3409
|
cpus: opts.cpus,
|
|
3281
3410
|
pidsLimit: opts.pidsLimit,
|
|
@@ -3310,6 +3439,8 @@ function buildCodexCliOverrides(opts) {
|
|
|
3310
3439
|
if (opts.sharedDockerCache === true) box.dockerCacheShared = true;
|
|
3311
3440
|
const codex = {};
|
|
3312
3441
|
if (opts.sessionName !== void 0) codex.sessionName = opts.sessionName;
|
|
3442
|
+
if (opts.dangerouslySkipPermissions !== void 0)
|
|
3443
|
+
codex.dangerouslySkipPermissions = opts.dangerouslySkipPermissions;
|
|
3313
3444
|
const out = {};
|
|
3314
3445
|
if (Object.keys(box).length > 0) out.box = box;
|
|
3315
3446
|
if (Object.keys(codex).length > 0) out.codex = codex;
|
|
@@ -3348,6 +3479,43 @@ async function maybeRunCodexLogin(args) {
|
|
|
3348
3479
|
}
|
|
3349
3480
|
log12.success("Signed in to Codex \u2014 saved for future boxes.");
|
|
3350
3481
|
}
|
|
3482
|
+
async function cloudCodexCredAvailable(env = process.env) {
|
|
3483
|
+
if ((env["OPENAI_API_KEY"] ?? "").length > 0) return true;
|
|
3484
|
+
for (const p of [CODEX_CREDENTIALS_BACKUP_FILE, join10(homedir8(), ".codex", "auth.json")]) {
|
|
3485
|
+
try {
|
|
3486
|
+
await access(p);
|
|
3487
|
+
return true;
|
|
3488
|
+
} catch {
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return false;
|
|
3492
|
+
}
|
|
3493
|
+
async function maybeRunCloudCodexLogin(args) {
|
|
3494
|
+
if (!process.stdin.isTTY || args.yes) return;
|
|
3495
|
+
if (await cloudCodexCredAvailable()) return;
|
|
3496
|
+
const answer = await confirm5({
|
|
3497
|
+
message: "Sign in to Codex? (saved and reused by every box)",
|
|
3498
|
+
initialValue: true
|
|
3499
|
+
});
|
|
3500
|
+
if (isCancel6(answer) || !answer) {
|
|
3501
|
+
log12.info("Skipped sign-in \u2014 codex will prompt you to sign in inside the box.");
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
const s = spinner4();
|
|
3505
|
+
s.start("preparing sandbox image");
|
|
3506
|
+
await ensureImage(args.image, { onProgress: (line) => s.message(clampSpinnerLine(line)) });
|
|
3507
|
+
s.message("preparing codex config");
|
|
3508
|
+
await ensureCodexVolume({ volume: SHARED_CODEX_VOLUME }, { syncFromHost: true, image: args.image });
|
|
3509
|
+
s.stop("image ready");
|
|
3510
|
+
const exitCode = await runCodexLoginContainer(args.image, []);
|
|
3511
|
+
if (exitCode !== 0) {
|
|
3512
|
+
log12.warn("Codex login did not complete; continuing \u2014 run `agentbox codex login` to retry.");
|
|
3513
|
+
return;
|
|
3514
|
+
}
|
|
3515
|
+
const { copied } = await extractCodexCredentials(SHARED_CODEX_VOLUME, args.image);
|
|
3516
|
+
if (copied) log12.success("Signed in to Codex \u2014 saved for future boxes.");
|
|
3517
|
+
else log12.warn("Codex login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
|
|
3518
|
+
}
|
|
3351
3519
|
var codexCommand = new Command5("codex").description("Create a sandboxed box and launch OpenAI Codex in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
|
|
3352
3520
|
"--snapshot <ref>",
|
|
3353
3521
|
"start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
|
|
@@ -3362,6 +3530,12 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
|
|
|
3362
3530
|
"--isolate-codex-config",
|
|
3363
3531
|
"use a per-box ~/.codex volume instead of the shared agentbox-codex-config"
|
|
3364
3532
|
).option("--with-playwright", "also install @playwright/cli@latest globally inside the box").option(
|
|
3533
|
+
"--dangerously-skip-permissions",
|
|
3534
|
+
"launch codex with --dangerously-bypass-approvals-and-sandbox (never prompt for approval); on by default since boxes are isolated"
|
|
3535
|
+
).option(
|
|
3536
|
+
"--no-dangerously-skip-permissions",
|
|
3537
|
+
"do not pass --dangerously-bypass-approvals-and-sandbox to codex in this box"
|
|
3538
|
+
).option(
|
|
3365
3539
|
"--with-env",
|
|
3366
3540
|
"copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
|
|
3367
3541
|
).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
|
|
@@ -3521,6 +3695,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
|
|
|
3521
3695
|
throw err;
|
|
3522
3696
|
}
|
|
3523
3697
|
if (isCloud) {
|
|
3698
|
+
await maybeRunCloudCodexLogin({ image: DEFAULT_BOX_IMAGE, yes: !!opts.yes });
|
|
3524
3699
|
const provider = await providerForCreate({ flag: opts.provider, config: cfg.effective });
|
|
3525
3700
|
const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
|
|
3526
3701
|
await cloudAgentCreate({
|
|
@@ -3542,7 +3717,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
|
|
|
3542
3717
|
binary: "codex",
|
|
3543
3718
|
sessionName: cfg.effective.codex.sessionName,
|
|
3544
3719
|
mode: "codex",
|
|
3545
|
-
extraArgs: codexArgs,
|
|
3720
|
+
extraArgs: applyCodexSkipPermissions(codexArgs, cfg.effective),
|
|
3546
3721
|
verbose: opts.verbose === true,
|
|
3547
3722
|
openIn: cfg.effective.attach.openIn,
|
|
3548
3723
|
attach: opts.attach !== false,
|
|
@@ -3613,7 +3788,7 @@ var codexCommand = new Command5("codex").description("Create a sandboxed box and
|
|
|
3613
3788
|
cmdLog.write(line);
|
|
3614
3789
|
}
|
|
3615
3790
|
});
|
|
3616
|
-
let effectiveCodexArgs = codexArgs;
|
|
3791
|
+
let effectiveCodexArgs = applyCodexSkipPermissions(codexArgs, cfg.effective);
|
|
3617
3792
|
if (resumePrepared) {
|
|
3618
3793
|
s.message("uploading codex session into box");
|
|
3619
3794
|
cmdLog.write("uploading codex session into box");
|
|
@@ -3685,6 +3860,12 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
|
|
|
3685
3860
|
const attachIn = resolveAttachInOption(opts);
|
|
3686
3861
|
const cliOverrides = {};
|
|
3687
3862
|
if (opts.sessionName) cliOverrides.codex = { sessionName: opts.sessionName };
|
|
3863
|
+
if (opts.dangerouslySkipPermissions !== void 0) {
|
|
3864
|
+
cliOverrides.codex = {
|
|
3865
|
+
...cliOverrides.codex,
|
|
3866
|
+
dangerouslySkipPermissions: opts.dangerouslySkipPermissions
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3688
3869
|
if (attachIn !== void 0) cliOverrides.attach = { openIn: attachIn };
|
|
3689
3870
|
const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides });
|
|
3690
3871
|
const sessionName = cfg.effective.codex.sessionName;
|
|
@@ -3736,7 +3917,7 @@ async function startOrAttachCodex(box, codexArgs, opts, resumePrepared) {
|
|
|
3736
3917
|
await ensureCodexInstalled(box.container, {
|
|
3737
3918
|
onProgress: (line) => s.message(clampSpinnerLine(line))
|
|
3738
3919
|
});
|
|
3739
|
-
let effectiveArgs = codexArgs;
|
|
3920
|
+
let effectiveArgs = applyCodexSkipPermissions(codexArgs, cfg.effective);
|
|
3740
3921
|
if (resumePrepared) {
|
|
3741
3922
|
s.message("uploading codex session into box");
|
|
3742
3923
|
try {
|
|
@@ -3857,8 +4038,12 @@ var codexStartCommand = new Command5("start").description(
|
|
|
3857
4038
|
return;
|
|
3858
4039
|
}
|
|
3859
4040
|
const cfg = await loadEffectiveConfig(box.workspacePath, {
|
|
3860
|
-
cliOverrides:
|
|
4041
|
+
cliOverrides: {
|
|
4042
|
+
...attachIn ? { attach: { openIn: attachIn } } : {},
|
|
4043
|
+
...opts.dangerouslySkipPermissions !== void 0 ? { codex: { dangerouslySkipPermissions: opts.dangerouslySkipPermissions } } : {}
|
|
4044
|
+
}
|
|
3861
4045
|
});
|
|
4046
|
+
effectiveCodexArgs = applyCodexSkipPermissions(effectiveCodexArgs, cfg.effective);
|
|
3862
4047
|
if (resumePrepared) {
|
|
3863
4048
|
try {
|
|
3864
4049
|
const provider = await providerForBox(box);
|
|
@@ -3926,6 +4111,9 @@ codexCommand.addCommand(codexStartCommand);
|
|
|
3926
4111
|
codexCommand.addCommand(codexLoginCommand);
|
|
3927
4112
|
|
|
3928
4113
|
// src/commands/opencode.ts
|
|
4114
|
+
import { access as access2 } from "fs/promises";
|
|
4115
|
+
import { homedir as homedir9 } from "os";
|
|
4116
|
+
import { join as join11 } from "path";
|
|
3929
4117
|
import { confirm as confirm6, intro as intro3, isCancel as isCancel7, log as log13, outro as outro4, spinner as spinner5 } from "@clack/prompts";
|
|
3930
4118
|
import { Command as Command6 } from "commander";
|
|
3931
4119
|
function reattachRef3(r) {
|
|
@@ -4019,6 +4207,48 @@ async function maybeRunOpencodeLogin(args) {
|
|
|
4019
4207
|
}
|
|
4020
4208
|
log13.success("Signed in to OpenCode \u2014 saved for future boxes.");
|
|
4021
4209
|
}
|
|
4210
|
+
async function cloudOpencodeCredAvailable(env = process.env) {
|
|
4211
|
+
for (const k of OPENCODE_FORWARDED_ENV_KEYS) {
|
|
4212
|
+
if ((env[k] ?? "").length > 0) return true;
|
|
4213
|
+
}
|
|
4214
|
+
for (const p of [OPENCODE_CREDENTIALS_BACKUP_FILE, join11(homedir9(), ".local", "share", "opencode", "auth.json")]) {
|
|
4215
|
+
try {
|
|
4216
|
+
await access2(p);
|
|
4217
|
+
return true;
|
|
4218
|
+
} catch {
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
return false;
|
|
4222
|
+
}
|
|
4223
|
+
async function maybeRunCloudOpencodeLogin(args) {
|
|
4224
|
+
if (!process.stdin.isTTY || args.yes) return;
|
|
4225
|
+
if (await cloudOpencodeCredAvailable()) return;
|
|
4226
|
+
const answer = await confirm6({
|
|
4227
|
+
message: "Sign in to OpenCode? (pick a provider; saved and reused by every box)",
|
|
4228
|
+
initialValue: true
|
|
4229
|
+
});
|
|
4230
|
+
if (isCancel7(answer) || !answer) {
|
|
4231
|
+
log13.info("Skipped sign-in \u2014 opencode will prompt you to sign in inside the box.");
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
4234
|
+
const s = spinner5();
|
|
4235
|
+
s.start("preparing sandbox image");
|
|
4236
|
+
await ensureImage(args.image, { onProgress: (line) => s.message(clampSpinnerLine(line)) });
|
|
4237
|
+
s.message("preparing opencode config");
|
|
4238
|
+
await ensureOpencodeVolume(
|
|
4239
|
+
{ volume: SHARED_OPENCODE_VOLUME },
|
|
4240
|
+
{ syncFromHost: true, image: args.image }
|
|
4241
|
+
);
|
|
4242
|
+
s.stop("image ready");
|
|
4243
|
+
const exitCode = await runOpencodeLoginContainer(args.image, []);
|
|
4244
|
+
if (exitCode !== 0) {
|
|
4245
|
+
log13.warn("OpenCode login did not complete; continuing \u2014 run `agentbox opencode login` to retry.");
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
const { copied } = await extractOpencodeCredentials(SHARED_OPENCODE_VOLUME, args.image);
|
|
4249
|
+
if (copied) log13.success("Signed in to OpenCode \u2014 saved for future boxes.");
|
|
4250
|
+
else log13.warn("OpenCode login finished but no auth.json was captured \u2014 sign in inside the box if needed.");
|
|
4251
|
+
}
|
|
4022
4252
|
var opencodeCommand = new Command6("opencode").description("Create a sandboxed box and launch OpenCode in a detachable tmux session").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "tar-pipe directly from the live host workspace at create time").option(
|
|
4023
4253
|
"--snapshot <ref>",
|
|
4024
4254
|
"start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
|
|
@@ -4177,6 +4407,7 @@ var opencodeCommand = new Command6("opencode").description("Create a sandboxed b
|
|
|
4177
4407
|
throw err;
|
|
4178
4408
|
}
|
|
4179
4409
|
if (isCloud) {
|
|
4410
|
+
await maybeRunCloudOpencodeLogin({ image: DEFAULT_BOX_IMAGE, yes: !!opts.yes });
|
|
4180
4411
|
const provider = await providerForCreate({ flag: opts.provider, config: cfg.effective });
|
|
4181
4412
|
const withPlaywright = cfg.effective.box.withPlaywright || cfg.effective.browser.default !== "agent-browser";
|
|
4182
4413
|
await cloudAgentCreate({
|
|
@@ -4908,7 +5139,10 @@ async function attachShell(record) {
|
|
|
4908
5139
|
var createCommand = new Command9("create").description("Create and start a new agent box (Docker container with /workspace seeded via in-container git worktree)").option("-w, --workspace <path>", "host workspace to mount", process.cwd()).option("-n, --name <name>", "friendly box name (default: <workspace-basename>-<id>)").option("--provider <name>", "sandbox backend: 'docker' (default) or 'daytona' (cloud)").option("--host-snapshot", "APFS-clone the host workspace into a per-box scratch dir before seeding /workspace (stabilizes the tar-pipe source)").option("--no-host-snapshot", "bind the live workspace directly (host edits leak into reads)").option(
|
|
4909
5140
|
"--snapshot <ref>",
|
|
4910
5141
|
"start from a project checkpoint (see `agentbox checkpoint`); overrides box.defaultCheckpoint"
|
|
4911
|
-
).option("--image <ref>", "override the box image", void 0).option(
|
|
5142
|
+
).option("--image <ref>", "override the box image", void 0).option(
|
|
5143
|
+
"--build",
|
|
5144
|
+
"build the docker base image locally instead of pulling the prebuilt one from the registry"
|
|
5145
|
+
).option("--attach", "drop into a shell inside the box after it is ready").option("--with-playwright", "also install @playwright/cli@latest globally inside the box").option(
|
|
4912
5146
|
"--with-env",
|
|
4913
5147
|
"copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at create time (gitignore-bypassing)"
|
|
4914
5148
|
).option("--no-vnc", "disable the per-box Xvnc + noVNC web client (on by default)").option(
|
|
@@ -5046,6 +5280,8 @@ var createCommand = new Command9("create").description("Create and start a new a
|
|
|
5046
5280
|
name: opts.name,
|
|
5047
5281
|
checkpointRef,
|
|
5048
5282
|
image: cfg.effective.box.image,
|
|
5283
|
+
allowPull: opts.build ? false : void 0,
|
|
5284
|
+
imageRegistry: cfg.effective.box.imageRegistry,
|
|
5049
5285
|
withPlaywright,
|
|
5050
5286
|
withEnv: cfg.effective.box.withEnv,
|
|
5051
5287
|
envFilesToImport: wiz.envFilesToImport,
|
|
@@ -5126,7 +5362,7 @@ var createCommand = new Command9("create").description("Create and start a new a
|
|
|
5126
5362
|
}
|
|
5127
5363
|
outro5("done");
|
|
5128
5364
|
if (attachClaudeAfter) {
|
|
5129
|
-
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-
|
|
5365
|
+
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-2DGI6FUA.js");
|
|
5130
5366
|
await cloudAgentAttach2({
|
|
5131
5367
|
box: result.record,
|
|
5132
5368
|
binary: "claude",
|
|
@@ -5169,13 +5405,16 @@ import { Command as Command10 } from "commander";
|
|
|
5169
5405
|
var SIDEBAR_WIDTH = 33;
|
|
5170
5406
|
var MIN_RIGHT_W = 20;
|
|
5171
5407
|
var MIN_RIGHT_H = 4;
|
|
5172
|
-
function computeLayout(cols, rows) {
|
|
5408
|
+
function computeLayout(cols, rows, requestedAlertH = 0) {
|
|
5173
5409
|
const sidebarW = Math.min(SIDEBAR_WIDTH, Math.max(0, cols - MIN_RIGHT_W - 1));
|
|
5174
5410
|
const sepX = sidebarW;
|
|
5175
5411
|
const rightX = sidebarW + 1;
|
|
5176
5412
|
const rightW = Math.max(0, cols - rightX);
|
|
5177
5413
|
const statusY = rows - 1;
|
|
5178
|
-
const
|
|
5414
|
+
const desired = Math.max(0, requestedAlertH);
|
|
5415
|
+
const alertH = statusY - desired >= MIN_RIGHT_H ? desired : 0;
|
|
5416
|
+
const paneH = Math.max(0, statusY - alertH);
|
|
5417
|
+
const alertY = statusY - alertH;
|
|
5179
5418
|
return {
|
|
5180
5419
|
cols,
|
|
5181
5420
|
rows,
|
|
@@ -5183,6 +5422,8 @@ function computeLayout(cols, rows) {
|
|
|
5183
5422
|
sepX,
|
|
5184
5423
|
right: { x: rightX, y: 0, w: rightW, h: paneH },
|
|
5185
5424
|
statusY,
|
|
5425
|
+
alertH,
|
|
5426
|
+
alertY,
|
|
5186
5427
|
tooSmall: rightW < MIN_RIGHT_W || paneH < MIN_RIGHT_H
|
|
5187
5428
|
};
|
|
5188
5429
|
}
|
|
@@ -5478,6 +5719,21 @@ var BLANK = {
|
|
|
5478
5719
|
strike: false
|
|
5479
5720
|
};
|
|
5480
5721
|
var PtySession = class {
|
|
5722
|
+
/** Box this session attaches to. Identifies it in the compositor's pool. */
|
|
5723
|
+
boxId;
|
|
5724
|
+
/** When true, the compositor keeps this session alive (in its pool) across
|
|
5725
|
+
* box switches instead of disposing it — see {@link Compositor.liveSessions}. */
|
|
5726
|
+
keepAlive;
|
|
5727
|
+
/** Agent/shell mode of this attach. The compositor restores `activeMode`
|
|
5728
|
+
* (drives the footer) from this when re-showing a pooled session. */
|
|
5729
|
+
mode;
|
|
5730
|
+
/**
|
|
5731
|
+
* Whether this session is the one currently shown in the right pane. A
|
|
5732
|
+
* kept-alive hidden session (`active === false`) still consumes PTY output
|
|
5733
|
+
* to keep its headless buffer current, but must NOT trigger right-pane
|
|
5734
|
+
* repaints. The compositor flips this on show/hide.
|
|
5735
|
+
*/
|
|
5736
|
+
active = true;
|
|
5481
5737
|
term;
|
|
5482
5738
|
pty;
|
|
5483
5739
|
cleanup;
|
|
@@ -5485,7 +5741,10 @@ var PtySession = class {
|
|
|
5485
5741
|
// Reused per cell read — valid only until the next cell() call (the renderer
|
|
5486
5742
|
// consumes it synchronously within composeRow).
|
|
5487
5743
|
out = { ...BLANK };
|
|
5488
|
-
constructor(spawn5, TerminalClass, command, args, cols, rows, onRenderable, onExit, cleanup) {
|
|
5744
|
+
constructor(spawn5, TerminalClass, boxId, keepAlive, mode, command, args, cols, rows, onRenderable, onExit, cleanup) {
|
|
5745
|
+
this.boxId = boxId;
|
|
5746
|
+
this.keepAlive = keepAlive;
|
|
5747
|
+
this.mode = mode;
|
|
5489
5748
|
this.term = new TerminalClass({
|
|
5490
5749
|
cols,
|
|
5491
5750
|
rows,
|
|
@@ -5501,13 +5760,15 @@ var PtySession = class {
|
|
|
5501
5760
|
env: process.env
|
|
5502
5761
|
});
|
|
5503
5762
|
this.pty.onData((d) => {
|
|
5504
|
-
this.term.write(d, () =>
|
|
5763
|
+
this.term.write(d, () => {
|
|
5764
|
+
if (this.active) onRenderable();
|
|
5765
|
+
});
|
|
5505
5766
|
});
|
|
5506
5767
|
this.term.onData((d) => {
|
|
5507
5768
|
if (!this.disposed) this.pty.write(d);
|
|
5508
5769
|
});
|
|
5509
5770
|
this.pty.onExit(() => {
|
|
5510
|
-
if (!this.disposed) onExit();
|
|
5771
|
+
if (!this.disposed) onExit(this.boxId);
|
|
5511
5772
|
});
|
|
5512
5773
|
}
|
|
5513
5774
|
write(bytes) {
|
|
@@ -5569,6 +5830,7 @@ var SB_AWAITING = SB_BG + "\x1B[38;5;51m\x1B[1m";
|
|
|
5569
5830
|
var SGR_RESET = "\x1B[0m";
|
|
5570
5831
|
var POLL_MS = 1e3;
|
|
5571
5832
|
var FRAME_MS = 16;
|
|
5833
|
+
var KEEP_ALIVE_MAX = 6;
|
|
5572
5834
|
var RESIZE_DEBOUNCE_MS = 120;
|
|
5573
5835
|
var LEADER_LINGER_MS = 1500;
|
|
5574
5836
|
var NOTICE_SPINNER_MS = 120;
|
|
@@ -5581,7 +5843,7 @@ var Compositor = class {
|
|
|
5581
5843
|
constructor(deps, initialId) {
|
|
5582
5844
|
this.deps = deps;
|
|
5583
5845
|
this.selectedId = initialId;
|
|
5584
|
-
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30);
|
|
5846
|
+
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30, 0);
|
|
5585
5847
|
this.parser = new InputParser({
|
|
5586
5848
|
onEvent: (e) => {
|
|
5587
5849
|
if (e.type === "leader") {
|
|
@@ -5642,7 +5904,17 @@ var Compositor = class {
|
|
|
5642
5904
|
inp = process.stdin;
|
|
5643
5905
|
boxes = [];
|
|
5644
5906
|
selectedId;
|
|
5907
|
+
/** The session currently shown in the right pane (may also be in
|
|
5908
|
+
* {@link liveSessions} when it's keep-alive). */
|
|
5645
5909
|
session = null;
|
|
5910
|
+
/**
|
|
5911
|
+
* Pool of kept-alive sessions, keyed by box id, for providers whose attach
|
|
5912
|
+
* is expensive to reconnect (vercel). Hidden entries keep their PTY + headless
|
|
5913
|
+
* buffer alive so switching back is instant — no probe, no re-spawn. Map
|
|
5914
|
+
* insertion order doubles as LRU recency (re-set on activate); bounded by
|
|
5915
|
+
* {@link KEEP_ALIVE_MAX}. Reconnect-cheap providers never enter this map.
|
|
5916
|
+
*/
|
|
5917
|
+
liveSessions = /* @__PURE__ */ new Map();
|
|
5646
5918
|
placeholder = null;
|
|
5647
5919
|
menu = null;
|
|
5648
5920
|
lifecycleMenu = null;
|
|
@@ -5734,6 +6006,22 @@ var Compositor = class {
|
|
|
5734
6006
|
} catch {
|
|
5735
6007
|
}
|
|
5736
6008
|
this.syncPromptSubscriptions();
|
|
6009
|
+
this.reconcileLiveSessions();
|
|
6010
|
+
}
|
|
6011
|
+
/**
|
|
6012
|
+
* Drop pooled (hidden) sessions whose box is gone or no longer running, so a
|
|
6013
|
+
* paused/stopped/destroyed box can't keep its remote attach process alive.
|
|
6014
|
+
* The *active* session is intentionally skipped — it's torn down on the next
|
|
6015
|
+
* switch/re-resolve via {@link deactivateActive} (which checks box state),
|
|
6016
|
+
* so evicting it here would blank the pane out from under the poll's
|
|
6017
|
+
* re-resolve logic.
|
|
6018
|
+
*/
|
|
6019
|
+
reconcileLiveSessions() {
|
|
6020
|
+
for (const boxId of [...this.liveSessions.keys()]) {
|
|
6021
|
+
if (this.session && this.session.boxId === boxId) continue;
|
|
6022
|
+
const running = this.boxes.some((b) => b.id === boxId && b.state === "running");
|
|
6023
|
+
if (!running) this.evictSession(boxId);
|
|
6024
|
+
}
|
|
5737
6025
|
}
|
|
5738
6026
|
/**
|
|
5739
6027
|
* Diff the current box list against {@link promptStreams}: subscribe to
|
|
@@ -5758,7 +6046,7 @@ var Compositor = class {
|
|
|
5758
6046
|
let changed = this.activePrompts.delete(boxId);
|
|
5759
6047
|
if (this.activeNotices.delete(boxId)) changed = true;
|
|
5760
6048
|
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
5761
|
-
if (changed) this.
|
|
6049
|
+
if (changed) this.redrawForAlert();
|
|
5762
6050
|
}
|
|
5763
6051
|
}
|
|
5764
6052
|
for (const boxId of wanted) {
|
|
@@ -5769,21 +6057,21 @@ var Compositor = class {
|
|
|
5769
6057
|
onPrompt: (ev) => {
|
|
5770
6058
|
if (this.tornDown) return;
|
|
5771
6059
|
this.activePrompts.set(boxId, ev);
|
|
5772
|
-
this.
|
|
6060
|
+
this.redrawForAlert();
|
|
5773
6061
|
},
|
|
5774
6062
|
onResolved: (id) => {
|
|
5775
6063
|
if (this.tornDown) return;
|
|
5776
6064
|
const current = this.activePrompts.get(boxId);
|
|
5777
6065
|
if (current && current.id === id) {
|
|
5778
6066
|
this.activePrompts.delete(boxId);
|
|
5779
|
-
this.
|
|
6067
|
+
this.redrawForAlert();
|
|
5780
6068
|
}
|
|
5781
6069
|
},
|
|
5782
6070
|
onNotice: (ev) => {
|
|
5783
6071
|
if (this.tornDown) return;
|
|
5784
6072
|
this.activeNotices.set(boxId, ev);
|
|
5785
6073
|
this.startNoticeSpinner();
|
|
5786
|
-
this.
|
|
6074
|
+
this.redrawForAlert();
|
|
5787
6075
|
},
|
|
5788
6076
|
onNoticeCleared: (id) => {
|
|
5789
6077
|
if (this.tornDown) return;
|
|
@@ -5791,7 +6079,7 @@ var Compositor = class {
|
|
|
5791
6079
|
if (current && current.id === id) {
|
|
5792
6080
|
this.activeNotices.delete(boxId);
|
|
5793
6081
|
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
5794
|
-
this.
|
|
6082
|
+
this.redrawForAlert();
|
|
5795
6083
|
}
|
|
5796
6084
|
},
|
|
5797
6085
|
onError: () => {
|
|
@@ -5818,9 +6106,11 @@ var Compositor = class {
|
|
|
5818
6106
|
return this.boxes.find((b) => b.id === this.selectedId);
|
|
5819
6107
|
}
|
|
5820
6108
|
async poll() {
|
|
5821
|
-
const
|
|
6109
|
+
const stateKey = () => JSON.stringify(
|
|
5822
6110
|
this.boxes.map((b) => [b.id, b.state, b.activity, b.sessionTitle])
|
|
5823
6111
|
);
|
|
6112
|
+
const before = stateKey();
|
|
6113
|
+
const beforeAlertH = this.alertHeight();
|
|
5824
6114
|
await this.refreshBoxes();
|
|
5825
6115
|
if (this.busy) {
|
|
5826
6116
|
} else if (!this.boxes.some((b) => b.id === this.selectedId) && this.boxes[0]) {
|
|
@@ -5832,19 +6122,99 @@ var Compositor = class {
|
|
|
5832
6122
|
const reresolve = this.session && !running || this.placeholder && running || this.menu && !running || this.lifecycleMenu != null && box?.state !== this.lifecycleMenu.state;
|
|
5833
6123
|
if (reresolve) await this.spawnActive();
|
|
5834
6124
|
}
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
6125
|
+
const stateChanged = stateKey() !== before;
|
|
6126
|
+
const alertChanged = this.alertHeight() !== beforeAlertH;
|
|
6127
|
+
if (alertChanged) {
|
|
6128
|
+
this.relayout();
|
|
6129
|
+
} else if (stateChanged) {
|
|
5838
6130
|
this.drawChrome();
|
|
5839
6131
|
}
|
|
5840
6132
|
}
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
6133
|
+
/**
|
|
6134
|
+
* Detach the active session from view. Keep-alive sessions (those in
|
|
6135
|
+
* {@link liveSessions}) stay running in the background; everything else is
|
|
6136
|
+
* disposed — matching the pre-pool dispose-on-switch behaviour for docker /
|
|
6137
|
+
* hetzner / daytona.
|
|
6138
|
+
*/
|
|
6139
|
+
deactivateActive() {
|
|
6140
|
+
const s = this.session;
|
|
6141
|
+
if (!s) return;
|
|
6142
|
+
s.active = false;
|
|
6143
|
+
const pooled = this.liveSessions.get(s.boxId) === s;
|
|
6144
|
+
const boxRunning = this.boxes.some((b) => b.id === s.boxId && b.state === "running");
|
|
6145
|
+
if (!pooled || !boxRunning) {
|
|
6146
|
+
if (pooled) this.liveSessions.delete(s.boxId);
|
|
6147
|
+
s.dispose();
|
|
6148
|
+
}
|
|
6149
|
+
this.session = null;
|
|
6150
|
+
}
|
|
6151
|
+
/** Dispose and drop the pooled session for `boxId` (box gone / stopped /
|
|
6152
|
+
* its attach died). Clears the active reference if it was the shown one. */
|
|
6153
|
+
evictSession(boxId) {
|
|
6154
|
+
const pooled = this.liveSessions.get(boxId);
|
|
6155
|
+
if (pooled) {
|
|
6156
|
+
this.liveSessions.delete(boxId);
|
|
6157
|
+
pooled.dispose();
|
|
6158
|
+
}
|
|
6159
|
+
if (this.session && this.session.boxId === boxId) {
|
|
6160
|
+
if (this.session !== pooled) this.session.dispose();
|
|
6161
|
+
this.session = null;
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
/** Bound the pool: evict least-recently-used pooled sessions (Map insertion
|
|
6165
|
+
* order) until at most {@link KEEP_ALIVE_MAX} remain, never the active one. */
|
|
6166
|
+
evictLruIfNeeded() {
|
|
6167
|
+
for (const boxId of this.liveSessions.keys()) {
|
|
6168
|
+
if (this.liveSessions.size <= KEEP_ALIVE_MAX) break;
|
|
6169
|
+
if (this.session && this.session.boxId === boxId) continue;
|
|
6170
|
+
this.evictSession(boxId);
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
/** Dispose the active session plus every pooled one (teardown). */
|
|
6174
|
+
disposeAllSessions() {
|
|
6175
|
+
const active = this.session;
|
|
6176
|
+
if (active && this.liveSessions.get(active.boxId) !== active) active.dispose();
|
|
5844
6177
|
this.session = null;
|
|
6178
|
+
for (const s of this.liveSessions.values()) s.dispose();
|
|
6179
|
+
this.liveSessions.clear();
|
|
6180
|
+
}
|
|
6181
|
+
/**
|
|
6182
|
+
* Show a pooled session in the right pane — the fast switch-back path: no
|
|
6183
|
+
* probe, no re-spawn, instant repaint from its already-current headless
|
|
6184
|
+
* buffer. Re-marks it most-recently-used and re-applies the current layout
|
|
6185
|
+
* size (it may have changed while hidden).
|
|
6186
|
+
*/
|
|
6187
|
+
activateSession(sess) {
|
|
6188
|
+
this.deactivateActive();
|
|
6189
|
+
this.placeholder = null;
|
|
6190
|
+
this.menu = null;
|
|
6191
|
+
this.lifecycleMenu = null;
|
|
6192
|
+
this.createMenu = null;
|
|
6193
|
+
this.pendingConfirm = null;
|
|
6194
|
+
this.session = sess;
|
|
6195
|
+
sess.active = true;
|
|
6196
|
+
this.activeMode = sess.mode;
|
|
6197
|
+
sess.resize(Math.max(1, this.layout.right.w), Math.max(1, this.layout.right.h));
|
|
6198
|
+
this.liveSessions.delete(sess.boxId);
|
|
6199
|
+
this.liveSessions.set(sess.boxId, sess);
|
|
6200
|
+
this.prevRows = null;
|
|
6201
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
6202
|
+
this.scheduleRender();
|
|
6203
|
+
}
|
|
6204
|
+
/**
|
|
6205
|
+
* Show the selected box. If a kept-alive session is pooled for it, re-show it
|
|
6206
|
+
* synchronously (instant). Otherwise fall through to the async resolve+spawn.
|
|
6207
|
+
*/
|
|
6208
|
+
showSelected() {
|
|
6209
|
+
const cached = this.liveSessions.get(this.selectedId);
|
|
6210
|
+
if (cached) {
|
|
6211
|
+
this.activateSession(cached);
|
|
6212
|
+
return;
|
|
6213
|
+
}
|
|
6214
|
+
void this.spawnActive();
|
|
5845
6215
|
}
|
|
5846
6216
|
async spawnActive() {
|
|
5847
|
-
this.
|
|
6217
|
+
this.deactivateActive();
|
|
5848
6218
|
this.placeholder = null;
|
|
5849
6219
|
this.menu = null;
|
|
5850
6220
|
this.lifecycleMenu = null;
|
|
@@ -5858,25 +6228,37 @@ var Compositor = class {
|
|
|
5858
6228
|
}
|
|
5859
6229
|
/** Turn a resolved/started target into the right-pane state. */
|
|
5860
6230
|
applyTarget(target) {
|
|
5861
|
-
this.
|
|
6231
|
+
this.deactivateActive();
|
|
5862
6232
|
this.placeholder = null;
|
|
5863
6233
|
this.menu = null;
|
|
5864
6234
|
this.lifecycleMenu = null;
|
|
5865
6235
|
this.createMenu = null;
|
|
5866
6236
|
this.pendingConfirm = null;
|
|
5867
6237
|
if (target.kind === "attach") {
|
|
5868
|
-
|
|
6238
|
+
const boxId = this.selectedId;
|
|
6239
|
+
const mode = target.mode ?? "claude";
|
|
6240
|
+
const keepAlive = target.keepAlive ?? false;
|
|
6241
|
+
this.activeMode = mode;
|
|
5869
6242
|
this.session = new PtySession(
|
|
5870
6243
|
this.deps.ptySpawn,
|
|
5871
6244
|
this.deps.termCtor,
|
|
6245
|
+
boxId,
|
|
6246
|
+
keepAlive,
|
|
6247
|
+
mode,
|
|
5872
6248
|
target.command,
|
|
5873
6249
|
target.args,
|
|
5874
6250
|
Math.max(1, this.layout.right.w),
|
|
5875
6251
|
Math.max(1, this.layout.right.h),
|
|
5876
6252
|
() => this.scheduleRender(),
|
|
5877
|
-
() => this.onSessionExit(),
|
|
6253
|
+
(id) => this.onSessionExit(id),
|
|
5878
6254
|
target.cleanup
|
|
5879
6255
|
);
|
|
6256
|
+
if (keepAlive) {
|
|
6257
|
+
const prev = this.liveSessions.get(boxId);
|
|
6258
|
+
if (prev && prev !== this.session) prev.dispose();
|
|
6259
|
+
this.liveSessions.set(boxId, this.session);
|
|
6260
|
+
this.evictLruIfNeeded();
|
|
6261
|
+
}
|
|
5880
6262
|
} else if (target.kind === "menu") {
|
|
5881
6263
|
this.menu = { boxName: this.selectedBox()?.name ?? this.selectedId };
|
|
5882
6264
|
} else if (target.kind === "lifecycle-menu") {
|
|
@@ -5891,7 +6273,7 @@ var Compositor = class {
|
|
|
5891
6273
|
this.placeholder = target.lines;
|
|
5892
6274
|
}
|
|
5893
6275
|
this.prevRows = null;
|
|
5894
|
-
this.drawChrome();
|
|
6276
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
5895
6277
|
this.scheduleRender();
|
|
5896
6278
|
}
|
|
5897
6279
|
handleMenuKey(bytes) {
|
|
@@ -6224,8 +6606,9 @@ var Compositor = class {
|
|
|
6224
6606
|
}, 2500);
|
|
6225
6607
|
this.drawChrome();
|
|
6226
6608
|
}
|
|
6227
|
-
onSessionExit() {
|
|
6228
|
-
this.
|
|
6609
|
+
onSessionExit(boxId) {
|
|
6610
|
+
this.evictSession(boxId);
|
|
6611
|
+
if (boxId !== this.selectedId) return;
|
|
6229
6612
|
this.placeholder = ["", " session ended \u2014 Ctrl-a \u2191/\u2193 to switch boxes"];
|
|
6230
6613
|
this.prevRows = null;
|
|
6231
6614
|
this.scheduleRender();
|
|
@@ -6241,7 +6624,7 @@ var Compositor = class {
|
|
|
6241
6624
|
const next = dir === "prev" ? (i - 1 + n) % n : (i + 1) % n;
|
|
6242
6625
|
this.selectedId = this.boxes[next].id;
|
|
6243
6626
|
this.drawChrome();
|
|
6244
|
-
|
|
6627
|
+
this.showSelected();
|
|
6245
6628
|
}
|
|
6246
6629
|
/** Blank the right pane and drop the diff cache (next paint is full). */
|
|
6247
6630
|
clearRightPane() {
|
|
@@ -6341,8 +6724,36 @@ var Compositor = class {
|
|
|
6341
6724
|
}
|
|
6342
6725
|
for (let y = 0; y < sidebar.h; y++)
|
|
6343
6726
|
s += cursorTo2(sepX, y) + SB_HEADER + (y === 0 ? "\u256E" : "\u2502") + SGR_RESET;
|
|
6344
|
-
let status;
|
|
6345
6727
|
const activePromptForSelected = this.activePrompts.get(this.selectedId);
|
|
6728
|
+
const activeNoticeForSelected = this.activeNotices.get(this.selectedId);
|
|
6729
|
+
if (this.layout.alertH > 0) {
|
|
6730
|
+
const bandRows = this.layout.alertH;
|
|
6731
|
+
let bandLines = null;
|
|
6732
|
+
if (activePromptForSelected) {
|
|
6733
|
+
bandLines = renderAlertBand(
|
|
6734
|
+
{ kind: "prompt", prompt: activePromptForSelected },
|
|
6735
|
+
this.layout.cols,
|
|
6736
|
+
bandRows
|
|
6737
|
+
);
|
|
6738
|
+
} else if (activeNoticeForSelected) {
|
|
6739
|
+
bandLines = renderAlertBand(
|
|
6740
|
+
{ kind: "notice", message: activeNoticeForSelected.message, frame: this.noticeFrame },
|
|
6741
|
+
this.layout.cols,
|
|
6742
|
+
bandRows
|
|
6743
|
+
);
|
|
6744
|
+
} else {
|
|
6745
|
+
const q = this.selectedBox()?.claudeQuestion;
|
|
6746
|
+
if (q) {
|
|
6747
|
+
bandLines = renderAlertBand({ kind: "question", question: q }, this.layout.cols, bandRows);
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
if (bandLines) {
|
|
6751
|
+
for (let i = 0; i < bandLines.length; i++) {
|
|
6752
|
+
s += cursorTo2(0, this.layout.alertY + i) + bandLines[i] + SGR_RESET;
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
let status;
|
|
6346
6757
|
if (this.pendingConfirm) {
|
|
6347
6758
|
const w = this.layout.cols;
|
|
6348
6759
|
const txt = ` Destroy ${this.pendingConfirm.name}? y = confirm \xB7 any other key = cancel `.slice(0, w).padEnd(w);
|
|
@@ -6351,15 +6762,14 @@ var Compositor = class {
|
|
|
6351
6762
|
const w = this.layout.cols;
|
|
6352
6763
|
const txt = ` ${this.flashMsg} `.slice(0, w).padEnd(w);
|
|
6353
6764
|
status = `\x1B[7m${txt}\x1B[0m`;
|
|
6354
|
-
} else if (activePromptForSelected) {
|
|
6765
|
+
} else if (this.layout.alertH === 0 && activePromptForSelected) {
|
|
6355
6766
|
status = renderFooter(
|
|
6356
6767
|
{ kind: "prompt", prompt: activePromptForSelected },
|
|
6357
6768
|
this.layout.cols
|
|
6358
6769
|
);
|
|
6359
|
-
} else if (this.
|
|
6360
|
-
const notice = this.activeNotices.get(this.selectedId);
|
|
6770
|
+
} else if (this.layout.alertH === 0 && activeNoticeForSelected) {
|
|
6361
6771
|
status = renderFooter(
|
|
6362
|
-
{ kind: "notice", message:
|
|
6772
|
+
{ kind: "notice", message: activeNoticeForSelected.message, frame: this.noticeFrame },
|
|
6363
6773
|
this.layout.cols
|
|
6364
6774
|
);
|
|
6365
6775
|
} else {
|
|
@@ -6382,17 +6792,62 @@ var Compositor = class {
|
|
|
6382
6792
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
6383
6793
|
this.resizeTimer = setTimeout(() => {
|
|
6384
6794
|
this.resizeTimer = null;
|
|
6385
|
-
this.
|
|
6386
|
-
this.prevRows = null;
|
|
6387
|
-
const r = this.layout.right;
|
|
6388
|
-
if (this.session && !this.layout.tooSmall) {
|
|
6389
|
-
this.session.resize(Math.max(1, r.w), Math.max(1, r.h));
|
|
6390
|
-
}
|
|
6391
|
-
this.out.write(SYNC_BEGIN + "\x1B[2J" + SYNC_END);
|
|
6392
|
-
this.drawChrome();
|
|
6393
|
-
this.render();
|
|
6795
|
+
this.relayout();
|
|
6394
6796
|
}, RESIZE_DEBOUNCE_MS);
|
|
6395
6797
|
}
|
|
6798
|
+
/**
|
|
6799
|
+
* Requested band height for the currently-selected box. Returns
|
|
6800
|
+
* `ALERT_BAND_ROWS` when the box has an active relay prompt, an active
|
|
6801
|
+
* notice (checkpoint), or claude is in the `question` state with a payload;
|
|
6802
|
+
* 0 otherwise. The layout silently drops the band to 0 if reserving it
|
|
6803
|
+
* would push the right pane below MIN_RIGHT_H.
|
|
6804
|
+
*/
|
|
6805
|
+
alertHeight() {
|
|
6806
|
+
const id = this.selectedId;
|
|
6807
|
+
if (this.activePrompts.has(id)) return ALERT_BAND_ROWS;
|
|
6808
|
+
if (this.activeNotices.has(id)) return ALERT_BAND_ROWS;
|
|
6809
|
+
const box = this.selectedBox();
|
|
6810
|
+
if (box?.claudeQuestion) return ALERT_BAND_ROWS;
|
|
6811
|
+
return 0;
|
|
6812
|
+
}
|
|
6813
|
+
/**
|
|
6814
|
+
* Recompute the layout against the current alert height, resize the inner
|
|
6815
|
+
* session, and repaint. Called from `scheduleResize` (terminal resize) and
|
|
6816
|
+
* from {@link syncAlertLayout} when the selected box's alert state flips.
|
|
6817
|
+
*/
|
|
6818
|
+
relayout() {
|
|
6819
|
+
this.layout = computeLayout(
|
|
6820
|
+
this.out.columns ?? 100,
|
|
6821
|
+
this.out.rows ?? 30,
|
|
6822
|
+
this.alertHeight()
|
|
6823
|
+
);
|
|
6824
|
+
this.prevRows = null;
|
|
6825
|
+
const r = this.layout.right;
|
|
6826
|
+
if (this.session && !this.layout.tooSmall) {
|
|
6827
|
+
this.session.resize(Math.max(1, r.w), Math.max(1, r.h));
|
|
6828
|
+
}
|
|
6829
|
+
this.out.write(SYNC_BEGIN + "\x1B[2J" + SYNC_END);
|
|
6830
|
+
this.drawChrome();
|
|
6831
|
+
this.render();
|
|
6832
|
+
}
|
|
6833
|
+
/**
|
|
6834
|
+
* If the selected box's alert state implies a different band height than
|
|
6835
|
+
* the current layout, run a full {@link relayout}; otherwise return false
|
|
6836
|
+
* so the caller can take the lighter `drawChrome()` path. Used by all
|
|
6837
|
+
* alert-state transitions (SSE handlers, poll, selection change).
|
|
6838
|
+
*/
|
|
6839
|
+
syncAlertLayout() {
|
|
6840
|
+
if (this.alertHeight() !== this.layout.alertH) {
|
|
6841
|
+
this.relayout();
|
|
6842
|
+
return true;
|
|
6843
|
+
}
|
|
6844
|
+
return false;
|
|
6845
|
+
}
|
|
6846
|
+
/** Common path for alert-state transitions: relayout when the band's
|
|
6847
|
+
* visibility changes, drawChrome only when it doesn't. */
|
|
6848
|
+
redrawForAlert() {
|
|
6849
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
6850
|
+
}
|
|
6396
6851
|
teardown() {
|
|
6397
6852
|
if (this.tornDown) return;
|
|
6398
6853
|
this.tornDown = true;
|
|
@@ -6407,7 +6862,7 @@ var Compositor = class {
|
|
|
6407
6862
|
this.activePrompts.clear();
|
|
6408
6863
|
this.activeNotices.clear();
|
|
6409
6864
|
this.parser.dispose();
|
|
6410
|
-
this.
|
|
6865
|
+
this.disposeAllSessions();
|
|
6411
6866
|
this.inp.off("data", this.onData);
|
|
6412
6867
|
this.out.off("resize", this.onResize);
|
|
6413
6868
|
if (this.inp.isTTY) this.inp.setRawMode(false);
|
|
@@ -6419,6 +6874,9 @@ var Compositor = class {
|
|
|
6419
6874
|
};
|
|
6420
6875
|
|
|
6421
6876
|
// src/commands/dashboard.ts
|
|
6877
|
+
function providerSupportsKeepAlive(provider) {
|
|
6878
|
+
return provider === "vercel";
|
|
6879
|
+
}
|
|
6422
6880
|
function sortBoxes(boxes) {
|
|
6423
6881
|
return [...boxes].sort((a, b) => {
|
|
6424
6882
|
const ap = a.projectRoot ?? "";
|
|
@@ -6455,7 +6913,10 @@ function toSidebar(b) {
|
|
|
6455
6913
|
activity: agent.activity,
|
|
6456
6914
|
sessionTitle: agent.sessionTitle,
|
|
6457
6915
|
index: b.projectIndex,
|
|
6458
|
-
project: b.projectRoot
|
|
6916
|
+
project: b.projectRoot,
|
|
6917
|
+
// Only claude reports an AskUserQuestion payload; gated on claudeActivity
|
|
6918
|
+
// === 'question' upstream (lifecycle.listBoxes) so it's undefined otherwise.
|
|
6919
|
+
claudeQuestion: b.claudeQuestion
|
|
6459
6920
|
};
|
|
6460
6921
|
}
|
|
6461
6922
|
var dashboardCommand = new Command10("dashboard").description("Box list + the selected box live Agent session").argument("[box]", "initial box (default: first running box; -p restricts to the cwd project)").option("-p, --project", "only this project's boxes (default: all boxes globally)").action(async (idOrName, opts) => {
|
|
@@ -6578,7 +7039,8 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6578
7039
|
command: spec.argv[0],
|
|
6579
7040
|
args: spec.argv.slice(1),
|
|
6580
7041
|
...spec.cleanup ? { cleanup: spec.cleanup } : {},
|
|
6581
|
-
mode: which
|
|
7042
|
+
mode: which,
|
|
7043
|
+
...providerSupportsKeepAlive(record.provider) ? { keepAlive: true } : {}
|
|
6582
7044
|
};
|
|
6583
7045
|
};
|
|
6584
7046
|
const isCloudBox = (box) => (box.provider ?? "docker") !== "docker";
|
|
@@ -6612,7 +7074,12 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6612
7074
|
{ volume: claudeVolume },
|
|
6613
7075
|
{ image: box.image, isolate: claudeVolume !== SHARED_CLAUDE_VOLUME }
|
|
6614
7076
|
);
|
|
6615
|
-
|
|
7077
|
+
const claudeCfg = await loadEffectiveConfig(box.workspacePath);
|
|
7078
|
+
await startClaudeSession({
|
|
7079
|
+
container: box.container,
|
|
7080
|
+
claudeArgs: applyClaudeSkipPermissions([], claudeCfg.effective),
|
|
7081
|
+
boxName: box.name
|
|
7082
|
+
});
|
|
6616
7083
|
const info = await claudeSessionInfo(box.container);
|
|
6617
7084
|
await waitForTmuxPaneContent(box.container, info.sessionName);
|
|
6618
7085
|
return {
|
|
@@ -6629,7 +7096,11 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6629
7096
|
}
|
|
6630
7097
|
await ensureCodexInstalled(box.container);
|
|
6631
7098
|
if (box.codexConfigVolume) await seedCodexHooks(box.codexConfigVolume, box.image);
|
|
6632
|
-
await
|
|
7099
|
+
const codexCfg = await loadEffectiveConfig(box.workspacePath);
|
|
7100
|
+
await startCodexSession({
|
|
7101
|
+
container: box.container,
|
|
7102
|
+
codexArgs: applyCodexSkipPermissions([], codexCfg.effective)
|
|
7103
|
+
});
|
|
6633
7104
|
await waitForTmuxPaneContent(box.container, DEFAULT_CODEX_SESSION);
|
|
6634
7105
|
return {
|
|
6635
7106
|
kind: "attach",
|
|
@@ -6698,7 +7169,10 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6698
7169
|
if (!agent) return { boxId: result.record.id };
|
|
6699
7170
|
if (agent === "codex") {
|
|
6700
7171
|
await ensureCodexInstalled(ctr, { onProgress });
|
|
6701
|
-
await startCodexSession({
|
|
7172
|
+
await startCodexSession({
|
|
7173
|
+
container: ctr,
|
|
7174
|
+
codexArgs: applyCodexSkipPermissions([], cfg.effective)
|
|
7175
|
+
});
|
|
6702
7176
|
await waitForTmuxPaneContent(ctr, DEFAULT_CODEX_SESSION);
|
|
6703
7177
|
return {
|
|
6704
7178
|
boxId: result.record.id,
|
|
@@ -6725,7 +7199,11 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6725
7199
|
};
|
|
6726
7200
|
}
|
|
6727
7201
|
await rebuildPluginNativeDeps(ctr, { volume: result.record.claudeConfigVolume });
|
|
6728
|
-
await startClaudeSession({
|
|
7202
|
+
await startClaudeSession({
|
|
7203
|
+
container: ctr,
|
|
7204
|
+
claudeArgs: applyClaudeSkipPermissions([], cfg.effective),
|
|
7205
|
+
boxName: result.record.name
|
|
7206
|
+
});
|
|
6729
7207
|
const info = await claudeSessionInfo(ctr);
|
|
6730
7208
|
await waitForTmuxPaneContent(ctr, info.sessionName);
|
|
6731
7209
|
return {
|
|
@@ -7801,11 +8279,11 @@ function resolveToken(raw) {
|
|
|
7801
8279
|
// src/lib/drive/tmux.ts
|
|
7802
8280
|
var TMUX_USER = "vscode";
|
|
7803
8281
|
async function captureSession(provider, box, session, opts = {}) {
|
|
7804
|
-
const
|
|
8282
|
+
const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
|
|
7805
8283
|
if (opts.rows) {
|
|
7806
|
-
|
|
8284
|
+
argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
|
|
7807
8285
|
}
|
|
7808
|
-
const res = await provider.exec(box,
|
|
8286
|
+
const res = await provider.exec(box, argv2, { user: TMUX_USER });
|
|
7809
8287
|
if (res.exitCode !== 0) {
|
|
7810
8288
|
throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
|
|
7811
8289
|
}
|
|
@@ -8062,8 +8540,8 @@ function sleep2(ms) {
|
|
|
8062
8540
|
import { log as log28 } from "@clack/prompts";
|
|
8063
8541
|
import { Command as Command23 } from "commander";
|
|
8064
8542
|
import { existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
8065
|
-
import { homedir as
|
|
8066
|
-
import { join as
|
|
8543
|
+
import { homedir as homedir10 } from "os";
|
|
8544
|
+
import { join as join12 } from "path";
|
|
8067
8545
|
var FORK_AGENTS = ["claude", "codex", "opencode"];
|
|
8068
8546
|
var AGENT_COMMAND = {
|
|
8069
8547
|
claude: claudeCommand,
|
|
@@ -8083,12 +8561,12 @@ function resolveSessionArgs(agent, opts) {
|
|
|
8083
8561
|
}
|
|
8084
8562
|
if (opts.session) return ["--resume", opts.session];
|
|
8085
8563
|
if (agent === "codex") return ["--continue"];
|
|
8086
|
-
const dir =
|
|
8564
|
+
const dir = join12(homedir10(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
|
|
8087
8565
|
if (!existsSync4(dir)) return ["--continue"];
|
|
8088
8566
|
const now = Date.now();
|
|
8089
8567
|
const recent = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
8090
8568
|
try {
|
|
8091
|
-
return statSync(
|
|
8569
|
+
return statSync(join12(dir, f)).mtimeMs;
|
|
8092
8570
|
} catch {
|
|
8093
8571
|
return 0;
|
|
8094
8572
|
}
|
|
@@ -8166,115 +8644,1010 @@ var forkCommand = new Command23("fork").description(
|
|
|
8166
8644
|
});
|
|
8167
8645
|
|
|
8168
8646
|
// src/commands/install.ts
|
|
8169
|
-
import { intro as
|
|
8170
|
-
import { Command as
|
|
8171
|
-
import { existsSync as
|
|
8172
|
-
import { homedir as
|
|
8173
|
-
import { dirname, join as
|
|
8647
|
+
import { confirm as confirm14, intro as intro6, isCancel as isCancel15, log as log30, note, outro as outro6, select as select2, spinner as spinner8 } from "@clack/prompts";
|
|
8648
|
+
import { Command as Command25 } from "commander";
|
|
8649
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync, writeFileSync as writeFileSync4 } from "fs";
|
|
8650
|
+
import { homedir as homedir13 } from "os";
|
|
8651
|
+
import { dirname as dirname2, join as join15, resolve as resolve2 } from "path";
|
|
8174
8652
|
import { fileURLToPath } from "url";
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
{
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
}
|
|
8193
|
-
];
|
|
8194
|
-
}
|
|
8195
|
-
function resolveHostSkillsDir() {
|
|
8196
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
8197
|
-
const candidates = [
|
|
8198
|
-
resolve2(here, "..", "share", "host-skills"),
|
|
8199
|
-
// bundled: dist/ -> ../share
|
|
8200
|
-
resolve2(here, "..", "..", "share", "host-skills")
|
|
8201
|
-
// src: src/commands/ -> ../../share
|
|
8202
|
-
];
|
|
8203
|
-
for (const c of candidates) {
|
|
8204
|
-
if (existsSync5(c)) return c;
|
|
8653
|
+
|
|
8654
|
+
// src/lib/doctor-checks.ts
|
|
8655
|
+
import { accessSync, constants as fsConstants, mkdirSync as mkdirSync3 } from "fs";
|
|
8656
|
+
import { homedir as homedir11 } from "os";
|
|
8657
|
+
import { join as join13 } from "path";
|
|
8658
|
+
import { execa as execa2 } from "execa";
|
|
8659
|
+
var ALL_PROVIDERS = ["docker", "daytona", "hetzner", "vercel"];
|
|
8660
|
+
var NODE_MIN_MAJOR = 20;
|
|
8661
|
+
var NODE_MIN_MINOR = 10;
|
|
8662
|
+
async function probeVersion(bin, args = ["--version"]) {
|
|
8663
|
+
try {
|
|
8664
|
+
const r = await execa2(bin, args, { reject: false });
|
|
8665
|
+
if (r.exitCode !== 0) return null;
|
|
8666
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`.trim().split("\n")[0] ?? "";
|
|
8667
|
+
return out.length > 0 ? out : bin;
|
|
8668
|
+
} catch {
|
|
8669
|
+
return null;
|
|
8205
8670
|
}
|
|
8206
|
-
throw new Error(
|
|
8207
|
-
`could not locate bundled host skills; tried:
|
|
8208
|
-
${candidates.join("\n ")}`
|
|
8209
|
-
);
|
|
8210
8671
|
}
|
|
8211
|
-
function
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
return "managed";
|
|
8216
|
-
}
|
|
8217
|
-
return force ? "forced" : "skip";
|
|
8672
|
+
function parseNodeMajorMinor(v) {
|
|
8673
|
+
const m = /^v?(\d+)\.(\d+)/.exec(v);
|
|
8674
|
+
if (!m) return [0, 0];
|
|
8675
|
+
return [Number(m[1]), Number(m[2])];
|
|
8218
8676
|
}
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8677
|
+
function firstLine(s) {
|
|
8678
|
+
const i = s.indexOf("\n");
|
|
8679
|
+
return i === -1 ? s : s.slice(0, i);
|
|
8680
|
+
}
|
|
8681
|
+
function errSummary(err) {
|
|
8682
|
+
return err instanceof Error ? firstLine(err.message) : String(err);
|
|
8683
|
+
}
|
|
8684
|
+
function checkNode() {
|
|
8685
|
+
const v = process.versions.node;
|
|
8686
|
+
const [maj, min] = parseNodeMajorMinor(v);
|
|
8687
|
+
const ok = maj > NODE_MIN_MAJOR || maj === NODE_MIN_MAJOR && min >= NODE_MIN_MINOR;
|
|
8688
|
+
return {
|
|
8689
|
+
label: "node",
|
|
8690
|
+
status: ok ? "ok" : "fail",
|
|
8691
|
+
detail: ok ? `v${v}` : `v${v} (need >=${String(NODE_MIN_MAJOR)}.${String(NODE_MIN_MINOR)})`,
|
|
8692
|
+
hint: ok ? void 0 : "upgrade Node before continuing"
|
|
8693
|
+
};
|
|
8694
|
+
}
|
|
8695
|
+
function checkPlatform() {
|
|
8696
|
+
return { label: "platform", status: "ok", detail: `${process.platform}/${process.arch}` };
|
|
8697
|
+
}
|
|
8698
|
+
function checkAgentboxHome() {
|
|
8699
|
+
const dir = join13(homedir11(), ".agentbox");
|
|
8226
8700
|
try {
|
|
8227
|
-
|
|
8701
|
+
mkdirSync3(dir, { recursive: true });
|
|
8702
|
+
accessSync(dir, fsConstants.W_OK);
|
|
8703
|
+
return { label: "~/.agentbox", status: "ok", detail: dir };
|
|
8228
8704
|
} catch (err) {
|
|
8229
|
-
|
|
8230
|
-
|
|
8705
|
+
return {
|
|
8706
|
+
label: "~/.agentbox",
|
|
8707
|
+
status: "fail",
|
|
8708
|
+
detail: `not writable: ${err instanceof Error ? err.message : String(err)}`,
|
|
8709
|
+
hint: "check directory permissions"
|
|
8710
|
+
};
|
|
8231
8711
|
}
|
|
8232
|
-
|
|
8233
|
-
|
|
8712
|
+
}
|
|
8713
|
+
async function checkGit() {
|
|
8714
|
+
const v = await probeVersion("git");
|
|
8715
|
+
return v ? { label: "git", status: "ok", detail: v } : {
|
|
8716
|
+
label: "git",
|
|
8717
|
+
status: "warn",
|
|
8718
|
+
detail: "not found",
|
|
8719
|
+
hint: "install git \u2014 required for the workspace git-bundle seed"
|
|
8720
|
+
};
|
|
8721
|
+
}
|
|
8722
|
+
async function checkSsh() {
|
|
8723
|
+
const v = await probeVersion("ssh", ["-V"]);
|
|
8724
|
+
return v ? { label: "ssh", status: "ok", detail: v } : {
|
|
8725
|
+
label: "ssh",
|
|
8726
|
+
status: "warn",
|
|
8727
|
+
detail: "not found",
|
|
8728
|
+
hint: "install ssh \u2014 required for hetzner and cloud attach"
|
|
8729
|
+
};
|
|
8730
|
+
}
|
|
8731
|
+
async function runSystemChecks() {
|
|
8732
|
+
const [git, ssh] = await Promise.all([checkGit(), checkSsh()]);
|
|
8733
|
+
return [checkNode(), checkPlatform(), checkAgentboxHome(), git, ssh];
|
|
8734
|
+
}
|
|
8735
|
+
async function dockerChecks() {
|
|
8736
|
+
const cli = await probeVersion("docker");
|
|
8737
|
+
if (!cli) {
|
|
8738
|
+
return [
|
|
8739
|
+
{
|
|
8740
|
+
label: "docker cli",
|
|
8741
|
+
status: "warn",
|
|
8742
|
+
detail: "not found",
|
|
8743
|
+
hint: "install Docker Desktop, OrbStack, or docker engine"
|
|
8744
|
+
}
|
|
8745
|
+
];
|
|
8746
|
+
}
|
|
8747
|
+
const cliRes = { label: "docker cli", status: "ok", detail: cli };
|
|
8748
|
+
const info = await execa2("docker", ["info"], { reject: false });
|
|
8749
|
+
if (info.exitCode !== 0) {
|
|
8750
|
+
return [
|
|
8751
|
+
cliRes,
|
|
8752
|
+
{
|
|
8753
|
+
label: "docker daemon",
|
|
8754
|
+
status: "warn",
|
|
8755
|
+
detail: "unreachable",
|
|
8756
|
+
hint: "start Docker (Desktop / OrbStack / `systemctl start docker`)"
|
|
8757
|
+
}
|
|
8758
|
+
];
|
|
8759
|
+
}
|
|
8760
|
+
const daemonRes = { label: "docker daemon", status: "ok", detail: "reachable" };
|
|
8761
|
+
const mod = await import("./dist-SBCQVFCE.js");
|
|
8762
|
+
let imgRes;
|
|
8763
|
+
try {
|
|
8764
|
+
const img = await mod.imageInfo(mod.DEFAULT_BOX_IMAGE);
|
|
8765
|
+
imgRes = img.exists ? { label: "box image", status: "ok", detail: `${mod.DEFAULT_BOX_IMAGE} built` } : {
|
|
8766
|
+
label: "box image",
|
|
8767
|
+
status: "warn",
|
|
8768
|
+
detail: `${mod.DEFAULT_BOX_IMAGE} not built`,
|
|
8769
|
+
hint: "run `agentbox prepare --provider docker` (or let the wizard do it)"
|
|
8770
|
+
};
|
|
8771
|
+
} catch (err) {
|
|
8772
|
+
imgRes = {
|
|
8773
|
+
label: "box image",
|
|
8774
|
+
status: "warn",
|
|
8775
|
+
detail: errSummary(err)
|
|
8776
|
+
};
|
|
8777
|
+
}
|
|
8778
|
+
const volNames = [mod.SHARED_CLAUDE_VOLUME, mod.SHARED_CODEX_VOLUME, mod.SHARED_OPENCODE_VOLUME];
|
|
8779
|
+
const vols = await Promise.all(
|
|
8780
|
+
volNames.map(async (n) => ({ name: n, exists: await mod.volumeExists(n).catch(() => false) }))
|
|
8781
|
+
);
|
|
8782
|
+
const present = vols.filter((v) => v.exists).length;
|
|
8783
|
+
const volRes = {
|
|
8784
|
+
label: "shared volumes",
|
|
8785
|
+
status: "ok",
|
|
8786
|
+
detail: `${String(present)}/${String(vols.length)} present (seeded lazily)`
|
|
8787
|
+
};
|
|
8788
|
+
return [cliRes, daemonRes, imgRes, volRes];
|
|
8789
|
+
}
|
|
8790
|
+
async function daytonaChecks() {
|
|
8791
|
+
try {
|
|
8792
|
+
const mod = await import("./dist-BD5QJRDC.js");
|
|
8793
|
+
const status = await mod.getDaytonaStatus();
|
|
8794
|
+
if (!status.configured) {
|
|
8795
|
+
return [
|
|
8796
|
+
{
|
|
8797
|
+
label: "credentials",
|
|
8798
|
+
status: "warn",
|
|
8799
|
+
detail: status.reason ?? "not configured",
|
|
8800
|
+
hint: "`agentbox daytona login`"
|
|
8801
|
+
}
|
|
8802
|
+
];
|
|
8803
|
+
}
|
|
8804
|
+
const credRes = { label: "credentials", status: "ok", detail: "configured" };
|
|
8805
|
+
const snapRes = status.snapshots.length > 0 ? {
|
|
8806
|
+
label: "base snapshot",
|
|
8807
|
+
status: "ok",
|
|
8808
|
+
detail: `${String(status.snapshots.length)} agentbox snapshot(s)`
|
|
8809
|
+
} : {
|
|
8810
|
+
label: "base snapshot",
|
|
8811
|
+
status: "warn",
|
|
8812
|
+
detail: "none",
|
|
8813
|
+
hint: "`agentbox prepare --provider daytona`"
|
|
8814
|
+
};
|
|
8815
|
+
return [credRes, snapRes];
|
|
8816
|
+
} catch (err) {
|
|
8817
|
+
return [
|
|
8818
|
+
{
|
|
8819
|
+
label: "credentials",
|
|
8820
|
+
status: "warn",
|
|
8821
|
+
detail: errSummary(err)
|
|
8822
|
+
}
|
|
8823
|
+
];
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8826
|
+
async function hetznerChecks() {
|
|
8827
|
+
try {
|
|
8828
|
+
const mod = await import("./dist-BNI5PQYK.js");
|
|
8829
|
+
const cred = mod.readHetznerCredStatus();
|
|
8830
|
+
const credRes = cred.source === "none" ? {
|
|
8831
|
+
label: "credentials",
|
|
8832
|
+
status: "warn",
|
|
8833
|
+
detail: "HCLOUD_TOKEN not set",
|
|
8834
|
+
hint: "`agentbox hetzner login`"
|
|
8835
|
+
} : { label: "credentials", status: "ok", detail: `token from ${cred.source}` };
|
|
8836
|
+
const prepared = mod.readPreparedState();
|
|
8837
|
+
const snapRes = prepared.base?.imageId ? {
|
|
8838
|
+
label: "base snapshot",
|
|
8839
|
+
status: "ok",
|
|
8840
|
+
detail: `image ${String(prepared.base.imageId)} (${prepared.base.cliVersion ?? "\u2014"})`
|
|
8841
|
+
} : {
|
|
8842
|
+
label: "base snapshot",
|
|
8843
|
+
status: "warn",
|
|
8844
|
+
detail: "not baked",
|
|
8845
|
+
hint: "`agentbox prepare --provider hetzner`"
|
|
8846
|
+
};
|
|
8847
|
+
return [credRes, snapRes];
|
|
8848
|
+
} catch (err) {
|
|
8849
|
+
return [
|
|
8850
|
+
{
|
|
8851
|
+
label: "credentials",
|
|
8852
|
+
status: "warn",
|
|
8853
|
+
detail: errSummary(err)
|
|
8854
|
+
}
|
|
8855
|
+
];
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
async function vercelChecks() {
|
|
8859
|
+
try {
|
|
8860
|
+
const mod = await import("./dist-SJHY3HYN.js");
|
|
8861
|
+
const cred = mod.readVercelCredStatus();
|
|
8862
|
+
const credRes = cred.auth === "none" ? {
|
|
8863
|
+
label: "credentials",
|
|
8864
|
+
status: "warn",
|
|
8865
|
+
detail: "not configured",
|
|
8866
|
+
hint: "`agentbox vercel login`"
|
|
8867
|
+
} : {
|
|
8868
|
+
label: "credentials",
|
|
8869
|
+
status: "ok",
|
|
8870
|
+
detail: `${cred.auth} (${cred.source})`
|
|
8871
|
+
};
|
|
8872
|
+
const prepared = mod.readPreparedState();
|
|
8873
|
+
const snapRes = prepared.base?.snapshotId ? {
|
|
8874
|
+
label: "base snapshot",
|
|
8875
|
+
status: "ok",
|
|
8876
|
+
detail: `${prepared.base.snapshotId.slice(0, 16)}\u2026 (${prepared.base.cliVersion ?? "\u2014"})`
|
|
8877
|
+
} : {
|
|
8878
|
+
label: "base snapshot",
|
|
8879
|
+
status: "warn",
|
|
8880
|
+
detail: "not baked",
|
|
8881
|
+
hint: "`agentbox prepare --provider vercel`"
|
|
8882
|
+
};
|
|
8883
|
+
return [credRes, snapRes];
|
|
8884
|
+
} catch (err) {
|
|
8885
|
+
return [
|
|
8886
|
+
{
|
|
8887
|
+
label: "credentials",
|
|
8888
|
+
status: "warn",
|
|
8889
|
+
detail: errSummary(err)
|
|
8890
|
+
}
|
|
8891
|
+
];
|
|
8892
|
+
}
|
|
8893
|
+
}
|
|
8894
|
+
async function runProviderChecks(name) {
|
|
8895
|
+
let results;
|
|
8896
|
+
switch (name) {
|
|
8897
|
+
case "docker":
|
|
8898
|
+
results = await dockerChecks();
|
|
8899
|
+
break;
|
|
8900
|
+
case "daytona":
|
|
8901
|
+
results = await daytonaChecks();
|
|
8902
|
+
break;
|
|
8903
|
+
case "hetzner":
|
|
8904
|
+
results = await hetznerChecks();
|
|
8905
|
+
break;
|
|
8906
|
+
case "vercel":
|
|
8907
|
+
results = await vercelChecks();
|
|
8908
|
+
break;
|
|
8909
|
+
}
|
|
8910
|
+
return { title: name, results };
|
|
8911
|
+
}
|
|
8912
|
+
async function runAllChecks() {
|
|
8913
|
+
const sys = { title: "system", results: await runSystemChecks() };
|
|
8914
|
+
const providerGroups = await Promise.all(ALL_PROVIDERS.map((n) => runProviderChecks(n)));
|
|
8915
|
+
return [sys, ...providerGroups];
|
|
8916
|
+
}
|
|
8917
|
+
function worstInResults(results) {
|
|
8918
|
+
let worst = "ok";
|
|
8919
|
+
for (const r of results) {
|
|
8920
|
+
if (r.status === "fail") return "fail";
|
|
8921
|
+
if (r.status === "warn") worst = "warn";
|
|
8922
|
+
}
|
|
8923
|
+
return worst;
|
|
8924
|
+
}
|
|
8925
|
+
function worstStatus(groups) {
|
|
8926
|
+
let worst = "ok";
|
|
8927
|
+
for (const g of groups) {
|
|
8928
|
+
const w = worstInResults(g.results);
|
|
8929
|
+
if (w === "fail") return "fail";
|
|
8930
|
+
if (w === "warn") worst = "warn";
|
|
8931
|
+
}
|
|
8932
|
+
return worst;
|
|
8933
|
+
}
|
|
8934
|
+
function summaryToken(group) {
|
|
8935
|
+
const worst = worstInResults(group.results);
|
|
8936
|
+
if (group.title === "system") {
|
|
8937
|
+
if (worst === "fail") return "system FAIL";
|
|
8938
|
+
if (worst === "warn") return "system warn";
|
|
8939
|
+
return "system ok";
|
|
8940
|
+
}
|
|
8941
|
+
if (worst === "fail") return `${group.title} FAIL`;
|
|
8942
|
+
if (worst === "warn") {
|
|
8943
|
+
const cred = group.results.find((r) => r.label === "credentials");
|
|
8944
|
+
if (cred && cred.status === "warn") return `${group.title} login needed`;
|
|
8945
|
+
return `${group.title} not prepared`;
|
|
8946
|
+
}
|
|
8947
|
+
return `${group.title} ready`;
|
|
8948
|
+
}
|
|
8949
|
+
var C_GREEN = "\x1B[32m";
|
|
8950
|
+
var C_YELLOW = "\x1B[33m";
|
|
8951
|
+
var C_RED = "\x1B[31m";
|
|
8952
|
+
var C_RESET = "\x1B[0m";
|
|
8953
|
+
var COLOR = !process.env.NO_COLOR;
|
|
8954
|
+
function statusMarker(s) {
|
|
8955
|
+
const glyph = s === "ok" ? "\u2713" : s === "warn" ? "\u26A0" : "\u2717";
|
|
8956
|
+
if (!COLOR) return glyph;
|
|
8957
|
+
const color = s === "ok" ? C_GREEN : s === "warn" ? C_YELLOW : C_RED;
|
|
8958
|
+
return `${color}${glyph}${C_RESET}`;
|
|
8959
|
+
}
|
|
8960
|
+
function formatCompact(groups) {
|
|
8961
|
+
return groups.map((g) => `${statusMarker(worstInResults(g.results))} ${summaryToken(g)}`).join(" \xB7 ");
|
|
8962
|
+
}
|
|
8963
|
+
function pad2(s, width) {
|
|
8964
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
8965
|
+
}
|
|
8966
|
+
function statusBadge(s) {
|
|
8967
|
+
if (s === "ok") return "[ ok ]";
|
|
8968
|
+
if (s === "warn") return "[warn]";
|
|
8969
|
+
return "[FAIL]";
|
|
8970
|
+
}
|
|
8971
|
+
function formatDetailed(groups) {
|
|
8972
|
+
const lines = [];
|
|
8973
|
+
for (const g of groups) {
|
|
8974
|
+
if (lines.length > 0) lines.push("");
|
|
8975
|
+
lines.push(`${g.title}:`);
|
|
8976
|
+
for (const r of g.results) {
|
|
8977
|
+
const badge = statusBadge(r.status);
|
|
8978
|
+
const tail = r.hint ? ` (${r.hint})` : "";
|
|
8979
|
+
lines.push(` ${badge} ${pad2(r.label, 18)} ${r.detail}${tail}`);
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
return lines;
|
|
8983
|
+
}
|
|
8984
|
+
|
|
8985
|
+
// src/lib/first-run.ts
|
|
8986
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
8987
|
+
import { homedir as homedir12 } from "os";
|
|
8988
|
+
import { dirname, join as join14 } from "path";
|
|
8989
|
+
var MARKER_VERSION = 1;
|
|
8990
|
+
function setupMarkerPath() {
|
|
8991
|
+
return join14(homedir12(), ".agentbox", "setup-complete.json");
|
|
8992
|
+
}
|
|
8993
|
+
function isFirstRun() {
|
|
8994
|
+
return !existsSync5(setupMarkerPath());
|
|
8995
|
+
}
|
|
8996
|
+
function markSetupComplete(provider) {
|
|
8997
|
+
const path = setupMarkerPath();
|
|
8998
|
+
mkdirSync4(dirname(path), { recursive: true });
|
|
8999
|
+
const body = {
|
|
9000
|
+
version: MARKER_VERSION,
|
|
9001
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9002
|
+
provider
|
|
9003
|
+
};
|
|
9004
|
+
writeFileSync3(path, JSON.stringify(body, null, 2) + "\n");
|
|
9005
|
+
}
|
|
9006
|
+
|
|
9007
|
+
// src/commands/prepare.ts
|
|
9008
|
+
import { intro as intro5, log as log29, spinner as spinner7 } from "@clack/prompts";
|
|
9009
|
+
import { Command as Command24 } from "commander";
|
|
9010
|
+
async function dockerStatus() {
|
|
9011
|
+
let img;
|
|
9012
|
+
try {
|
|
9013
|
+
img = await imageInfo(DEFAULT_BOX_IMAGE);
|
|
9014
|
+
} catch {
|
|
9015
|
+
return { daemon: "unreachable", volumes: [] };
|
|
9016
|
+
}
|
|
9017
|
+
const names = [SHARED_CLAUDE_VOLUME, SHARED_CODEX_VOLUME, SHARED_OPENCODE_VOLUME];
|
|
9018
|
+
const volumes = await Promise.all(
|
|
9019
|
+
names.map(async (name) => ({ name, exists: await volumeExists(name).catch(() => false) }))
|
|
9020
|
+
);
|
|
9021
|
+
return { daemon: "reachable", image: img, volumes };
|
|
9022
|
+
}
|
|
9023
|
+
function humanBytes(n) {
|
|
9024
|
+
if (n === void 0 || !Number.isFinite(n)) return "\u2014";
|
|
9025
|
+
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
9026
|
+
if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
9027
|
+
if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
9028
|
+
return `${String(n)} B`;
|
|
9029
|
+
}
|
|
9030
|
+
function humanAge(iso) {
|
|
9031
|
+
if (!iso) return "\u2014";
|
|
9032
|
+
const t = Date.parse(iso);
|
|
9033
|
+
if (!Number.isFinite(t)) return iso;
|
|
9034
|
+
const ageSec = Math.max(0, (Date.now() - t) / 1e3);
|
|
9035
|
+
if (ageSec < 60) return `${ageSec.toFixed(0)}s ago`;
|
|
9036
|
+
if (ageSec < 3600) return `${(ageSec / 60).toFixed(0)}m ago`;
|
|
9037
|
+
if (ageSec < 86400) return `${(ageSec / 3600).toFixed(1)}h ago`;
|
|
9038
|
+
return `${(ageSec / 86400).toFixed(1)}d ago`;
|
|
9039
|
+
}
|
|
9040
|
+
function pad3(s, width) {
|
|
9041
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
9042
|
+
}
|
|
9043
|
+
async function renderDocker(status) {
|
|
9044
|
+
const out = ["docker:"];
|
|
9045
|
+
if (status.daemon === "unreachable") {
|
|
9046
|
+
out.push(" docker daemon unreachable (is Docker running?)");
|
|
9047
|
+
return out;
|
|
9048
|
+
}
|
|
9049
|
+
if (!status.image?.exists) {
|
|
9050
|
+
out.push(
|
|
9051
|
+
` image ${DEFAULT_BOX_IMAGE} (not built \u2014 run \`agentbox prepare --provider docker\`)`
|
|
9052
|
+
);
|
|
9053
|
+
} else {
|
|
9054
|
+
out.push(
|
|
9055
|
+
` image ${pad3(DEFAULT_BOX_IMAGE, 30)} ${pad3(humanBytes(status.image.sizeBytes), 10)} built ${humanAge(status.image.createdAt)}`
|
|
9056
|
+
);
|
|
9057
|
+
}
|
|
9058
|
+
for (const v of status.volumes) {
|
|
9059
|
+
if (v.exists) {
|
|
9060
|
+
out.push(` vol ${pad3(v.name, 30)} present`);
|
|
9061
|
+
} else {
|
|
9062
|
+
out.push(
|
|
9063
|
+
` vol ${pad3(v.name, 30)} (none \u2014 seeded lazily on first \`agentbox claude/codex/opencode\`)`
|
|
9064
|
+
);
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
return out;
|
|
9068
|
+
}
|
|
9069
|
+
async function daytonaStatus() {
|
|
9070
|
+
try {
|
|
9071
|
+
const mod = await import("./dist-BD5QJRDC.js");
|
|
9072
|
+
return await mod.getDaytonaStatus();
|
|
9073
|
+
} catch (err) {
|
|
9074
|
+
return {
|
|
9075
|
+
configured: false,
|
|
9076
|
+
reason: err instanceof Error ? err.message.split("\n")[0] : String(err)
|
|
9077
|
+
};
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
function renderDaytona(status, pinnedImage) {
|
|
9081
|
+
const out = ["daytona:"];
|
|
9082
|
+
if (!status.configured) {
|
|
9083
|
+
out.push(
|
|
9084
|
+
` (not configured \u2014 \`agentbox daytona login\` to set up${status.reason ? `; ${status.reason}` : ""})`
|
|
9085
|
+
);
|
|
9086
|
+
return out;
|
|
9087
|
+
}
|
|
9088
|
+
if (status.reason) out.push(` warn: ${status.reason}`);
|
|
9089
|
+
if (status.snapshots.length === 0) {
|
|
9090
|
+
out.push(" no agentbox snapshots \u2014 run `agentbox prepare --provider daytona`");
|
|
9091
|
+
} else {
|
|
9092
|
+
for (const s of status.snapshots) {
|
|
9093
|
+
const sizeStr = s.sizeGb !== void 0 ? `${s.sizeGb.toFixed(2)} GB` : "\u2014";
|
|
9094
|
+
const pinned = pinnedImage && pinnedImage === s.name ? " (pinned in project)" : "";
|
|
9095
|
+
const tail = s.state === "error" && s.errorReason ? ` error: ${s.errorReason.slice(0, 80)}` : ` ${humanAge(s.createdAt)}`;
|
|
9096
|
+
out.push(
|
|
9097
|
+
` snap ${pad3(s.name, 40)} ${pad3(s.state ?? "\u2014", 10)} ${pad3(sizeStr, 10)}${tail}${pinned}`
|
|
9098
|
+
);
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
if (status.volumes.length === 0) {
|
|
9102
|
+
out.push(" no agentbox volumes \u2014 created lazily on first cloud `agentbox create`");
|
|
9103
|
+
} else {
|
|
9104
|
+
for (const v of status.volumes) {
|
|
9105
|
+
const last = v.lastUsedAt ? ` last used ${humanAge(v.lastUsedAt)}` : "";
|
|
9106
|
+
out.push(` vol ${pad3(v.name, 40)} ${pad3(v.state ?? "\u2014", 10)}${last}`);
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
return out;
|
|
9110
|
+
}
|
|
9111
|
+
async function showStatus(opts) {
|
|
9112
|
+
const cfg = await loadEffectiveConfig(process.cwd()).catch(() => null);
|
|
9113
|
+
const pinnedRaw = cfg?.effective.box.image;
|
|
9114
|
+
const pinned = typeof pinnedRaw === "string" && pinnedRaw.length > 0 && pinnedRaw !== DEFAULT_BOX_IMAGE ? pinnedRaw : void 0;
|
|
9115
|
+
const lines = [];
|
|
9116
|
+
const wantDocker = !opts.onlyProvider || opts.onlyProvider === "docker";
|
|
9117
|
+
const wantDaytona = !opts.onlyProvider || opts.onlyProvider === "daytona";
|
|
9118
|
+
if (wantDocker) {
|
|
9119
|
+
const status = await dockerStatus();
|
|
9120
|
+
lines.push(...await renderDocker(status));
|
|
9121
|
+
}
|
|
9122
|
+
if (wantDaytona) {
|
|
9123
|
+
if (lines.length > 0) lines.push("");
|
|
9124
|
+
const status = await daytonaStatus();
|
|
9125
|
+
lines.push(...renderDaytona(status, pinned));
|
|
9126
|
+
}
|
|
9127
|
+
if (pinned) {
|
|
9128
|
+
lines.push("");
|
|
9129
|
+
lines.push(`project pin: box.image = ${pinned}`);
|
|
9130
|
+
}
|
|
9131
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
9132
|
+
}
|
|
9133
|
+
async function runPrepare(providerName, opts = {}) {
|
|
9134
|
+
if (!isKnownProvider(providerName)) {
|
|
9135
|
+
process.stderr.write("error: --provider must be one of: docker, daytona, hetzner, vercel\n");
|
|
9136
|
+
process.exit(1);
|
|
9137
|
+
}
|
|
9138
|
+
if (providerName === "daytona" && !opts.yes && process.stdin.isTTY) {
|
|
9139
|
+
process.stdout.write(
|
|
9140
|
+
"This will trigger a Daytona image build (~7 min cold, ~seconds with cache) and register a named snapshot in your org.\nRe-run with --yes to skip this notice.\n"
|
|
9141
|
+
);
|
|
9142
|
+
}
|
|
9143
|
+
const provider = await getProvider(providerName);
|
|
9144
|
+
if (typeof provider.prepare !== "function") {
|
|
9145
|
+
log29.error(`provider '${providerName}' does not implement prepare`);
|
|
9146
|
+
process.exit(1);
|
|
9147
|
+
}
|
|
9148
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9149
|
+
const registry = providerName === "docker" ? await loadEffectiveConfig(cwd).then((c) => c.effective.box.imageRegistry).catch(() => void 0) : void 0;
|
|
9150
|
+
const sp = spinner7();
|
|
9151
|
+
sp.start(`preparing ${providerName}\u2026`);
|
|
9152
|
+
try {
|
|
9153
|
+
const result = await provider.prepare({
|
|
9154
|
+
name: opts.name,
|
|
9155
|
+
hostWorkspace: cwd,
|
|
9156
|
+
force: opts.force,
|
|
9157
|
+
allowPull: opts.build ? false : void 0,
|
|
9158
|
+
registry,
|
|
9159
|
+
onLog: (line) => sp.message(line.slice(0, 80))
|
|
9160
|
+
});
|
|
9161
|
+
if (result.snapshotName !== void 0) {
|
|
9162
|
+
sp.stop(`prepared ${providerName}: snapshot '${result.snapshotName}'`);
|
|
9163
|
+
try {
|
|
9164
|
+
const written = await setConfigValue("project", "box.image", result.snapshotName, cwd);
|
|
9165
|
+
log29.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
|
|
9166
|
+
} catch (err) {
|
|
9167
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9168
|
+
log29.warn(
|
|
9169
|
+
`prepared snapshot '${result.snapshotName}', but failed to pin it into the project config: ${msg}
|
|
9170
|
+
Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.`
|
|
9171
|
+
);
|
|
9172
|
+
}
|
|
9173
|
+
} else {
|
|
9174
|
+
sp.stop(`${providerName.slice(0, 1).toUpperCase() + providerName.slice(1)} provider ready`);
|
|
9175
|
+
}
|
|
9176
|
+
if (!opts.suppressStatus) {
|
|
9177
|
+
process.stdout.write("\n");
|
|
9178
|
+
await showStatus({ onlyProvider: providerName });
|
|
9179
|
+
}
|
|
9180
|
+
if (!opts.suppressTip) {
|
|
9181
|
+
log29.info(
|
|
9182
|
+
"tip: install the agentbox host skill so Claude Code on this machine can drive AgentBox for you:\n npx skills add https://github.com/madarco/agentbox --skill agentbox"
|
|
9183
|
+
);
|
|
9184
|
+
}
|
|
9185
|
+
} catch (err) {
|
|
9186
|
+
sp.stop(`prepare failed: ${describeError(err)}`);
|
|
9187
|
+
throw err;
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
var prepareCommand = new Command24("prepare").description(
|
|
9191
|
+
"Build base sandbox images / snapshots, or show what is already prepared across providers."
|
|
9192
|
+
).option(
|
|
9193
|
+
"-p, --provider <name>",
|
|
9194
|
+
"provider to prepare (docker | daytona | hetzner | vercel). Omit for status-only."
|
|
9195
|
+
).option("-n, --name <name>", "snapshot name (Daytona only; default: agentbox-base-<timestamp>)").option("-f, --force", "rebuild even if the image / snapshot already exists").option(
|
|
9196
|
+
"--build",
|
|
9197
|
+
"docker: build the base image locally instead of pulling the prebuilt one from the registry"
|
|
9198
|
+
).option("-y, --yes", "skip confirmation prompts (cost / time warnings)").option("--status", "show status without preparing anything").action(async (opts) => {
|
|
9199
|
+
if (!opts.provider || opts.status) {
|
|
9200
|
+
await showStatus({});
|
|
9201
|
+
return;
|
|
9202
|
+
}
|
|
9203
|
+
const providerName = opts.provider.trim();
|
|
9204
|
+
intro5(`preparing ${providerName} base image`);
|
|
9205
|
+
await runPrepare(providerName, {
|
|
9206
|
+
name: opts.name,
|
|
9207
|
+
force: opts.force,
|
|
9208
|
+
build: opts.build,
|
|
9209
|
+
yes: opts.yes
|
|
9210
|
+
});
|
|
9211
|
+
});
|
|
9212
|
+
function describeError(err) {
|
|
9213
|
+
if (!(err instanceof Error)) return String(err);
|
|
9214
|
+
const parts = [err.message];
|
|
9215
|
+
let cause = err.cause;
|
|
9216
|
+
for (let i = 0; i < 5 && cause; i++) {
|
|
9217
|
+
if (cause instanceof Error) {
|
|
9218
|
+
parts.push(`caused by: ${cause.message}`);
|
|
9219
|
+
const code = cause.code;
|
|
9220
|
+
if (typeof code === "string") parts.push(`(${code})`);
|
|
9221
|
+
cause = cause.cause;
|
|
9222
|
+
} else if (typeof cause === "object") {
|
|
9223
|
+
parts.push(`caused by: ${JSON.stringify(cause)}`);
|
|
9224
|
+
break;
|
|
9225
|
+
} else {
|
|
9226
|
+
parts.push(`caused by: ${String(cause)}`);
|
|
9227
|
+
break;
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
return parts.join(" \u2014 ");
|
|
9231
|
+
}
|
|
9232
|
+
|
|
9233
|
+
// src/commands/install.ts
|
|
9234
|
+
var MANAGED_SENTINEL = "<!-- agentbox-managed:v1 -->";
|
|
9235
|
+
var LOGO_L1 = "\u2584\u2580\u2588 \u2588\u2580\u2580 \u2588\u2580\u2580 \u2588\u2584\u2591\u2588 \u2580\u2588\u2580 \u2588\u2584\u2584 \u2588\u2580\u2588 \u2580\u2584\u2580";
|
|
9236
|
+
var LOGO_L2 = "\u2588\u2580\u2588 \u2588\u2584\u2588 \u2588\u2588\u2584 \u2588\u2591\u2580\u2588 \u2591\u2588\u2591 \u2588\u2584\u2588 \u2588\u2584\u2588 \u2588\u2591\u2588";
|
|
9237
|
+
var LOGO_WIDTH = LOGO_L1.length;
|
|
9238
|
+
var BANNER = (() => {
|
|
9239
|
+
const art = `${LOGO_L1}
|
|
9240
|
+
${LOGO_L2}`;
|
|
9241
|
+
const tinted = process.env.NO_COLOR ? art : `\x1B[38;5;39m${art}\x1B[0m`;
|
|
9242
|
+
return `
|
|
9243
|
+
${tinted}
|
|
9244
|
+
|
|
9245
|
+
`;
|
|
9246
|
+
})();
|
|
9247
|
+
var SYNC_BEGIN2 = "\x1B[?2026h";
|
|
9248
|
+
var SYNC_END2 = "\x1B[?2026l";
|
|
9249
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
9250
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
9251
|
+
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
9252
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9253
|
+
function shineColor(dist) {
|
|
9254
|
+
const d = Math.abs(dist);
|
|
9255
|
+
if (d === 0) return 231;
|
|
9256
|
+
if (d === 1) return 159;
|
|
9257
|
+
if (d === 2) return 81;
|
|
9258
|
+
return 39;
|
|
9259
|
+
}
|
|
9260
|
+
function paintLine(line, center) {
|
|
9261
|
+
let out = "";
|
|
9262
|
+
let prev = -1;
|
|
9263
|
+
for (let i = 0; i < line.length; i++) {
|
|
9264
|
+
const ch = line[i];
|
|
9265
|
+
const c = shineColor(i - center);
|
|
9266
|
+
if (c !== prev) {
|
|
9267
|
+
out += `\x1B[38;5;${String(c)}m`;
|
|
9268
|
+
prev = c;
|
|
9269
|
+
}
|
|
9270
|
+
out += ch;
|
|
9271
|
+
}
|
|
9272
|
+
return out + "\x1B[0m";
|
|
9273
|
+
}
|
|
9274
|
+
async function animateBanner() {
|
|
9275
|
+
if (process.env.NO_COLOR || process.env.CI || process.env.AGENTBOX_NO_ANIM || !process.stdout.isTTY) {
|
|
9276
|
+
process.stdout.write(BANNER);
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
const restoreCursor = () => {
|
|
9280
|
+
process.stdout.write(SHOW_CURSOR);
|
|
9281
|
+
};
|
|
9282
|
+
process.once("exit", restoreCursor);
|
|
9283
|
+
const onSigint = () => {
|
|
9284
|
+
restoreCursor();
|
|
9285
|
+
process.exit(130);
|
|
9286
|
+
};
|
|
9287
|
+
process.once("SIGINT", onSigint);
|
|
9288
|
+
const frameMs = 45;
|
|
9289
|
+
const start = -3;
|
|
9290
|
+
const end = LOGO_WIDTH + 2;
|
|
9291
|
+
const down = 4;
|
|
9292
|
+
process.stdout.write(`
|
|
9293
|
+
${HIDE_CURSOR}`);
|
|
9294
|
+
process.stdout.write("\n".repeat(down) + `\x1B[${String(down)}A`);
|
|
9295
|
+
const statusLine2 = (spin) => ` \x1B[38;5;51m${spin}\x1B[0m \x1B[38;5;245mChecking system\u2026\x1B[0m`;
|
|
9296
|
+
for (let center = start; center <= end; center++) {
|
|
9297
|
+
const spin = SPIN[Math.floor((center - start) / 2) % SPIN.length] ?? SPIN[0];
|
|
9298
|
+
const frame = SYNC_BEGIN2 + paintLine(LOGO_L1, center) + "\n" + paintLine(LOGO_L2, center) + "\n\n\x1B[2K" + // down to the status row (col 0), clear it
|
|
9299
|
+
statusLine2(spin) + "\x1B[3A\r" + // back up to the logo's first row
|
|
9300
|
+
SYNC_END2;
|
|
9301
|
+
process.stdout.write(frame);
|
|
9302
|
+
await sleep3(frameMs);
|
|
9303
|
+
}
|
|
9304
|
+
process.stdout.write(SYNC_BEGIN2 + `\x1B[38;5;39m${LOGO_L1}
|
|
9305
|
+
${LOGO_L2}\x1B[0m` + SYNC_END2);
|
|
9306
|
+
await sleep3(250);
|
|
9307
|
+
process.stdout.write(SYNC_BEGIN2 + "\n\x1B[2K\n\x1B[2K" + SHOW_CURSOR + SYNC_END2);
|
|
9308
|
+
process.removeListener("exit", restoreCursor);
|
|
9309
|
+
process.removeListener("SIGINT", onSigint);
|
|
9310
|
+
}
|
|
9311
|
+
var LEGACY_INFO_MARKER = "Drive AgentBox from the host:";
|
|
9312
|
+
function installTargets() {
|
|
9313
|
+
const home = homedir13();
|
|
9314
|
+
const claudeSkills = join15(home, ".claude", "skills");
|
|
9315
|
+
return [
|
|
9316
|
+
{ src: join15("agentbox", "SKILL.md"), dest: join15(claudeSkills, "agentbox", "SKILL.md") },
|
|
9317
|
+
{
|
|
9318
|
+
src: join15("agentbox-info", "SKILL.md"),
|
|
9319
|
+
dest: join15(claudeSkills, "agentbox-info", "SKILL.md")
|
|
9320
|
+
},
|
|
9321
|
+
{
|
|
9322
|
+
src: join15("codex", "agentbox.md"),
|
|
9323
|
+
dest: join15(home, ".codex", "prompts", "agentbox.md"),
|
|
9324
|
+
gateDir: join15(home, ".codex")
|
|
9325
|
+
},
|
|
9326
|
+
{
|
|
9327
|
+
src: join15("opencode", "agentbox.md"),
|
|
9328
|
+
dest: join15(home, ".config", "opencode", "commands", "agentbox.md"),
|
|
9329
|
+
gateDir: join15(home, ".config", "opencode")
|
|
9330
|
+
}
|
|
9331
|
+
];
|
|
9332
|
+
}
|
|
9333
|
+
function resolveHostSkillsDir() {
|
|
9334
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
9335
|
+
const candidates = [
|
|
9336
|
+
resolve2(here, "..", "share", "host-skills"),
|
|
9337
|
+
resolve2(here, "..", "..", "share", "host-skills")
|
|
9338
|
+
];
|
|
9339
|
+
for (const c of candidates) {
|
|
9340
|
+
if (existsSync6(c)) return c;
|
|
9341
|
+
}
|
|
9342
|
+
throw new Error(`could not locate bundled host skills; tried:
|
|
9343
|
+
${candidates.join("\n ")}`);
|
|
9344
|
+
}
|
|
9345
|
+
function writableReason(target, force) {
|
|
9346
|
+
if (!existsSync6(target)) return "new";
|
|
9347
|
+
const existing = readFileSync(target, "utf8");
|
|
9348
|
+
if (existing.includes(MANAGED_SENTINEL) || existing.includes(LEGACY_INFO_MARKER)) {
|
|
9349
|
+
return "managed";
|
|
9350
|
+
}
|
|
9351
|
+
return force ? "forced" : "skip";
|
|
9352
|
+
}
|
|
9353
|
+
function installHostSkills(opts = {}) {
|
|
9354
|
+
const force = opts.force === true;
|
|
9355
|
+
const dryRun = opts.dryRun === true;
|
|
9356
|
+
const quiet = opts.quiet === true;
|
|
9357
|
+
const srcDir = resolveHostSkillsDir();
|
|
9358
|
+
const written = [];
|
|
9359
|
+
const blocked = [];
|
|
9360
|
+
let skipped = 0;
|
|
8234
9361
|
for (const t of installTargets()) {
|
|
8235
|
-
const src =
|
|
8236
|
-
if (!
|
|
8237
|
-
|
|
9362
|
+
const src = join15(srcDir, t.src);
|
|
9363
|
+
if (!existsSync6(src)) {
|
|
9364
|
+
if (!quiet) log30.warn(`bundled file missing (skipped): ${src}`);
|
|
8238
9365
|
skipped++;
|
|
8239
9366
|
continue;
|
|
8240
9367
|
}
|
|
8241
|
-
if (t.gateDir && !
|
|
9368
|
+
if (t.gateDir && !existsSync6(t.gateDir)) continue;
|
|
8242
9369
|
const reason = writableReason(t.dest, force);
|
|
8243
9370
|
if (reason === "skip") {
|
|
8244
|
-
|
|
9371
|
+
if (!quiet) log30.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
|
|
9372
|
+
blocked.push(t.dest);
|
|
8245
9373
|
skipped++;
|
|
8246
9374
|
continue;
|
|
8247
9375
|
}
|
|
8248
9376
|
if (dryRun) {
|
|
8249
|
-
|
|
9377
|
+
if (!quiet) log30.info(`would write ${t.dest} (${reason})`);
|
|
8250
9378
|
written.push(t.dest);
|
|
8251
9379
|
continue;
|
|
8252
9380
|
}
|
|
8253
|
-
|
|
8254
|
-
|
|
9381
|
+
mkdirSync5(dirname2(t.dest), { recursive: true });
|
|
9382
|
+
writeFileSync4(t.dest, readFileSync(src, "utf8"));
|
|
8255
9383
|
written.push(t.dest);
|
|
8256
9384
|
}
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
9385
|
+
return { written, skipped, blocked };
|
|
9386
|
+
}
|
|
9387
|
+
var PROVIDER_HINTS = {
|
|
9388
|
+
docker: "builds a ~1GB local image; no login needed",
|
|
9389
|
+
hetzner: "paste an API token from the Hetzner Console",
|
|
9390
|
+
daytona: "approve a browser sign-in link",
|
|
9391
|
+
vercel: "installs the Vercel sandbox CLI, then a browser sign-in"
|
|
9392
|
+
};
|
|
9393
|
+
var PROVIDER_LABEL = {
|
|
9394
|
+
docker: "Docker (local)",
|
|
9395
|
+
hetzner: "Hetzner (cloud VPS)",
|
|
9396
|
+
daytona: "Daytona (cloud sandbox)",
|
|
9397
|
+
vercel: "Vercel (cloud microVM)"
|
|
9398
|
+
};
|
|
9399
|
+
function ensureTty() {
|
|
9400
|
+
if (process.stdin.isTTY && process.stdout.isTTY) return true;
|
|
9401
|
+
process.stderr.write(
|
|
9402
|
+
"agentbox install: an interactive terminal is required. Run `agentbox <provider> login` and `agentbox prepare --provider <name>` instead.\n"
|
|
9403
|
+
);
|
|
9404
|
+
return false;
|
|
9405
|
+
}
|
|
9406
|
+
async function runProviderLogin(name) {
|
|
9407
|
+
if (name === "docker") return true;
|
|
9408
|
+
if (name === "daytona") {
|
|
9409
|
+
const mod2 = await import("./dist-BD5QJRDC.js");
|
|
9410
|
+
const status2 = await mod2.getDaytonaStatus();
|
|
9411
|
+
if (status2.configured) {
|
|
9412
|
+
log30.info("daytona: already configured");
|
|
9413
|
+
const rotate = await confirm14({ message: "Re-authenticate Daytona?", initialValue: false });
|
|
9414
|
+
if (isCancel15(rotate)) return false;
|
|
9415
|
+
if (rotate) await mod2.ensureDaytonaCredentials({ force: true });
|
|
9416
|
+
return true;
|
|
9417
|
+
}
|
|
9418
|
+
await mod2.ensureDaytonaCredentials();
|
|
9419
|
+
return true;
|
|
9420
|
+
}
|
|
9421
|
+
if (name === "hetzner") {
|
|
9422
|
+
const mod2 = await import("./dist-BNI5PQYK.js");
|
|
9423
|
+
const status2 = mod2.readHetznerCredStatus();
|
|
9424
|
+
if (status2.source !== "none") {
|
|
9425
|
+
log30.info("hetzner: already configured");
|
|
9426
|
+
const rotate = await confirm14({ message: "Re-authenticate Hetzner?", initialValue: false });
|
|
9427
|
+
if (isCancel15(rotate)) return false;
|
|
9428
|
+
if (rotate) await mod2.ensureHetznerCredentials({ force: true });
|
|
9429
|
+
return true;
|
|
9430
|
+
}
|
|
9431
|
+
await mod2.ensureHetznerCredentials();
|
|
9432
|
+
return true;
|
|
9433
|
+
}
|
|
9434
|
+
const mod = await import("./dist-SJHY3HYN.js");
|
|
9435
|
+
const status = mod.readVercelCredStatus();
|
|
9436
|
+
if (status.auth !== "none") {
|
|
9437
|
+
log30.info(`vercel: already configured (${status.auth})`);
|
|
9438
|
+
const rotate = await confirm14({ message: "Re-authenticate Vercel?", initialValue: false });
|
|
9439
|
+
if (isCancel15(rotate)) return false;
|
|
9440
|
+
if (rotate) await mod.ensureVercelCredentials({ force: true });
|
|
9441
|
+
return true;
|
|
9442
|
+
}
|
|
9443
|
+
await mod.ensureVercelCredentials();
|
|
9444
|
+
return true;
|
|
9445
|
+
}
|
|
9446
|
+
function tutorialBody(provider) {
|
|
9447
|
+
const startCmd = provider === "docker" ? "agentbox claude" : `agentbox ${provider} claude`;
|
|
9448
|
+
return `Get started:
|
|
9449
|
+
${startCmd} # for claude, codex, opencode
|
|
9450
|
+
-> Setup wizard? -> Yes # install dependencies and setup agentbox.yaml
|
|
9451
|
+
-> Ctrl+a d # to detach from the box and leave claude running
|
|
9452
|
+
agentbox claude attach 1 # resume it later
|
|
9453
|
+
agentbox install # to set up another provider`;
|
|
9454
|
+
}
|
|
9455
|
+
var KNOWN_PROVIDERS = ["docker", "hetzner", "daytona", "vercel"];
|
|
9456
|
+
function isProviderName(s) {
|
|
9457
|
+
return KNOWN_PROVIDERS.includes(s);
|
|
9458
|
+
}
|
|
9459
|
+
async function runInstallWizard(opts = {}) {
|
|
9460
|
+
if (!ensureTty()) return false;
|
|
9461
|
+
await animateBanner();
|
|
9462
|
+
intro6("Check system compatibility");
|
|
9463
|
+
const sysResults = await runSystemChecks();
|
|
9464
|
+
const sysGroup = { title: "system", results: sysResults };
|
|
9465
|
+
process.stdout.write(" " + formatCompact([sysGroup]) + "\n");
|
|
9466
|
+
const hardFail = sysResults.find((r) => r.status === "fail");
|
|
9467
|
+
if (hardFail) {
|
|
9468
|
+
log30.error(`system check failed: ${hardFail.label} \u2014 ${hardFail.detail}`);
|
|
9469
|
+
log30.info("run `agentbox doctor` for full detail");
|
|
9470
|
+
const cont = await confirm14({ message: "Continue anyway?", initialValue: false });
|
|
9471
|
+
if (isCancel15(cont) || !cont) {
|
|
9472
|
+
outro6("aborted");
|
|
9473
|
+
return false;
|
|
9474
|
+
}
|
|
9475
|
+
}
|
|
9476
|
+
let providerName;
|
|
9477
|
+
if (opts.provider) {
|
|
9478
|
+
const candidate = opts.provider.trim();
|
|
9479
|
+
if (!isProviderName(candidate)) {
|
|
9480
|
+
log30.error(`unknown --provider: ${candidate}`);
|
|
9481
|
+
return false;
|
|
9482
|
+
}
|
|
9483
|
+
providerName = candidate;
|
|
9484
|
+
} else {
|
|
9485
|
+
const picked = await select2({
|
|
9486
|
+
message: "Which provider do you want to set up?",
|
|
9487
|
+
initialValue: "docker",
|
|
9488
|
+
options: KNOWN_PROVIDERS.map((p) => ({
|
|
9489
|
+
value: p,
|
|
9490
|
+
label: PROVIDER_LABEL[p],
|
|
9491
|
+
hint: PROVIDER_HINTS[p]
|
|
9492
|
+
}))
|
|
9493
|
+
});
|
|
9494
|
+
if (isCancel15(picked)) {
|
|
9495
|
+
outro6("cancelled");
|
|
9496
|
+
return false;
|
|
9497
|
+
}
|
|
9498
|
+
providerName = picked;
|
|
8260
9499
|
}
|
|
8261
|
-
if (
|
|
8262
|
-
|
|
9500
|
+
if (providerName !== "docker") {
|
|
9501
|
+
const loggedIn = await runProviderLogin(providerName);
|
|
9502
|
+
if (!loggedIn) {
|
|
9503
|
+
outro6("cancelled");
|
|
9504
|
+
return false;
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
const prepareMsg = providerName === "docker" ? "Build the box image now? (~1GB, a few minutes)" : `Bake the ${providerName} base snapshot now? (a few minutes, uses cloud time)`;
|
|
9508
|
+
const wantPrepare = opts.yes ? true : await confirm14({ message: prepareMsg, initialValue: true });
|
|
9509
|
+
if (isCancel15(wantPrepare)) {
|
|
9510
|
+
outro6("cancelled");
|
|
9511
|
+
return false;
|
|
9512
|
+
}
|
|
9513
|
+
if (wantPrepare) {
|
|
9514
|
+
try {
|
|
9515
|
+
await runPrepare(providerName, {
|
|
9516
|
+
cwd: process.cwd(),
|
|
9517
|
+
yes: true,
|
|
9518
|
+
suppressStatus: true,
|
|
9519
|
+
suppressTip: true
|
|
9520
|
+
});
|
|
9521
|
+
} catch (err) {
|
|
9522
|
+
log30.warn(
|
|
9523
|
+
`prepare failed: ${err instanceof Error ? err.message : String(err)} \u2014 you can rerun \`agentbox prepare --provider ${providerName}\` later`
|
|
9524
|
+
);
|
|
9525
|
+
}
|
|
9526
|
+
} else {
|
|
9527
|
+
log30.info(
|
|
9528
|
+
`skipped \u2014 the ${providerName} base will build lazily on first \`agentbox ${providerName === "docker" ? "" : providerName + " "}create\``
|
|
9529
|
+
);
|
|
9530
|
+
}
|
|
9531
|
+
const sp = spinner8();
|
|
9532
|
+
sp.start("installing host /agentbox skill\u2026");
|
|
9533
|
+
let skillRes;
|
|
9534
|
+
try {
|
|
9535
|
+
skillRes = installHostSkills({ force: opts.force, dryRun: opts.dryRun, quiet: true });
|
|
9536
|
+
if (skillRes.written.length > 0) {
|
|
9537
|
+
sp.stop(`Agentbox Skills: Installed in ${String(skillRes.written.length)} locations`);
|
|
9538
|
+
} else {
|
|
9539
|
+
sp.stop(`Agentbox Skills: nothing to write (${String(skillRes.skipped)} skipped)`);
|
|
9540
|
+
}
|
|
9541
|
+
if (skillRes.blocked.length > 0) {
|
|
9542
|
+
log30.warn(
|
|
9543
|
+
`user-modified host skill file(s) left in place: ${skillRes.blocked.join(", ")}
|
|
9544
|
+
pass \`agentbox install --skills-only --force\` to overwrite`
|
|
9545
|
+
);
|
|
9546
|
+
}
|
|
9547
|
+
} catch (err) {
|
|
9548
|
+
sp.stop("Agentbox Skills: failed");
|
|
9549
|
+
log30.warn(err instanceof Error ? err.message : String(err));
|
|
9550
|
+
}
|
|
9551
|
+
markSetupComplete(providerName);
|
|
9552
|
+
const providerGroup = await runProviderChecks(providerName);
|
|
9553
|
+
process.stdout.write(" " + formatCompact([sysGroup, providerGroup]) + "\n");
|
|
9554
|
+
note(tutorialBody(providerName), "Next steps");
|
|
9555
|
+
outro6(
|
|
9556
|
+
opts.fromAutoTrigger ? "\u2728 Setup complete \u2014 continuing with your command\u2026" : "\u2728 Setup complete"
|
|
9557
|
+
);
|
|
9558
|
+
return true;
|
|
9559
|
+
}
|
|
9560
|
+
var installCommand = new Command25("install").description(
|
|
9561
|
+
"Interactive setup wizard: system check, pick a provider, log in, prepare its base image/snapshot, and install the host /agentbox skill. `--skills-only` runs just the skill install."
|
|
9562
|
+
).option(
|
|
9563
|
+
"--skills-only",
|
|
9564
|
+
"only install the host /agentbox skill files (no wizard, no login, no prepare)"
|
|
9565
|
+
).option("--force", "overwrite existing skill files even if not AgentBox-managed").option("--dry-run", "print what would be written without changing anything").option(
|
|
9566
|
+
"-p, --provider <name>",
|
|
9567
|
+
"pre-select the provider to set up (docker | daytona | hetzner | vercel)"
|
|
9568
|
+
).option("-y, --yes", "auto-confirm the prepare step").action(async (opts) => {
|
|
9569
|
+
if (opts.skillsOnly) {
|
|
9570
|
+
intro6("Installing AgentBox host commands...");
|
|
9571
|
+
let res;
|
|
9572
|
+
try {
|
|
9573
|
+
res = installHostSkills({ force: opts.force, dryRun: opts.dryRun });
|
|
9574
|
+
} catch (err) {
|
|
9575
|
+
log30.error(err instanceof Error ? err.message : String(err));
|
|
9576
|
+
process.exit(1);
|
|
9577
|
+
}
|
|
9578
|
+
if (opts.dryRun) {
|
|
9579
|
+
outro6(
|
|
9580
|
+
`dry-run: ${String(res.written.length)} file(s) would be written, ${String(res.skipped)} skipped`
|
|
9581
|
+
);
|
|
9582
|
+
return;
|
|
9583
|
+
}
|
|
9584
|
+
if (res.written.length === 0) {
|
|
9585
|
+
outro6(`nothing installed (${String(res.skipped)} skipped)`);
|
|
9586
|
+
return;
|
|
9587
|
+
}
|
|
9588
|
+
outro6(`installed: ${res.written.join(", ")}`);
|
|
8263
9589
|
return;
|
|
8264
9590
|
}
|
|
8265
|
-
|
|
9591
|
+
const ok = await runInstallWizard({
|
|
9592
|
+
provider: opts.provider,
|
|
9593
|
+
yes: opts.yes,
|
|
9594
|
+
force: opts.force,
|
|
9595
|
+
dryRun: opts.dryRun
|
|
9596
|
+
});
|
|
9597
|
+
if (!ok) process.exit(1);
|
|
9598
|
+
});
|
|
9599
|
+
|
|
9600
|
+
// src/commands/doctor.ts
|
|
9601
|
+
import { Command as Command26 } from "commander";
|
|
9602
|
+
var doctorCommand = new Command26("doctor").description(
|
|
9603
|
+
"Diagnose system compatibility and provider readiness (Node, git, ssh, Docker daemon, provider credentials, prepared snapshots)."
|
|
9604
|
+
).option(
|
|
9605
|
+
"-p, --provider <name>",
|
|
9606
|
+
"limit checks to one provider (docker | daytona | hetzner | vercel)"
|
|
9607
|
+
).action(async (opts) => {
|
|
9608
|
+
let groups;
|
|
9609
|
+
if (opts.provider) {
|
|
9610
|
+
const name = opts.provider.trim();
|
|
9611
|
+
if (!isKnownProvider(name)) {
|
|
9612
|
+
process.stderr.write(
|
|
9613
|
+
"error: --provider must be one of: docker, daytona, hetzner, vercel\n"
|
|
9614
|
+
);
|
|
9615
|
+
process.exit(1);
|
|
9616
|
+
}
|
|
9617
|
+
groups = [
|
|
9618
|
+
{ title: "system", results: await runSystemChecks() },
|
|
9619
|
+
await runProviderChecks(name)
|
|
9620
|
+
];
|
|
9621
|
+
} else {
|
|
9622
|
+
groups = await runAllChecks();
|
|
9623
|
+
}
|
|
9624
|
+
process.stdout.write(formatDetailed(groups).join("\n") + "\n");
|
|
9625
|
+
const worst = worstStatus(groups);
|
|
9626
|
+
if (worst === "fail") {
|
|
9627
|
+
process.stdout.write(
|
|
9628
|
+
"\nOne or more required checks failed. Fix the FAIL items above before continuing.\n"
|
|
9629
|
+
);
|
|
9630
|
+
process.exit(1);
|
|
9631
|
+
}
|
|
9632
|
+
if (worst === "warn") {
|
|
9633
|
+
process.stdout.write(
|
|
9634
|
+
"\nWarnings are providers that need setup. Run `agentbox install` to configure one,\nor `agentbox prepare --status` to see remote snapshot inventory.\n"
|
|
9635
|
+
);
|
|
9636
|
+
} else {
|
|
9637
|
+
process.stdout.write("\nAll checks passed.\n");
|
|
9638
|
+
}
|
|
8266
9639
|
});
|
|
8267
9640
|
|
|
8268
9641
|
// src/commands/git.ts
|
|
8269
|
-
import { Command as
|
|
9642
|
+
import { Command as Command27 } from "commander";
|
|
8270
9643
|
var WORKSPACE = "/workspace";
|
|
8271
9644
|
var TOKEN_TTL_MS = 12e4;
|
|
8272
|
-
async function runInBox(box,
|
|
9645
|
+
async function runInBox(box, argv2) {
|
|
8273
9646
|
const provider = await providerForBox(box);
|
|
8274
|
-
return provider.exec(box,
|
|
9647
|
+
return provider.exec(box, argv2, { cwd: WORKSPACE });
|
|
8275
9648
|
}
|
|
8276
|
-
async function runAndStream(box,
|
|
8277
|
-
const r = await runInBox(box,
|
|
9649
|
+
async function runAndStream(box, argv2) {
|
|
9650
|
+
const r = await runInBox(box, argv2);
|
|
8278
9651
|
if (r.stdout) process.stdout.write(r.stdout);
|
|
8279
9652
|
if (r.stderr) process.stderr.write(r.stderr);
|
|
8280
9653
|
return r.exitCode;
|
|
@@ -8298,33 +9671,33 @@ function buildPredictedGhPrParams(ghArgs) {
|
|
|
8298
9671
|
async function exitWith(code) {
|
|
8299
9672
|
process.exit(code);
|
|
8300
9673
|
}
|
|
8301
|
-
var pushCommand = new
|
|
9674
|
+
var pushCommand = new Command27("push").description("Push the box's branch via the host relay (host creds, no prompt)").argument("<box>", "box ref: project index, id, id prefix, name, or container").argument("[args...]", "extra flags forwarded to `agentbox-ctl git push` (e.g. --force-with-lease, --tags)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
|
|
8302
9675
|
try {
|
|
8303
9676
|
const box = await resolveBoxOrExit(boxRef);
|
|
8304
9677
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8305
9678
|
const tokenArgs = await hostInitiatedArgs(box.id, "git.push", predicted);
|
|
8306
|
-
const
|
|
8307
|
-
if (opts.remote)
|
|
8308
|
-
|
|
8309
|
-
await exitWith(await runAndStream(box,
|
|
9679
|
+
const argv2 = ["agentbox-ctl", "git", "push", ...tokenArgs];
|
|
9680
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9681
|
+
argv2.push(...args);
|
|
9682
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8310
9683
|
} catch (err) {
|
|
8311
9684
|
handleLifecycleError(err);
|
|
8312
9685
|
}
|
|
8313
9686
|
});
|
|
8314
|
-
var fetchCommand = new
|
|
9687
|
+
var fetchCommand = new Command27("fetch").description("Fetch via the host relay (refs land in the shared .git)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `agentbox-ctl git fetch` (e.g. --prune)").option("--remote <name>", "remote name (default: origin)").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args, opts) => {
|
|
8315
9688
|
try {
|
|
8316
9689
|
const box = await resolveBoxOrExit(boxRef);
|
|
8317
9690
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8318
9691
|
const tokenArgs = await hostInitiatedArgs(box.id, "git.fetch", predicted);
|
|
8319
|
-
const
|
|
8320
|
-
if (opts.remote)
|
|
8321
|
-
|
|
8322
|
-
await exitWith(await runAndStream(box,
|
|
9692
|
+
const argv2 = ["agentbox-ctl", "git", "fetch", ...tokenArgs];
|
|
9693
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9694
|
+
argv2.push(...args);
|
|
9695
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8323
9696
|
} catch (err) {
|
|
8324
9697
|
handleLifecycleError(err);
|
|
8325
9698
|
}
|
|
8326
9699
|
});
|
|
8327
|
-
var pullCommand = new
|
|
9700
|
+
var pullCommand = new Command27("pull").description(
|
|
8328
9701
|
"Fetch via the relay then merge in /workspace. With <branch>: first `git checkout <branch>` so the box switches base branch and pulls latest \u2014 useful for reusing a box on a new task."
|
|
8329
9702
|
).argument("<box>", "box ref").argument("[branch]", "optional branch to switch to before pulling (e.g. main)").argument("[args...]", "extra flags forwarded to `agentbox-ctl git pull`").option("--remote <name>", "remote name (default: origin)").option("--ff-only", "pass --ff-only to the in-box merge").allowExcessArguments(true).allowUnknownOption(true).action(
|
|
8330
9703
|
async (boxRef, branch, args, opts) => {
|
|
@@ -8336,17 +9709,17 @@ var pullCommand = new Command25("pull").description(
|
|
|
8336
9709
|
}
|
|
8337
9710
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8338
9711
|
const tokenArgs = await hostInitiatedArgs(box.id, "git.fetch", predicted);
|
|
8339
|
-
const
|
|
8340
|
-
if (opts.remote)
|
|
8341
|
-
if (opts.ffOnly)
|
|
8342
|
-
|
|
8343
|
-
await exitWith(await runAndStream(box,
|
|
9712
|
+
const argv2 = ["agentbox-ctl", "git", "pull", ...tokenArgs];
|
|
9713
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9714
|
+
if (opts.ffOnly) argv2.push("--ff-only");
|
|
9715
|
+
argv2.push(...args);
|
|
9716
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8344
9717
|
} catch (err) {
|
|
8345
9718
|
handleLifecycleError(err);
|
|
8346
9719
|
}
|
|
8347
9720
|
}
|
|
8348
9721
|
);
|
|
8349
|
-
var checkoutCommand = new
|
|
9722
|
+
var checkoutCommand = new Command27("checkout").description("Change the box's working branch (runs `git checkout <branch>` in /workspace)").argument("<box>", "box ref").argument("<branch>", "branch to check out inside the box").argument("[args...]", "extra flags forwarded to `git checkout`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, branch, args) => {
|
|
8350
9723
|
try {
|
|
8351
9724
|
const box = await resolveBoxOrExit(boxRef);
|
|
8352
9725
|
await exitWith(await runAndStream(box, ["git", "checkout", branch, ...args]));
|
|
@@ -8354,7 +9727,7 @@ var checkoutCommand = new Command25("checkout").description("Change the box's wo
|
|
|
8354
9727
|
handleLifecycleError(err);
|
|
8355
9728
|
}
|
|
8356
9729
|
});
|
|
8357
|
-
var statusCommand = new
|
|
9730
|
+
var statusCommand = new Command27("status").description("Run `git status` in the box's /workspace (read-only, no relay)").argument("<box>", "box ref").argument("[args...]", "extra flags forwarded to `git status`").allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
|
|
8358
9731
|
try {
|
|
8359
9732
|
const box = await resolveBoxOrExit(boxRef);
|
|
8360
9733
|
await exitWith(await runAndStream(box, ["git", "status", ...args]));
|
|
@@ -8378,7 +9751,7 @@ function injectPrCreateHead2(op, box, args) {
|
|
|
8378
9751
|
return injectPrCreateHead(op, rootWt?.branch, args);
|
|
8379
9752
|
}
|
|
8380
9753
|
function buildPrSubcommand(op) {
|
|
8381
|
-
return new
|
|
9754
|
+
return new Command27(op).description(PR_OP_DESCRIPTIONS[op]).argument("<box>", "box ref").argument(
|
|
8382
9755
|
"[args...]",
|
|
8383
9756
|
"extra flags forwarded to `gh pr <op>` (e.g. --title, --body, --label, --draft, --json)"
|
|
8384
9757
|
).allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
|
|
@@ -8387,25 +9760,25 @@ function buildPrSubcommand(op) {
|
|
|
8387
9760
|
const ghArgs = injectPrCreateHead2(op, box, args);
|
|
8388
9761
|
const predicted = buildPredictedGhPrParams(ghArgs);
|
|
8389
9762
|
const tokenArgs = await hostInitiatedArgs(box.id, `gh.pr.${op}`, predicted);
|
|
8390
|
-
const
|
|
8391
|
-
await exitWith(await runAndStream(box,
|
|
9763
|
+
const argv2 = ["agentbox-ctl", "gh", "pr", op, ...tokenArgs, ...ghArgs];
|
|
9764
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8392
9765
|
} catch (err) {
|
|
8393
9766
|
handleLifecycleError(err);
|
|
8394
9767
|
}
|
|
8395
9768
|
});
|
|
8396
9769
|
}
|
|
8397
|
-
var prCommand = new
|
|
9770
|
+
var prCommand = new Command27("pr").description(
|
|
8398
9771
|
"PR operations against a box's branch via the host `gh` CLI"
|
|
8399
9772
|
);
|
|
8400
9773
|
for (const op of GH_PR_OPS) {
|
|
8401
9774
|
const sub = buildPrSubcommand(op);
|
|
8402
9775
|
prCommand.addCommand(sub, op === "create" ? { isDefault: true } : void 0);
|
|
8403
9776
|
}
|
|
8404
|
-
var gitCommand = new
|
|
9777
|
+
var gitCommand = new Command27("git").description("Run git / gh pr operations against a box from the host").addCommand(pushCommand).addCommand(fetchCommand).addCommand(pullCommand).addCommand(checkoutCommand).addCommand(statusCommand).addCommand(prCommand);
|
|
8405
9778
|
|
|
8406
9779
|
// src/commands/list.ts
|
|
8407
|
-
import { log as
|
|
8408
|
-
import { Command as
|
|
9780
|
+
import { log as log31 } from "@clack/prompts";
|
|
9781
|
+
import { Command as Command28 } from "commander";
|
|
8409
9782
|
import { pathToFileURL } from "url";
|
|
8410
9783
|
|
|
8411
9784
|
// src/hyperlink.ts
|
|
@@ -8418,6 +9791,38 @@ function hyperlink(label, url, stream) {
|
|
|
8418
9791
|
return `${ESC2}]8;;${url}${ST}${label}${ESC2}]8;;${ST}`;
|
|
8419
9792
|
}
|
|
8420
9793
|
|
|
9794
|
+
// src/lib/cloud-state.ts
|
|
9795
|
+
var PROBE_TIMEOUT_MS = 4e3;
|
|
9796
|
+
async function applyLiveCloudStates(boxes) {
|
|
9797
|
+
await Promise.all(
|
|
9798
|
+
boxes.map(async (b) => {
|
|
9799
|
+
if (!b.provider || b.provider === "docker") return;
|
|
9800
|
+
try {
|
|
9801
|
+
const provider = await providerForBox(b);
|
|
9802
|
+
const state = await withTimeout(provider.probeState(b), PROBE_TIMEOUT_MS);
|
|
9803
|
+
if (state !== null) b.state = state;
|
|
9804
|
+
} catch {
|
|
9805
|
+
}
|
|
9806
|
+
})
|
|
9807
|
+
);
|
|
9808
|
+
}
|
|
9809
|
+
function withTimeout(p, ms) {
|
|
9810
|
+
return new Promise((resolve3) => {
|
|
9811
|
+
const t = setTimeout(() => resolve3(null), ms);
|
|
9812
|
+
if (typeof t.unref === "function") t.unref();
|
|
9813
|
+
p.then(
|
|
9814
|
+
(v) => {
|
|
9815
|
+
clearTimeout(t);
|
|
9816
|
+
resolve3(v);
|
|
9817
|
+
},
|
|
9818
|
+
() => {
|
|
9819
|
+
clearTimeout(t);
|
|
9820
|
+
resolve3(null);
|
|
9821
|
+
}
|
|
9822
|
+
);
|
|
9823
|
+
});
|
|
9824
|
+
}
|
|
9825
|
+
|
|
8421
9826
|
// src/watch.ts
|
|
8422
9827
|
function withWatchOptions(cmd) {
|
|
8423
9828
|
return cmd.option("-w, --watch", "redraw continuously until interrupted (Ctrl-C)").option("--interval <seconds>", "refresh interval for --watch", "2");
|
|
@@ -8433,7 +9838,7 @@ async function watchRender(produce, rawInterval) {
|
|
|
8433
9838
|
process.stdout.write("\x1B[?25l");
|
|
8434
9839
|
process.once("exit", () => process.stdout.write("\x1B[?25h"));
|
|
8435
9840
|
process.once("SIGINT", () => process.exit(0));
|
|
8436
|
-
const
|
|
9841
|
+
const sleep5 = (d) => new Promise((r) => setTimeout(r, d));
|
|
8437
9842
|
for (; ; ) {
|
|
8438
9843
|
let body;
|
|
8439
9844
|
try {
|
|
@@ -8449,7 +9854,7 @@ async function watchRender(produce, rawInterval) {
|
|
|
8449
9854
|
${body.replace(/\n+$/, "")}
|
|
8450
9855
|
`
|
|
8451
9856
|
);
|
|
8452
|
-
await
|
|
9857
|
+
await sleep5(ms);
|
|
8453
9858
|
}
|
|
8454
9859
|
}
|
|
8455
9860
|
|
|
@@ -8501,6 +9906,7 @@ function workspaceCell(path, target, stream) {
|
|
|
8501
9906
|
return { text: hyperlink(display, url, stream), width: display.length };
|
|
8502
9907
|
}
|
|
8503
9908
|
function agentSummary(b) {
|
|
9909
|
+
if (b.state !== "running") return "-";
|
|
8504
9910
|
const agents = [];
|
|
8505
9911
|
if (b.claudeActivity && b.claudeActivity !== "unknown") {
|
|
8506
9912
|
agents.push(`claude:${b.claudeActivity}`);
|
|
@@ -8554,14 +9960,19 @@ function renderTable(boxes, stream) {
|
|
|
8554
9960
|
(row2) => row2.map((cell, i) => padCell(cell ?? plain(""), i)).join(" ").trimEnd()
|
|
8555
9961
|
).join("\n");
|
|
8556
9962
|
}
|
|
8557
|
-
async function scopedBoxes(all) {
|
|
9963
|
+
async function scopedBoxes(all, live) {
|
|
8558
9964
|
const boxes = await listBoxes();
|
|
8559
|
-
if (all)
|
|
9965
|
+
if (all) {
|
|
9966
|
+
if (live) await applyLiveCloudStates(boxes);
|
|
9967
|
+
return { boxes, projectRoot: "", scoped: false };
|
|
9968
|
+
}
|
|
8560
9969
|
const { root } = await findProjectRoot(process.cwd());
|
|
8561
|
-
|
|
9970
|
+
const scoped2 = boxes.filter((b) => b.projectRoot === root);
|
|
9971
|
+
if (live) await applyLiveCloudStates(scoped2);
|
|
9972
|
+
return { boxes: scoped2, projectRoot: root, scoped: true };
|
|
8562
9973
|
}
|
|
8563
|
-
async function buildListText(all) {
|
|
8564
|
-
const { boxes, projectRoot, scoped: scoped2 } = await scopedBoxes(all);
|
|
9974
|
+
async function buildListText(all, live) {
|
|
9975
|
+
const { boxes, projectRoot, scoped: scoped2 } = await scopedBoxes(all, live);
|
|
8565
9976
|
if (boxes.length === 0) {
|
|
8566
9977
|
if (scoped2) {
|
|
8567
9978
|
return `no boxes in this project (${projectRoot}) \u2014 run \`agentbox create\`, or \`agentbox list --global\` to see all`;
|
|
@@ -8575,31 +9986,35 @@ async function buildListText(all) {
|
|
|
8575
9986
|
${table}`;
|
|
8576
9987
|
}
|
|
8577
9988
|
var listCommand2 = withWatchOptions(
|
|
8578
|
-
new
|
|
9989
|
+
new Command28("list").alias("ls").description("List agent boxes in the current project (-g for all)").option("-j, --json", "machine-readable JSON output").option("-g, --global", "include boxes from all projects").option(
|
|
9990
|
+
"--live",
|
|
9991
|
+
"probe live cloud state via the provider SDK (slower; default: last host-known state)"
|
|
9992
|
+
)
|
|
8579
9993
|
).action(async (opts) => {
|
|
8580
9994
|
if (opts.json && opts.watch) {
|
|
8581
|
-
|
|
9995
|
+
log31.error("cannot combine --json with --watch");
|
|
8582
9996
|
process.exit(2);
|
|
8583
9997
|
}
|
|
8584
9998
|
const all = opts.global ?? false;
|
|
9999
|
+
const live = opts.live ?? false;
|
|
8585
10000
|
if (opts.watch) {
|
|
8586
|
-
await watchRender(() => buildListText(all), opts.interval);
|
|
10001
|
+
await watchRender(() => buildListText(all, live), opts.interval);
|
|
8587
10002
|
return;
|
|
8588
10003
|
}
|
|
8589
10004
|
if (opts.json) {
|
|
8590
|
-
const { boxes } = await scopedBoxes(all);
|
|
10005
|
+
const { boxes } = await scopedBoxes(all, live);
|
|
8591
10006
|
process.stdout.write(JSON.stringify(boxes, null, 2) + "\n");
|
|
8592
10007
|
return;
|
|
8593
10008
|
}
|
|
8594
|
-
process.stdout.write(await buildListText(all) + "\n");
|
|
10009
|
+
process.stdout.write(await buildListText(all, live) + "\n");
|
|
8595
10010
|
});
|
|
8596
10011
|
|
|
8597
10012
|
// src/commands/logs.ts
|
|
8598
|
-
import { log as
|
|
8599
|
-
import { Command as
|
|
10013
|
+
import { log as log32 } from "@clack/prompts";
|
|
10014
|
+
import { Command as Command29 } from "commander";
|
|
8600
10015
|
import { spawn as spawn3 } from "child_process";
|
|
8601
10016
|
var DAEMON_LOG_PATH = "/var/log/agentbox/ctl-daemon.log";
|
|
8602
|
-
var logsCommand = new
|
|
10017
|
+
var logsCommand = new Command29("logs").description("Print recent log lines from a box service; -f to stream").argument(
|
|
8603
10018
|
"[box]",
|
|
8604
10019
|
"box ref (optional when cwd has exactly 1 box): project index, id, id prefix, name, or container"
|
|
8605
10020
|
).argument("[service]", "service name from agentbox.yaml").option("-n, --tail <n>", "how many recent lines to print first", "200").option("-f, --follow", "keep the connection open and stream new lines").option(
|
|
@@ -8617,9 +10032,9 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8617
10032
|
service = boxArg;
|
|
8618
10033
|
}
|
|
8619
10034
|
if (!service && !opts.daemon) {
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
10035
|
+
log32.error("missing <service> argument");
|
|
10036
|
+
log32.info("usage: agentbox logs [box] <service> [-n N] [-f]");
|
|
10037
|
+
log32.info(" agentbox logs [box] --daemon [-n N] [-f]");
|
|
8623
10038
|
process.exit(2);
|
|
8624
10039
|
}
|
|
8625
10040
|
const box = await resolveBoxOrExit(idOrName);
|
|
@@ -8630,7 +10045,7 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8630
10045
|
if (!opts.follow) {
|
|
8631
10046
|
const proc = await provider.exec(box, args, { user: "vscode" });
|
|
8632
10047
|
if (proc.exitCode !== 0) {
|
|
8633
|
-
|
|
10048
|
+
log32.error(
|
|
8634
10049
|
`${opts.daemon ? "daemon log" : "agentbox-ctl logs"} failed: ${proc.stderr || proc.stdout}`
|
|
8635
10050
|
);
|
|
8636
10051
|
process.exit(1);
|
|
@@ -8686,12 +10101,12 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8686
10101
|
});
|
|
8687
10102
|
|
|
8688
10103
|
// src/commands/open.ts
|
|
8689
|
-
import { log as
|
|
8690
|
-
import { execa as
|
|
8691
|
-
import { existsSync as
|
|
8692
|
-
import { homedir as
|
|
8693
|
-
import { join as
|
|
8694
|
-
import { Command as
|
|
10104
|
+
import { log as log33 } from "@clack/prompts";
|
|
10105
|
+
import { execa as execa3 } from "execa";
|
|
10106
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
10107
|
+
import { homedir as homedir14 } from "os";
|
|
10108
|
+
import { join as join16 } from "path";
|
|
10109
|
+
import { Command as Command30 } from "commander";
|
|
8695
10110
|
|
|
8696
10111
|
// src/commands/path.ts
|
|
8697
10112
|
async function runPath(box, opts) {
|
|
@@ -8713,7 +10128,7 @@ async function runPath(box, opts) {
|
|
|
8713
10128
|
}
|
|
8714
10129
|
|
|
8715
10130
|
// src/commands/open.ts
|
|
8716
|
-
var openCommand = new
|
|
10131
|
+
var openCommand = new Command30("open").description("Open a box's /workspace in Finder (docker: rsync'd snapshot; cloud: sshfs mount)").argument(
|
|
8717
10132
|
"[box]",
|
|
8718
10133
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
8719
10134
|
).option("--no-refresh", "skip the rsync; open whatever's already on disk (docker only)").option(
|
|
@@ -8752,7 +10167,7 @@ var openCommand = new Command28("open").description("Open a box's /workspace in
|
|
|
8752
10167
|
}
|
|
8753
10168
|
});
|
|
8754
10169
|
async function runCloudOpen(box, provider, opts) {
|
|
8755
|
-
const mountRoot =
|
|
10170
|
+
const mountRoot = join16(homedir14(), ".agentbox", "mounts", box.name);
|
|
8756
10171
|
if (opts.unmount) {
|
|
8757
10172
|
const ok = await tryUnmount(mountRoot);
|
|
8758
10173
|
if (ok) process.stdout.write(`unmounted ${mountRoot}
|
|
@@ -8789,14 +10204,14 @@ async function runCloudOpen(box, provider, opts) {
|
|
|
8789
10204
|
user: target.user,
|
|
8790
10205
|
identityFile: target.identityFile
|
|
8791
10206
|
});
|
|
8792
|
-
if (!
|
|
8793
|
-
|
|
10207
|
+
if (!existsSync7(mountRoot)) {
|
|
10208
|
+
mkdirSync6(mountRoot, { recursive: true, mode: 493 });
|
|
8794
10209
|
} else if (await isMounted(mountRoot)) {
|
|
8795
|
-
|
|
10210
|
+
log33.info(`re-mounting (stale mount detected at ${mountRoot})`);
|
|
8796
10211
|
await tryUnmount(mountRoot);
|
|
8797
10212
|
}
|
|
8798
|
-
|
|
8799
|
-
const mount = await
|
|
10213
|
+
log33.info(`mounting ${alias}:/workspace at ${mountRoot}`);
|
|
10214
|
+
const mount = await execa3(
|
|
8800
10215
|
sshfsBin,
|
|
8801
10216
|
[
|
|
8802
10217
|
`${alias}:/workspace`,
|
|
@@ -8815,35 +10230,35 @@ async function runCloudOpen(box, provider, opts) {
|
|
|
8815
10230
|
if (mount.exitCode !== 0) {
|
|
8816
10231
|
throw new Error(`sshfs mount failed (exit ${String(mount.exitCode)}): ${mount.stderr || mount.stdout}`);
|
|
8817
10232
|
}
|
|
8818
|
-
await
|
|
10233
|
+
await execa3("open", [mountRoot], { reject: false });
|
|
8819
10234
|
process.stdout.write(`opened ${mountRoot}
|
|
8820
10235
|
`);
|
|
8821
10236
|
process.stdout.write(`unmount later with: agentbox open ${box.name} --unmount
|
|
8822
10237
|
`);
|
|
8823
10238
|
}
|
|
8824
10239
|
async function locateBinary(name) {
|
|
8825
|
-
const r = await
|
|
10240
|
+
const r = await execa3("which", [name], { reject: false });
|
|
8826
10241
|
if (r.exitCode !== 0) return null;
|
|
8827
10242
|
const path = (r.stdout ?? "").trim();
|
|
8828
10243
|
return path.length > 0 ? path : null;
|
|
8829
10244
|
}
|
|
8830
10245
|
async function isMounted(path) {
|
|
8831
|
-
const r = await
|
|
10246
|
+
const r = await execa3("sh", ["-c", `mount | grep -F " on ${path} "`], { reject: false });
|
|
8832
10247
|
return r.exitCode === 0;
|
|
8833
10248
|
}
|
|
8834
10249
|
async function tryUnmount(path) {
|
|
8835
10250
|
if (await isMounted(path)) {
|
|
8836
|
-
const u = await
|
|
10251
|
+
const u = await execa3("umount", [path], { reject: false });
|
|
8837
10252
|
if (u.exitCode === 0) return true;
|
|
8838
|
-
const d = await
|
|
10253
|
+
const d = await execa3("diskutil", ["unmount", path], { reject: false });
|
|
8839
10254
|
return d.exitCode === 0;
|
|
8840
10255
|
}
|
|
8841
10256
|
return false;
|
|
8842
10257
|
}
|
|
8843
10258
|
|
|
8844
10259
|
// src/commands/pause.ts
|
|
8845
|
-
import { Command as
|
|
8846
|
-
var pauseCommand = new
|
|
10260
|
+
import { Command as Command31 } from "commander";
|
|
10261
|
+
var pauseCommand = new Command31("pause").description(
|
|
8847
10262
|
"Pause a box. Docker: `docker pause` (cgroup freeze \u2014 sub-second resume). Cloud: backend.pause (Daytona archive \u2014 cold storage; resume is slower but uses no quota while archived)."
|
|
8848
10263
|
).argument(
|
|
8849
10264
|
"[box]",
|
|
@@ -8865,220 +10280,9 @@ var pauseCommand = new Command29("pause").description(
|
|
|
8865
10280
|
}
|
|
8866
10281
|
});
|
|
8867
10282
|
|
|
8868
|
-
// src/commands/prepare.ts
|
|
8869
|
-
import { intro as intro6, log as log33, spinner as spinner7 } from "@clack/prompts";
|
|
8870
|
-
import { Command as Command30 } from "commander";
|
|
8871
|
-
async function dockerStatus() {
|
|
8872
|
-
let img;
|
|
8873
|
-
try {
|
|
8874
|
-
img = await imageInfo(DEFAULT_BOX_IMAGE);
|
|
8875
|
-
} catch {
|
|
8876
|
-
return { daemon: "unreachable", volumes: [] };
|
|
8877
|
-
}
|
|
8878
|
-
const names = [SHARED_CLAUDE_VOLUME, SHARED_CODEX_VOLUME, SHARED_OPENCODE_VOLUME];
|
|
8879
|
-
const volumes = await Promise.all(
|
|
8880
|
-
names.map(async (name) => ({ name, exists: await volumeExists(name).catch(() => false) }))
|
|
8881
|
-
);
|
|
8882
|
-
return { daemon: "reachable", image: img, volumes };
|
|
8883
|
-
}
|
|
8884
|
-
function humanBytes(n) {
|
|
8885
|
-
if (n === void 0 || !Number.isFinite(n)) return "\u2014";
|
|
8886
|
-
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
8887
|
-
if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
8888
|
-
if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
8889
|
-
return `${String(n)} B`;
|
|
8890
|
-
}
|
|
8891
|
-
function humanAge(iso) {
|
|
8892
|
-
if (!iso) return "\u2014";
|
|
8893
|
-
const t = Date.parse(iso);
|
|
8894
|
-
if (!Number.isFinite(t)) return iso;
|
|
8895
|
-
const ageSec = Math.max(0, (Date.now() - t) / 1e3);
|
|
8896
|
-
if (ageSec < 60) return `${ageSec.toFixed(0)}s ago`;
|
|
8897
|
-
if (ageSec < 3600) return `${(ageSec / 60).toFixed(0)}m ago`;
|
|
8898
|
-
if (ageSec < 86400) return `${(ageSec / 3600).toFixed(1)}h ago`;
|
|
8899
|
-
return `${(ageSec / 86400).toFixed(1)}d ago`;
|
|
8900
|
-
}
|
|
8901
|
-
function pad2(s, width) {
|
|
8902
|
-
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
8903
|
-
}
|
|
8904
|
-
async function renderDocker(status) {
|
|
8905
|
-
const out = ["docker:"];
|
|
8906
|
-
if (status.daemon === "unreachable") {
|
|
8907
|
-
out.push(" docker daemon unreachable (is Docker running?)");
|
|
8908
|
-
return out;
|
|
8909
|
-
}
|
|
8910
|
-
if (!status.image?.exists) {
|
|
8911
|
-
out.push(` image ${DEFAULT_BOX_IMAGE} (not built \u2014 run \`agentbox prepare --provider docker\`)`);
|
|
8912
|
-
} else {
|
|
8913
|
-
out.push(
|
|
8914
|
-
` image ${pad2(DEFAULT_BOX_IMAGE, 30)} ${pad2(humanBytes(status.image.sizeBytes), 10)} built ${humanAge(status.image.createdAt)}`
|
|
8915
|
-
);
|
|
8916
|
-
}
|
|
8917
|
-
for (const v of status.volumes) {
|
|
8918
|
-
if (v.exists) {
|
|
8919
|
-
out.push(` vol ${pad2(v.name, 30)} present`);
|
|
8920
|
-
} else {
|
|
8921
|
-
out.push(` vol ${pad2(v.name, 30)} (none \u2014 seeded lazily on first \`agentbox claude/codex/opencode\`)`);
|
|
8922
|
-
}
|
|
8923
|
-
}
|
|
8924
|
-
return out;
|
|
8925
|
-
}
|
|
8926
|
-
async function daytonaStatus() {
|
|
8927
|
-
try {
|
|
8928
|
-
const mod = await import("./dist-CX5CGVEB.js");
|
|
8929
|
-
return await mod.getDaytonaStatus();
|
|
8930
|
-
} catch (err) {
|
|
8931
|
-
return { configured: false, reason: err instanceof Error ? err.message.split("\n")[0] : String(err) };
|
|
8932
|
-
}
|
|
8933
|
-
}
|
|
8934
|
-
function renderDaytona(status, pinnedImage) {
|
|
8935
|
-
const out = ["daytona:"];
|
|
8936
|
-
if (!status.configured) {
|
|
8937
|
-
out.push(
|
|
8938
|
-
` (not configured \u2014 \`agentbox daytona login\` to set up${status.reason ? `; ${status.reason}` : ""})`
|
|
8939
|
-
);
|
|
8940
|
-
return out;
|
|
8941
|
-
}
|
|
8942
|
-
if (status.reason) out.push(` warn: ${status.reason}`);
|
|
8943
|
-
if (status.snapshots.length === 0) {
|
|
8944
|
-
out.push(" no agentbox snapshots \u2014 run `agentbox prepare --provider daytona`");
|
|
8945
|
-
} else {
|
|
8946
|
-
for (const s of status.snapshots) {
|
|
8947
|
-
const sizeStr = s.sizeGb !== void 0 ? `${s.sizeGb.toFixed(2)} GB` : "\u2014";
|
|
8948
|
-
const pinned = pinnedImage && pinnedImage === s.name ? " (pinned in project)" : "";
|
|
8949
|
-
const tail = s.state === "error" && s.errorReason ? ` error: ${s.errorReason.slice(0, 80)}` : ` ${humanAge(s.createdAt)}`;
|
|
8950
|
-
out.push(
|
|
8951
|
-
` snap ${pad2(s.name, 40)} ${pad2(s.state ?? "\u2014", 10)} ${pad2(sizeStr, 10)}${tail}${pinned}`
|
|
8952
|
-
);
|
|
8953
|
-
}
|
|
8954
|
-
}
|
|
8955
|
-
if (status.volumes.length === 0) {
|
|
8956
|
-
out.push(" no agentbox volumes \u2014 created lazily on first cloud `agentbox create`");
|
|
8957
|
-
} else {
|
|
8958
|
-
for (const v of status.volumes) {
|
|
8959
|
-
const last = v.lastUsedAt ? ` last used ${humanAge(v.lastUsedAt)}` : "";
|
|
8960
|
-
out.push(` vol ${pad2(v.name, 40)} ${pad2(v.state ?? "\u2014", 10)}${last}`);
|
|
8961
|
-
}
|
|
8962
|
-
}
|
|
8963
|
-
return out;
|
|
8964
|
-
}
|
|
8965
|
-
async function showStatus(opts) {
|
|
8966
|
-
const cfg = await loadEffectiveConfig(process.cwd()).catch(() => null);
|
|
8967
|
-
const pinnedRaw = cfg?.effective.box.image;
|
|
8968
|
-
const pinned = typeof pinnedRaw === "string" && pinnedRaw.length > 0 && pinnedRaw !== DEFAULT_BOX_IMAGE ? pinnedRaw : void 0;
|
|
8969
|
-
const lines = [];
|
|
8970
|
-
const wantDocker = !opts.onlyProvider || opts.onlyProvider === "docker";
|
|
8971
|
-
const wantDaytona = !opts.onlyProvider || opts.onlyProvider === "daytona";
|
|
8972
|
-
if (wantDocker) {
|
|
8973
|
-
const status = await dockerStatus();
|
|
8974
|
-
lines.push(...await renderDocker(status));
|
|
8975
|
-
}
|
|
8976
|
-
if (wantDaytona) {
|
|
8977
|
-
if (lines.length > 0) lines.push("");
|
|
8978
|
-
const status = await daytonaStatus();
|
|
8979
|
-
lines.push(...renderDaytona(status, pinned));
|
|
8980
|
-
}
|
|
8981
|
-
if (pinned) {
|
|
8982
|
-
lines.push("");
|
|
8983
|
-
lines.push(`project pin: box.image = ${pinned}`);
|
|
8984
|
-
}
|
|
8985
|
-
process.stdout.write(lines.join("\n") + "\n");
|
|
8986
|
-
}
|
|
8987
|
-
var prepareCommand = new Command30("prepare").description(
|
|
8988
|
-
"Build base sandbox images / snapshots, or show what is already prepared across providers."
|
|
8989
|
-
).option(
|
|
8990
|
-
"-p, --provider <name>",
|
|
8991
|
-
"provider to prepare (docker | daytona | hetzner | vercel). Omit for status-only."
|
|
8992
|
-
).option(
|
|
8993
|
-
"-n, --name <name>",
|
|
8994
|
-
"snapshot name (Daytona only; default: agentbox-base-<timestamp>)"
|
|
8995
|
-
).option("-f, --force", "rebuild even if the image / snapshot already exists").option("-y, --yes", "skip confirmation prompts (cost / time warnings)").option("--status", "show status without preparing anything").action(async (opts) => {
|
|
8996
|
-
if (!opts.provider || opts.status) {
|
|
8997
|
-
await showStatus({});
|
|
8998
|
-
return;
|
|
8999
|
-
}
|
|
9000
|
-
const providerName = opts.provider.trim();
|
|
9001
|
-
if (!isKnownProvider(providerName)) {
|
|
9002
|
-
process.stderr.write(
|
|
9003
|
-
`error: --provider must be one of: docker, daytona, hetzner
|
|
9004
|
-
`
|
|
9005
|
-
);
|
|
9006
|
-
process.exit(1);
|
|
9007
|
-
}
|
|
9008
|
-
intro6(`preparing ${providerName} base image`);
|
|
9009
|
-
if (providerName === "daytona" && !opts.yes && process.stdin.isTTY) {
|
|
9010
|
-
process.stdout.write(
|
|
9011
|
-
"This will trigger a Daytona image build (~7 min cold, ~seconds with cache) and register a named snapshot in your org.\nRe-run with --yes to skip this notice.\n"
|
|
9012
|
-
);
|
|
9013
|
-
}
|
|
9014
|
-
const provider = await getProvider(providerName);
|
|
9015
|
-
if (typeof provider.prepare !== "function") {
|
|
9016
|
-
log33.error(`provider '${providerName}' does not implement prepare`);
|
|
9017
|
-
process.exit(1);
|
|
9018
|
-
}
|
|
9019
|
-
const sp = spinner7();
|
|
9020
|
-
sp.start(`preparing ${providerName}\u2026`);
|
|
9021
|
-
try {
|
|
9022
|
-
const result = await provider.prepare({
|
|
9023
|
-
name: opts.name,
|
|
9024
|
-
hostWorkspace: process.cwd(),
|
|
9025
|
-
force: opts.force,
|
|
9026
|
-
onLog: (line) => sp.message(line.slice(0, 80))
|
|
9027
|
-
});
|
|
9028
|
-
if (result.snapshotName !== void 0) {
|
|
9029
|
-
sp.stop(`prepared ${providerName}: snapshot '${result.snapshotName}'`);
|
|
9030
|
-
try {
|
|
9031
|
-
const written = await setConfigValue(
|
|
9032
|
-
"project",
|
|
9033
|
-
"box.image",
|
|
9034
|
-
result.snapshotName,
|
|
9035
|
-
process.cwd()
|
|
9036
|
-
);
|
|
9037
|
-
log33.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
|
|
9038
|
-
} catch (err) {
|
|
9039
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
9040
|
-
log33.warn(
|
|
9041
|
-
`prepared snapshot '${result.snapshotName}', but failed to pin it into the project config: ${msg}
|
|
9042
|
-
Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.`
|
|
9043
|
-
);
|
|
9044
|
-
}
|
|
9045
|
-
} else {
|
|
9046
|
-
sp.stop(`prepared ${providerName}`);
|
|
9047
|
-
}
|
|
9048
|
-
process.stdout.write("\n");
|
|
9049
|
-
await showStatus({ onlyProvider: providerName });
|
|
9050
|
-
log33.info(
|
|
9051
|
-
"tip: install the agentbox host skill so Claude Code on this machine can drive AgentBox for you:\n npx skills add https://github.com/madarco/agentbox --skill agentbox"
|
|
9052
|
-
);
|
|
9053
|
-
} catch (err) {
|
|
9054
|
-
sp.stop(`prepare failed: ${describeError(err)}`);
|
|
9055
|
-
process.exit(1);
|
|
9056
|
-
}
|
|
9057
|
-
});
|
|
9058
|
-
function describeError(err) {
|
|
9059
|
-
if (!(err instanceof Error)) return String(err);
|
|
9060
|
-
const parts = [err.message];
|
|
9061
|
-
let cause = err.cause;
|
|
9062
|
-
for (let i = 0; i < 5 && cause; i++) {
|
|
9063
|
-
if (cause instanceof Error) {
|
|
9064
|
-
parts.push(`caused by: ${cause.message}`);
|
|
9065
|
-
const code = cause.code;
|
|
9066
|
-
if (typeof code === "string") parts.push(`(${code})`);
|
|
9067
|
-
cause = cause.cause;
|
|
9068
|
-
} else if (typeof cause === "object") {
|
|
9069
|
-
parts.push(`caused by: ${JSON.stringify(cause)}`);
|
|
9070
|
-
break;
|
|
9071
|
-
} else {
|
|
9072
|
-
parts.push(`caused by: ${String(cause)}`);
|
|
9073
|
-
break;
|
|
9074
|
-
}
|
|
9075
|
-
}
|
|
9076
|
-
return parts.join(" \u2014 ");
|
|
9077
|
-
}
|
|
9078
|
-
|
|
9079
10283
|
// src/commands/prune.ts
|
|
9080
|
-
import { confirm as
|
|
9081
|
-
import { Command as
|
|
10284
|
+
import { confirm as confirm15, isCancel as isCancel16, log as log34 } from "@clack/prompts";
|
|
10285
|
+
import { Command as Command32 } from "commander";
|
|
9082
10286
|
function totalRemovals(r, projectConfigs) {
|
|
9083
10287
|
return r.removedRecords.length + r.removedContainers.length + r.removedVolumes.length + r.removedSnapshotDirs.length + r.removedBoxDirs.length + projectConfigs.length;
|
|
9084
10288
|
}
|
|
@@ -9124,7 +10328,7 @@ async function liveProjectRoots() {
|
|
|
9124
10328
|
return [];
|
|
9125
10329
|
}
|
|
9126
10330
|
}
|
|
9127
|
-
var pruneCommand = new
|
|
10331
|
+
var pruneCommand = new Command32("prune").description("Clean up orphan state.json records (and with --all, orphan docker resources)").option("--dry-run", "show what would be removed, don't change anything").option(
|
|
9128
10332
|
"--all",
|
|
9129
10333
|
"also remove orphan agentbox-* containers, volumes, snapshot dirs, and orphan per-project config dirs"
|
|
9130
10334
|
).option("-y, --yes", "skip the confirmation prompt").option(
|
|
@@ -9154,8 +10358,8 @@ var pruneCommand = new Command31("prune").description("Clean up orphan state.jso
|
|
|
9154
10358
|
${summary(preview, previewProjects)}`);
|
|
9155
10359
|
if (dryRun) return;
|
|
9156
10360
|
if (!opts.yes) {
|
|
9157
|
-
const ok = await
|
|
9158
|
-
if (
|
|
10361
|
+
const ok = await confirm15({ message: "Proceed with prune?", initialValue: true });
|
|
10362
|
+
if (isCancel16(ok) || !ok) {
|
|
9159
10363
|
log34.info("cancelled");
|
|
9160
10364
|
return;
|
|
9161
10365
|
}
|
|
@@ -9173,19 +10377,9 @@ var CLOUD_PRUNE_PROVIDERS = ["daytona", "hetzner", "vercel"];
|
|
|
9173
10377
|
function isCloudPruneProvider(name) {
|
|
9174
10378
|
return CLOUD_PRUNE_PROVIDERS.includes(name);
|
|
9175
10379
|
}
|
|
9176
|
-
async function cloudBackendFor(provider) {
|
|
9177
|
-
switch (provider) {
|
|
9178
|
-
case "daytona":
|
|
9179
|
-
return (await import("./dist-CX5CGVEB.js")).daytonaBackend;
|
|
9180
|
-
case "hetzner":
|
|
9181
|
-
return (await import("./dist-GDHP34ZK.js")).hetznerBackend;
|
|
9182
|
-
case "vercel":
|
|
9183
|
-
return (await import("./dist-XML54CNB.js")).vercelBackend;
|
|
9184
|
-
}
|
|
9185
|
-
}
|
|
9186
10380
|
async function pruneCloud(provider, opts) {
|
|
9187
10381
|
const dryRun = opts.dryRun ?? false;
|
|
9188
|
-
const backend = await
|
|
10382
|
+
const backend = await cloudBackendForProvider(provider);
|
|
9189
10383
|
if (!backend.list) {
|
|
9190
10384
|
log34.error(`${provider} backend doesn't expose \`list()\`; cannot enumerate sandboxes for prune`);
|
|
9191
10385
|
process.exit(2);
|
|
@@ -9218,11 +10412,11 @@ async function pruneCloud(provider, opts) {
|
|
|
9218
10412
|
}
|
|
9219
10413
|
if (dryRun) return;
|
|
9220
10414
|
if (!opts.yes) {
|
|
9221
|
-
const ok = await
|
|
10415
|
+
const ok = await confirm15({
|
|
9222
10416
|
message: `Delete ${String(orphans.length)} orphan sandbox(es)?`,
|
|
9223
10417
|
initialValue: false
|
|
9224
10418
|
});
|
|
9225
|
-
if (
|
|
10419
|
+
if (isCancel16(ok) || !ok) {
|
|
9226
10420
|
log34.info("cancelled");
|
|
9227
10421
|
return;
|
|
9228
10422
|
}
|
|
@@ -9249,10 +10443,10 @@ async function pruneCloud(provider, opts) {
|
|
|
9249
10443
|
// src/commands/queue.ts
|
|
9250
10444
|
import { readFile as readFile4, stat as stat5 } from "fs/promises";
|
|
9251
10445
|
import { intro as intro7, log as log35, outro as outro7 } from "@clack/prompts";
|
|
9252
|
-
import { Command as
|
|
10446
|
+
import { Command as Command33 } from "commander";
|
|
9253
10447
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
9254
|
-
var queueCommand = new
|
|
9255
|
-
var queueListCommand = new
|
|
10448
|
+
var queueCommand = new Command33("queue").description("Inspect and manage background `agentbox claude|codex|opencode -i` jobs");
|
|
10449
|
+
var queueListCommand = new Command33("list").description("List queued, running, and (with --all) terminal background jobs").option("--all", "include done/failed/cancelled jobs (default: hide terminal)").action(async (opts) => {
|
|
9256
10450
|
const jobs = await loadQueue();
|
|
9257
10451
|
const cfg = await loadQueueConfig();
|
|
9258
10452
|
const visible = opts.all === true ? jobs : jobs.filter((j) => !TERMINAL_STATUSES.has(j.status));
|
|
@@ -9275,17 +10469,17 @@ var queueListCommand = new Command32("list").description("List queued, running,
|
|
|
9275
10469
|
const widths = headers.map(
|
|
9276
10470
|
(h) => Math.max(h.length, ...rows.map((r) => String(r[h]).length))
|
|
9277
10471
|
);
|
|
9278
|
-
const
|
|
9279
|
-
process.stdout.write(headers.map((h, i) =>
|
|
10472
|
+
const pad4 = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
10473
|
+
process.stdout.write(headers.map((h, i) => pad4(h, widths[i])).join(" ") + "\n");
|
|
9280
10474
|
process.stdout.write(widths.map((w) => "-".repeat(w)).join(" ") + "\n");
|
|
9281
10475
|
for (const r of rows) {
|
|
9282
10476
|
process.stdout.write(
|
|
9283
|
-
headers.map((h, i) =>
|
|
10477
|
+
headers.map((h, i) => pad4(String(r[h]), widths[i])).join(" ") + "\n"
|
|
9284
10478
|
);
|
|
9285
10479
|
}
|
|
9286
10480
|
log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
|
|
9287
10481
|
});
|
|
9288
|
-
var queueShowCommand = new
|
|
10482
|
+
var queueShowCommand = new Command33("show").description("Dump a job manifest and tail its log").argument("<id>", "queue job id (from `agentbox queue list`)").option("--tail <n>", "lines of log to print (default: 50)", "50").action(async (id, opts) => {
|
|
9289
10483
|
const job = await readJob(id);
|
|
9290
10484
|
if (!job) {
|
|
9291
10485
|
log35.error(`no job with id ${id}`);
|
|
@@ -9307,7 +10501,7 @@ var queueShowCommand = new Command32("show").description("Dump a job manifest an
|
|
|
9307
10501
|
log35.info(`(no log at ${job.logPath} yet)`);
|
|
9308
10502
|
}
|
|
9309
10503
|
});
|
|
9310
|
-
var queueCancelCommand = new
|
|
10504
|
+
var queueCancelCommand = new Command33("cancel").description("Cancel a queued job; running jobs are NOT killed \u2014 use `agentbox destroy` instead").argument("<id>", "queue job id (from `agentbox queue list`)").action(async (id) => {
|
|
9311
10505
|
intro7(`Cancelling queue job ${id}...`);
|
|
9312
10506
|
const job = await readJob(id);
|
|
9313
10507
|
if (!job) {
|
|
@@ -9329,7 +10523,7 @@ var queueCancelCommand = new Command32("cancel").description("Cancel a queued jo
|
|
|
9329
10523
|
await writeJob(cancelled);
|
|
9330
10524
|
outro7(`job ${id} cancelled`);
|
|
9331
10525
|
});
|
|
9332
|
-
var queueClearCommand = new
|
|
10526
|
+
var queueClearCommand = new Command33("clear").description("Sweep terminal-state manifests from ~/.agentbox/queue/").option("--done", "remove done jobs").option("--failed", "remove failed jobs").option("--cancelled", "remove cancelled jobs").option("--all", "remove every terminal-state job (done + failed + cancelled)").action(async (opts) => {
|
|
9333
10527
|
const targets = /* @__PURE__ */ new Set();
|
|
9334
10528
|
if (opts.all === true || opts.done === true) targets.add("done");
|
|
9335
10529
|
if (opts.all === true || opts.failed === true) targets.add("failed");
|
|
@@ -9358,7 +10552,7 @@ var QUEUE_WAIT_EVENTS = [
|
|
|
9358
10552
|
var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
9359
10553
|
var DEFAULT_QUEUE_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9360
10554
|
var QUEUE_POLL_INTERVAL_MS = 500;
|
|
9361
|
-
var queueWaitForCommand = new
|
|
10555
|
+
var queueWaitForCommand = new Command33("wait-for").description(
|
|
9362
10556
|
`Block until a queue / box event fires. <event> one of: ${QUEUE_WAIT_EVENTS.join(" | ")}.`
|
|
9363
10557
|
).argument("<event>", `target event: ${QUEUE_WAIT_EVENTS.join(" | ")}`).option("--box <ref>", "box ref (required for box-paused / box-running / box-stopped)").option("--job <id>", "queue job id (required for job-done)").option("--timeout <ms>", `wall-clock cap (default: ${String(DEFAULT_QUEUE_WAIT_TIMEOUT_MS)})`).option("--json", "emit a JSON envelope { matched, elapsedMs, ... }").action(async (eventRaw, opts) => {
|
|
9364
10558
|
if (!QUEUE_WAIT_EVENTS.includes(eventRaw)) {
|
|
@@ -9446,11 +10640,11 @@ async function pollUntil(deadline, probe) {
|
|
|
9446
10640
|
if (result !== void 0) return result;
|
|
9447
10641
|
const remaining = deadline - Date.now();
|
|
9448
10642
|
if (remaining <= 0) break;
|
|
9449
|
-
await
|
|
10643
|
+
await sleep4(Math.min(QUEUE_POLL_INTERVAL_MS, remaining));
|
|
9450
10644
|
}
|
|
9451
10645
|
throw new QueueWaitTimeout();
|
|
9452
10646
|
}
|
|
9453
|
-
function
|
|
10647
|
+
function sleep4(ms) {
|
|
9454
10648
|
return new Promise((r) => setTimeout(r, ms));
|
|
9455
10649
|
}
|
|
9456
10650
|
function parsePositiveInt3(raw, label) {
|
|
@@ -9483,8 +10677,8 @@ function truncate(s, max) {
|
|
|
9483
10677
|
}
|
|
9484
10678
|
|
|
9485
10679
|
// src/commands/relay.ts
|
|
9486
|
-
import { log as log36, spinner as
|
|
9487
|
-
import { Command as
|
|
10680
|
+
import { log as log36, spinner as spinner9 } from "@clack/prompts";
|
|
10681
|
+
import { Command as Command34 } from "commander";
|
|
9488
10682
|
async function rehydrateFromState() {
|
|
9489
10683
|
const state = await readState();
|
|
9490
10684
|
await rehydrateRelayRegistry(
|
|
@@ -9508,12 +10702,14 @@ function renderStatus(s) {
|
|
|
9508
10702
|
if (s.running && s.health) {
|
|
9509
10703
|
return [
|
|
9510
10704
|
"relay: running",
|
|
9511
|
-
` pid:
|
|
9512
|
-
` port:
|
|
9513
|
-
` url:
|
|
9514
|
-
`
|
|
9515
|
-
`
|
|
9516
|
-
`
|
|
10705
|
+
` pid: ${s.pid === null ? "?" : String(s.pid)}`,
|
|
10706
|
+
` port: ${String(s.port)}`,
|
|
10707
|
+
` url: ${s.endpoint.hostUrl}`,
|
|
10708
|
+
` version: ${s.health.version ?? "(unknown \u2014 relay predates version field)"}`,
|
|
10709
|
+
` commit: ${s.health.commit ?? "(unknown)"}`,
|
|
10710
|
+
` boxes: ${String(s.health.boxes)}`,
|
|
10711
|
+
` events: ${String(s.health.events)}`,
|
|
10712
|
+
` log: ${s.logFile}`
|
|
9517
10713
|
].join("\n");
|
|
9518
10714
|
}
|
|
9519
10715
|
if (s.pidAlive) {
|
|
@@ -9524,7 +10720,7 @@ function renderStatus(s) {
|
|
|
9524
10720
|
}
|
|
9525
10721
|
return ["relay: not running", ` log: ${s.logFile}`].join("\n");
|
|
9526
10722
|
}
|
|
9527
|
-
var statusSub = new
|
|
10723
|
+
var statusSub = new Command34("status").description("Show whether the host relay is running, with pid / port / box count").option("--json", "emit RelayStatus as JSON").action(async (opts) => {
|
|
9528
10724
|
try {
|
|
9529
10725
|
const s = await getRelayStatus();
|
|
9530
10726
|
if (opts.json) {
|
|
@@ -9536,9 +10732,9 @@ var statusSub = new Command33("status").description("Show whether the host relay
|
|
|
9536
10732
|
handleLifecycleError(err);
|
|
9537
10733
|
}
|
|
9538
10734
|
});
|
|
9539
|
-
var stopSub = new
|
|
10735
|
+
var stopSub = new Command34("stop").description("Stop the host relay process (idempotent)").action(async () => {
|
|
9540
10736
|
try {
|
|
9541
|
-
const s =
|
|
10737
|
+
const s = spinner9();
|
|
9542
10738
|
s.start("stopping relay");
|
|
9543
10739
|
const result = await stopRelay();
|
|
9544
10740
|
s.stop(
|
|
@@ -9548,9 +10744,9 @@ var stopSub = new Command33("stop").description("Stop the host relay process (id
|
|
|
9548
10744
|
handleLifecycleError(err);
|
|
9549
10745
|
}
|
|
9550
10746
|
});
|
|
9551
|
-
var startSub = new
|
|
10747
|
+
var startSub = new Command34("start").description("Start the host relay if not already running (idempotent)").action(async () => {
|
|
9552
10748
|
try {
|
|
9553
|
-
const s =
|
|
10749
|
+
const s = spinner9();
|
|
9554
10750
|
s.start("starting relay");
|
|
9555
10751
|
const ep = await ensureRelay();
|
|
9556
10752
|
await rehydrateFromState();
|
|
@@ -9559,15 +10755,15 @@ var startSub = new Command33("start").description("Start the host relay if not a
|
|
|
9559
10755
|
handleLifecycleError(err);
|
|
9560
10756
|
}
|
|
9561
10757
|
});
|
|
9562
|
-
var restartSub = new
|
|
10758
|
+
var restartSub = new Command34("restart").description("Stop then start the host relay").action(async () => {
|
|
9563
10759
|
try {
|
|
9564
|
-
const s =
|
|
10760
|
+
const s = spinner9();
|
|
9565
10761
|
s.start("stopping relay");
|
|
9566
10762
|
const stopped = await stopRelay();
|
|
9567
10763
|
s.stop(
|
|
9568
10764
|
stopped.stopped ? `stopped relay (pid ${String(stopped.pid)})` : "relay was not running"
|
|
9569
10765
|
);
|
|
9570
|
-
const s2 =
|
|
10766
|
+
const s2 = spinner9();
|
|
9571
10767
|
s2.start("starting relay");
|
|
9572
10768
|
try {
|
|
9573
10769
|
const ep = await ensureRelay();
|
|
@@ -9582,11 +10778,11 @@ var restartSub = new Command33("restart").description("Stop then start the host
|
|
|
9582
10778
|
handleLifecycleError(err);
|
|
9583
10779
|
}
|
|
9584
10780
|
});
|
|
9585
|
-
var relayCommand = new
|
|
10781
|
+
var relayCommand = new Command34("relay").description("Manage the host relay process (status / stop / start / restart)").addCommand(statusSub, { isDefault: true }).addCommand(stopSub).addCommand(startSub).addCommand(restartSub);
|
|
9586
10782
|
|
|
9587
10783
|
// src/commands/_run-queued-job.ts
|
|
9588
|
-
import { Command as
|
|
9589
|
-
var runQueuedJobCommand = new
|
|
10784
|
+
import { Command as Command35 } from "commander";
|
|
10785
|
+
var runQueuedJobCommand = new Command35("_run-queued-job").description("internal: run a queued background agent job (do not invoke directly)").argument("<id>", "queue job id (from ~/.agentbox/queue/<id>.json)").action(async (id) => {
|
|
9590
10786
|
const log45 = openCommandLog(`queue-${id}`);
|
|
9591
10787
|
log45.write(`worker pid=${String(process.pid)} starting for job ${id}`);
|
|
9592
10788
|
let job = null;
|
|
@@ -9682,7 +10878,7 @@ async function runDockerJob(job, log45, onBoxCreated) {
|
|
|
9682
10878
|
log45.write(`starting claude session`);
|
|
9683
10879
|
await startClaudeSession({
|
|
9684
10880
|
container: result.record.container,
|
|
9685
|
-
claudeArgs: promptedArgs,
|
|
10881
|
+
claudeArgs: applyClaudeSkipPermissions(promptedArgs, cfg.effective),
|
|
9686
10882
|
sessionName: cfg.effective.claude.sessionName,
|
|
9687
10883
|
boxName: result.record.name
|
|
9688
10884
|
});
|
|
@@ -9694,7 +10890,7 @@ async function runDockerJob(job, log45, onBoxCreated) {
|
|
|
9694
10890
|
log45.write(`starting codex session`);
|
|
9695
10891
|
await startCodexSession({
|
|
9696
10892
|
container: result.record.container,
|
|
9697
|
-
codexArgs: promptedArgs,
|
|
10893
|
+
codexArgs: applyCodexSkipPermissions(promptedArgs, cfg.effective),
|
|
9698
10894
|
sessionName: cfg.effective.codex.sessionName
|
|
9699
10895
|
});
|
|
9700
10896
|
} else if (job.agent === "opencode") {
|
|
@@ -9729,13 +10925,20 @@ function buildOverridesFromJob(job) {
|
|
|
9729
10925
|
else if (job.agent === "codex") out.codex = { sessionName: opts.sessionName };
|
|
9730
10926
|
else if (job.agent === "opencode") out.opencode = { sessionName: opts.sessionName };
|
|
9731
10927
|
}
|
|
10928
|
+
if (opts.dangerouslySkipPermissions !== void 0) {
|
|
10929
|
+
if (job.agent === "claude-code") {
|
|
10930
|
+
out.claude = { ...out.claude, dangerouslySkipPermissions: opts.dangerouslySkipPermissions };
|
|
10931
|
+
} else if (job.agent === "codex") {
|
|
10932
|
+
out.codex = { ...out.codex, dangerouslySkipPermissions: opts.dangerouslySkipPermissions };
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
9732
10935
|
return out;
|
|
9733
10936
|
}
|
|
9734
10937
|
|
|
9735
10938
|
// src/commands/screen.ts
|
|
9736
10939
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9737
10940
|
import { log as log37 } from "@clack/prompts";
|
|
9738
|
-
import { Command as
|
|
10941
|
+
import { Command as Command36 } from "commander";
|
|
9739
10942
|
var SIGNED_URL_TTL_MIN = 1;
|
|
9740
10943
|
var SIGNED_URL_TTL_MAX = 86400;
|
|
9741
10944
|
function parseTtlOrExit(raw) {
|
|
@@ -9748,7 +10951,7 @@ function parseTtlOrExit(raw) {
|
|
|
9748
10951
|
}
|
|
9749
10952
|
return n;
|
|
9750
10953
|
}
|
|
9751
|
-
var screenCommand = new
|
|
10954
|
+
var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC) viewer in the browser (auto-unpause/start)").argument(
|
|
9752
10955
|
"[box]",
|
|
9753
10956
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
9754
10957
|
).option("--print", "print the URL to stdout instead of launching the browser").option("--loopback", "docker only: use the 127.0.0.1 URL instead of the OrbStack .orb.local URL").option(
|
|
@@ -9813,6 +11016,28 @@ var screenCommand = new Command35("screen").description("Open a box's VNC (noVNC
|
|
|
9813
11016
|
} else if (state === "missing") {
|
|
9814
11017
|
throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
|
|
9815
11018
|
}
|
|
11019
|
+
const persisted = await readBoxStatus(box);
|
|
11020
|
+
const hasWebService = persisted?.services.some((s) => s.expose) ?? false;
|
|
11021
|
+
if (hasWebService) {
|
|
11022
|
+
try {
|
|
11023
|
+
const webUrl = await p.resolveUrl(box, { kind: "web" });
|
|
11024
|
+
const q = `'${webUrl.replace(/'/g, "'\\''")}'`;
|
|
11025
|
+
const br = await p.exec(box, ["bash", "-lc", `agent-browser open --headed ${q}`], {
|
|
11026
|
+
user: "vscode"
|
|
11027
|
+
});
|
|
11028
|
+
if (br.exitCode === 0) {
|
|
11029
|
+
log37.info(`opened ${webUrl} in the in-box browser (visible in the VNC view)`);
|
|
11030
|
+
} else {
|
|
11031
|
+
log37.warn(
|
|
11032
|
+
`could not open in-box browser (continuing): ${br.stderr.trim() || br.stdout.trim() || `exit ${String(br.exitCode)}`}`
|
|
11033
|
+
);
|
|
11034
|
+
}
|
|
11035
|
+
} catch (err) {
|
|
11036
|
+
log37.warn(
|
|
11037
|
+
`in-box browser skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
11038
|
+
);
|
|
11039
|
+
}
|
|
11040
|
+
}
|
|
9816
11041
|
const base = await p.resolveUrl(box, { kind: "vnc", ttl });
|
|
9817
11042
|
url = `${base.replace(/\/$/, "")}/vnc.html?autoconnect=1&password=${encodeURIComponent(box.vncPassword)}`;
|
|
9818
11043
|
}
|
|
@@ -9835,7 +11060,7 @@ var screenCommand = new Command35("screen").description("Open a box's VNC (noVNC
|
|
|
9835
11060
|
// src/commands/shell.ts
|
|
9836
11061
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9837
11062
|
import { log as log39 } from "@clack/prompts";
|
|
9838
|
-
import { Command as
|
|
11063
|
+
import { Command as Command37 } from "commander";
|
|
9839
11064
|
|
|
9840
11065
|
// src/commands/_provider-guard.ts
|
|
9841
11066
|
import { log as log38 } from "@clack/prompts";
|
|
@@ -9950,7 +11175,7 @@ async function startOrAttachShell(box, cfg) {
|
|
|
9950
11175
|
});
|
|
9951
11176
|
process.exit(code);
|
|
9952
11177
|
}
|
|
9953
|
-
var shellCommand = new
|
|
11178
|
+
var shellCommand = new Command37("shell").description(
|
|
9954
11179
|
"Open an interactive shell in a box, in a detachable tmux session (auto-unpause/start)"
|
|
9955
11180
|
).argument(
|
|
9956
11181
|
"[box]",
|
|
@@ -10041,7 +11266,7 @@ var shellCommand = new Command36("shell").description(
|
|
|
10041
11266
|
handleLifecycleError(err);
|
|
10042
11267
|
}
|
|
10043
11268
|
});
|
|
10044
|
-
var shellAttachCommand = new
|
|
11269
|
+
var shellAttachCommand = new Command37("attach").description(
|
|
10045
11270
|
"Attach to a shell tmux session in a box, starting one if none is running (auto-unpause/start)"
|
|
10046
11271
|
).argument(
|
|
10047
11272
|
"[box]",
|
|
@@ -10079,7 +11304,7 @@ function renderShellTable(sessions) {
|
|
|
10079
11304
|
for (const r of rows) process.stdout.write(`${fmt(r)}
|
|
10080
11305
|
`);
|
|
10081
11306
|
}
|
|
10082
|
-
var shellLsCommand = new
|
|
11307
|
+
var shellLsCommand = new Command37("ls").description("List the shell tmux sessions running in a box").argument(
|
|
10083
11308
|
"[box]",
|
|
10084
11309
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10085
11310
|
).action(async (idOrName) => {
|
|
@@ -10102,7 +11327,7 @@ var shellLsCommand = new Command36("ls").description("List the shell tmux sessio
|
|
|
10102
11327
|
handleLifecycleError(err);
|
|
10103
11328
|
}
|
|
10104
11329
|
});
|
|
10105
|
-
var shellKillCommand = new
|
|
11330
|
+
var shellKillCommand = new Command37("kill").description("Kill a shell tmux session in a box (the shell and anything running in it)").argument(
|
|
10106
11331
|
"[box]",
|
|
10107
11332
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10108
11333
|
).option("-n, --name <label>", "shell label to kill (default: the box default shell)").option("--all", "kill every shell session in the box").action(async function(idOrName) {
|
|
@@ -10140,8 +11365,8 @@ shellCommand.addCommand(shellLsCommand);
|
|
|
10140
11365
|
shellCommand.addCommand(shellKillCommand);
|
|
10141
11366
|
|
|
10142
11367
|
// src/commands/start.ts
|
|
10143
|
-
import { Command as
|
|
10144
|
-
var startCommand = new
|
|
11368
|
+
import { Command as Command38 } from "commander";
|
|
11369
|
+
var startCommand = new Command38("start").description(
|
|
10145
11370
|
"Start a stopped box. Docker: docker start + relaunch ctl/dockerd/vnc daemons. Cloud: backend.start, then re-resolve preview URLs/tokens, re-launch in-sandbox ctl/dockerd daemons, and re-register with the host relay (so the CloudBoxPoller resumes)."
|
|
10146
11371
|
).argument(
|
|
10147
11372
|
"[box]",
|
|
@@ -10165,7 +11390,7 @@ var startCommand = new Command37("start").description(
|
|
|
10165
11390
|
|
|
10166
11391
|
// src/commands/status.ts
|
|
10167
11392
|
import { log as log41 } from "@clack/prompts";
|
|
10168
|
-
import { Command as
|
|
11393
|
+
import { Command as Command39 } from "commander";
|
|
10169
11394
|
|
|
10170
11395
|
// src/endpoints-render.ts
|
|
10171
11396
|
function renderEndpointLines(endpoints, stream) {
|
|
@@ -10377,7 +11602,7 @@ async function runInspect(box, opts) {
|
|
|
10377
11602
|
|
|
10378
11603
|
// src/commands/status.ts
|
|
10379
11604
|
var statusCommand2 = withWatchOptions(
|
|
10380
|
-
new
|
|
11605
|
+
new Command39("status").description("Show service + task status from a box's agentbox-ctl daemon").argument(
|
|
10381
11606
|
"[box]",
|
|
10382
11607
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10383
11608
|
).option("-j, --json", "machine-readable JSON output").option("--inspect", "show detailed box info (volumes, limits, paths) instead of service/task status")
|
|
@@ -10551,8 +11776,8 @@ function renderPersisted2(s, state) {
|
|
|
10551
11776
|
}
|
|
10552
11777
|
|
|
10553
11778
|
// src/commands/stop.ts
|
|
10554
|
-
import { Command as
|
|
10555
|
-
var stopCommand = new
|
|
11779
|
+
import { Command as Command40 } from "commander";
|
|
11780
|
+
var stopCommand = new Command40("stop").description(
|
|
10556
11781
|
"Stop a box (Docker: docker stop; preserves upper + node_modules volumes. Cloud: backend.stop \u2014 sandbox stays in your account, disk preserved)."
|
|
10557
11782
|
).argument(
|
|
10558
11783
|
"[box]",
|
|
@@ -10581,7 +11806,7 @@ restart with: agentbox start ${box.name}
|
|
|
10581
11806
|
});
|
|
10582
11807
|
|
|
10583
11808
|
// src/commands/top.ts
|
|
10584
|
-
import { Command as
|
|
11809
|
+
import { Command as Command41 } from "commander";
|
|
10585
11810
|
var COLS = ["BOX", "STATE", "CPU%", "MEM USAGE / LIMIT", "MEM%", "PIDS", "DISK", "NET I/O"];
|
|
10586
11811
|
function row(name, state, s) {
|
|
10587
11812
|
const mem = `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memLimitBytes)}`;
|
|
@@ -10614,6 +11839,7 @@ async function selectBoxes(idOrName, opts) {
|
|
|
10614
11839
|
}
|
|
10615
11840
|
async function snapshot(idOrName, opts) {
|
|
10616
11841
|
const boxes = await selectBoxes(idOrName, opts);
|
|
11842
|
+
if (opts.live) await applyLiveCloudStates(boxes);
|
|
10617
11843
|
const stats = await Promise.all(
|
|
10618
11844
|
boxes.map((b) => {
|
|
10619
11845
|
if ((b.provider ?? "docker") !== "docker") return emptyStats(b.provider ?? "cloud");
|
|
@@ -10654,10 +11880,10 @@ async function renderProjectFooters() {
|
|
|
10654
11880
|
|
|
10655
11881
|
SYSTEM: ${parts.join(" - ")}` : "";
|
|
10656
11882
|
}
|
|
10657
|
-
var topCommand = new
|
|
11883
|
+
var topCommand = new Command41("top").description("Live resource monitor (cpu/mem/pids/disk) for a box, the project, or every box").argument(
|
|
10658
11884
|
"[box]",
|
|
10659
11885
|
"box ref (default: every box on the host; --project narrows to the cwd's project)"
|
|
10660
|
-
).option("-p, --project", "show only boxes in the cwd's project").option("--once", "print a single snapshot instead of watching").option("-j, --json", "machine-readable JSON (implies --once)").option("--interval <seconds>", "refresh interval", "2").action(async (idOrName, opts) => {
|
|
11886
|
+
).option("-p, --project", "show only boxes in the cwd's project").option("--once", "print a single snapshot instead of watching").option("-j, --json", "machine-readable JSON (implies --once)").option("--interval <seconds>", "refresh interval", "2").option("--live", "probe live cloud state via the provider SDK (slower; default: last host-known)").action(async (idOrName, opts) => {
|
|
10661
11887
|
try {
|
|
10662
11888
|
if (opts.json) {
|
|
10663
11889
|
const { boxes, stats } = await snapshot(idOrName, opts);
|
|
@@ -10687,8 +11913,8 @@ var topCommand = new Command40("top").description("Live resource monitor (cpu/me
|
|
|
10687
11913
|
});
|
|
10688
11914
|
|
|
10689
11915
|
// src/commands/unpause.ts
|
|
10690
|
-
import { Command as
|
|
10691
|
-
var unpauseCommand = new
|
|
11916
|
+
import { Command as Command42 } from "commander";
|
|
11917
|
+
var unpauseCommand = new Command42("unpause").description(
|
|
10692
11918
|
"Resume a paused box. Docker: `docker unpause` (sub-second). Cloud: backend.resume (re-hydrates from archive \u2014 slower first time)."
|
|
10693
11919
|
).argument(
|
|
10694
11920
|
"[box]",
|
|
@@ -10712,8 +11938,8 @@ var unpauseCommand = new Command41("unpause").description(
|
|
|
10712
11938
|
|
|
10713
11939
|
// src/commands/update.ts
|
|
10714
11940
|
import { spawn as spawn4 } from "child_process";
|
|
10715
|
-
import { confirm as
|
|
10716
|
-
import { Command as
|
|
11941
|
+
import { confirm as confirm16, intro as intro8, isCancel as isCancel17, log as log42, outro as outro8, spinner as spinner10 } from "@clack/prompts";
|
|
11942
|
+
import { Command as Command43 } from "commander";
|
|
10717
11943
|
|
|
10718
11944
|
// src/exec-method.ts
|
|
10719
11945
|
function detectExecutionMethod(input) {
|
|
@@ -10757,7 +11983,7 @@ function runInherit(cmd, args) {
|
|
|
10757
11983
|
child.on("close", (code) => resolveP(code ?? 0));
|
|
10758
11984
|
});
|
|
10759
11985
|
}
|
|
10760
|
-
var updateCommand = new
|
|
11986
|
+
var updateCommand = new Command43("self-update").description(
|
|
10761
11987
|
"Update agentbox: self-update via npm/pnpm (unless run via npx), wipe the box image so it rebuilds, and reload the relay"
|
|
10762
11988
|
).option("-y, --yes", "skip the confirmation prompt").option("--dry-run", "show what would happen, don't change anything").option("--skip-self", "skip the package self-update; only refresh the image + relay").action(async (opts) => {
|
|
10763
11989
|
try {
|
|
@@ -10780,8 +12006,8 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10780
12006
|
return;
|
|
10781
12007
|
}
|
|
10782
12008
|
if (!opts.yes) {
|
|
10783
|
-
const ok = await
|
|
10784
|
-
if (
|
|
12009
|
+
const ok = await confirm16({ message: "Proceed with update?", initialValue: true });
|
|
12010
|
+
if (isCancel17(ok) || !ok) {
|
|
10785
12011
|
log42.info("cancelled");
|
|
10786
12012
|
return;
|
|
10787
12013
|
}
|
|
@@ -10803,13 +12029,13 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10803
12029
|
log42.success(`updated ${PKG} via ${cmd.cmd}`);
|
|
10804
12030
|
}
|
|
10805
12031
|
}
|
|
10806
|
-
const s =
|
|
12032
|
+
const s = spinner10();
|
|
10807
12033
|
s.start(`removing image ${DEFAULT_BOX_IMAGE}`);
|
|
10808
12034
|
const removed = await removeImage(DEFAULT_BOX_IMAGE);
|
|
10809
12035
|
s.stop(
|
|
10810
12036
|
removed ? `removed image ${DEFAULT_BOX_IMAGE} (rebuilds on next create/claude)` : `image ${DEFAULT_BOX_IMAGE} not present (nothing to remove)`
|
|
10811
12037
|
);
|
|
10812
|
-
const sr =
|
|
12038
|
+
const sr = spinner10();
|
|
10813
12039
|
sr.start("stopping relay");
|
|
10814
12040
|
const stop = await stopRelay();
|
|
10815
12041
|
sr.stop(
|
|
@@ -10820,7 +12046,7 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10820
12046
|
"relay will restart automatically (with the updated build) on your next `agentbox create` / `agentbox claude`"
|
|
10821
12047
|
);
|
|
10822
12048
|
} else {
|
|
10823
|
-
const sr2 =
|
|
12049
|
+
const sr2 = spinner10();
|
|
10824
12050
|
sr2.start("restarting relay");
|
|
10825
12051
|
try {
|
|
10826
12052
|
const ep = await ensureRelay();
|
|
@@ -10841,7 +12067,7 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10841
12067
|
// src/commands/url.ts
|
|
10842
12068
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
10843
12069
|
import { log as log43 } from "@clack/prompts";
|
|
10844
|
-
import { Command as
|
|
12070
|
+
import { Command as Command44 } from "commander";
|
|
10845
12071
|
var SIGNED_URL_TTL_MIN2 = 1;
|
|
10846
12072
|
var SIGNED_URL_TTL_MAX2 = 86400;
|
|
10847
12073
|
function parseTtlOrExit2(raw) {
|
|
@@ -10854,7 +12080,7 @@ function parseTtlOrExit2(raw) {
|
|
|
10854
12080
|
}
|
|
10855
12081
|
return n;
|
|
10856
12082
|
}
|
|
10857
|
-
var urlCommand = new
|
|
12083
|
+
var urlCommand = new Command44("url").description(
|
|
10858
12084
|
"Open a box's web app URL in the browser, even when no service declares `expose:` (auto-unpause/start)"
|
|
10859
12085
|
).argument(
|
|
10860
12086
|
"[box]",
|
|
@@ -10933,8 +12159,8 @@ var urlCommand = new Command43("url").description(
|
|
|
10933
12159
|
|
|
10934
12160
|
// src/commands/wait.ts
|
|
10935
12161
|
import { log as log44 } from "@clack/prompts";
|
|
10936
|
-
import { Command as
|
|
10937
|
-
var waitCommand = new
|
|
12162
|
+
import { Command as Command45 } from "commander";
|
|
12163
|
+
var waitCommand = new Command45("wait").description("Block until the box reports all autostart units ready").argument(
|
|
10938
12164
|
"[box]",
|
|
10939
12165
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10940
12166
|
).option("--timeout <ms>", "overall timeout in milliseconds", "120000").option("--units <names...>", "restrict to the named units").option("-j, --json", "machine-readable JSON output").action(async (idOrName, opts) => {
|
|
@@ -10974,14 +12200,14 @@ var SUGARED_COMMANDS = ["create", "claude", "codex", "opencode"];
|
|
|
10974
12200
|
function isSugared(name) {
|
|
10975
12201
|
return SUGARED_COMMANDS.includes(name);
|
|
10976
12202
|
}
|
|
10977
|
-
function rewriteProviderPrefix(
|
|
10978
|
-
if (
|
|
10979
|
-
const provider =
|
|
10980
|
-
const subcmd =
|
|
10981
|
-
if (typeof provider !== "string" || typeof subcmd !== "string") return [...
|
|
10982
|
-
if (!isKnownProvider(provider) || !isSugared(subcmd)) return [...
|
|
10983
|
-
const head =
|
|
10984
|
-
const rest =
|
|
12203
|
+
function rewriteProviderPrefix(argv2) {
|
|
12204
|
+
if (argv2.length < 4) return [...argv2];
|
|
12205
|
+
const provider = argv2[2];
|
|
12206
|
+
const subcmd = argv2[3];
|
|
12207
|
+
if (typeof provider !== "string" || typeof subcmd !== "string") return [...argv2];
|
|
12208
|
+
if (!isKnownProvider(provider) || !isSugared(subcmd)) return [...argv2];
|
|
12209
|
+
const head = argv2.slice(0, 2);
|
|
12210
|
+
const rest = argv2.slice(4);
|
|
10985
12211
|
return [...head, subcmd, "--provider", provider, ...rest];
|
|
10986
12212
|
}
|
|
10987
12213
|
|
|
@@ -10989,7 +12215,7 @@ function rewriteProviderPrefix(argv) {
|
|
|
10989
12215
|
process.env.DOCKER_CLI_HINTS ??= "false";
|
|
10990
12216
|
process.env.AGENTBOX_CLI_VERSION = AGENTBOX_VERSION;
|
|
10991
12217
|
process.env.AGENTBOX_CLI_COMMIT = AGENTBOX_COMMIT;
|
|
10992
|
-
var program = new
|
|
12218
|
+
var program = new Command46();
|
|
10993
12219
|
program.name("agentbox").description("Launch coding agents in isolated sandboxes").version(AGENTBOX_VERSION);
|
|
10994
12220
|
program.enablePositionalOptions();
|
|
10995
12221
|
program.addCommand(createCommand);
|
|
@@ -11031,10 +12257,43 @@ program.addCommand(vercelCommand);
|
|
|
11031
12257
|
program.addCommand(dockerCommand);
|
|
11032
12258
|
program.addCommand(updateCommand);
|
|
11033
12259
|
program.addCommand(installCommand);
|
|
12260
|
+
program.addCommand(doctorCommand);
|
|
11034
12261
|
program.configureHelp({ visibleCommands: () => [] });
|
|
11035
12262
|
program.addHelpText("after", () => "\n" + buildGroupedHelp(program));
|
|
11036
12263
|
await applyEngineOverrideAtStartup();
|
|
11037
|
-
|
|
12264
|
+
var argv = rewriteProviderPrefix(process.argv);
|
|
12265
|
+
var FIRST_RUN_EXEMPT = /* @__PURE__ */ new Set([
|
|
12266
|
+
"install",
|
|
12267
|
+
"doctor",
|
|
12268
|
+
"help",
|
|
12269
|
+
"relay",
|
|
12270
|
+
"_run-queued-job",
|
|
12271
|
+
"drive",
|
|
12272
|
+
"screen"
|
|
12273
|
+
]);
|
|
12274
|
+
function isFirstRunHookEligible(args) {
|
|
12275
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
12276
|
+
const rest = args.slice(2);
|
|
12277
|
+
if (rest.length === 0) return false;
|
|
12278
|
+
for (const a of rest) {
|
|
12279
|
+
if (a === "--help" || a === "-h" || a === "--version" || a === "-V") return false;
|
|
12280
|
+
}
|
|
12281
|
+
const first = rest[0];
|
|
12282
|
+
if (typeof first !== "string" || first.startsWith("-")) return false;
|
|
12283
|
+
if (FIRST_RUN_EXEMPT.has(first)) return false;
|
|
12284
|
+
return true;
|
|
12285
|
+
}
|
|
12286
|
+
if (isFirstRun() && isFirstRunHookEligible(argv)) {
|
|
12287
|
+
try {
|
|
12288
|
+
await runInstallWizard({ fromAutoTrigger: true });
|
|
12289
|
+
} catch (err) {
|
|
12290
|
+
process.stderr.write(
|
|
12291
|
+
`install wizard failed: ${err instanceof Error ? err.message : String(err)}
|
|
12292
|
+
`
|
|
12293
|
+
);
|
|
12294
|
+
}
|
|
12295
|
+
}
|
|
12296
|
+
program.parseAsync(argv).catch((err) => {
|
|
11038
12297
|
console.error(err);
|
|
11039
12298
|
process.exit(1);
|
|
11040
12299
|
});
|