@madarco/agentbox 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/README.md +161 -0
- package/dist/{_cloud-attach-ZXBCNWJX.js → _cloud-attach-O6NYTLES.js} +3 -3
- package/dist/{chunk-BXQMIEHC.js → chunk-2GPORKYF.js} +254 -162
- package/dist/chunk-2GPORKYF.js.map +1 -0
- package/dist/{chunk-NCJP5MTN.js → chunk-7UIAO7PC.js} +213 -51
- package/dist/chunk-7UIAO7PC.js.map +1 -0
- package/dist/{chunk-GU5LW4B5.js → chunk-R4O5WPHW.js} +374 -62
- package/dist/chunk-R4O5WPHW.js.map +1 -0
- package/dist/{dist-GDHP34ZK.js → dist-5FQGYRW5.js} +15 -3
- package/dist/dist-5FQGYRW5.js.map +1 -0
- package/dist/{dist-32EZBYG4.js → dist-BQNX7RQE.js} +12 -2
- package/dist/{dist-XML54CNB.js → dist-PZW3GWWU.js} +30 -5
- package/dist/dist-PZW3GWWU.js.map +1 -0
- package/dist/{dist-CX5CGVEB.js → dist-TMHSUVTP.js} +3 -3
- package/dist/index.js +1773 -526
- package/dist/index.js.map +1 -1
- package/package.json +9 -7
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +9 -8
- package/runtime/docker/packages/ctl/dist/bin.cjs +32 -3
- package/runtime/hetzner/agentbox-setup-skill.md +9 -8
- package/runtime/hetzner/ctl.cjs +32 -3
- package/runtime/relay/bin.cjs +32 -3
- package/runtime/vercel/agentbox-setup-skill.md +9 -8
- package/runtime/vercel/ctl.cjs +32 -3
- package/runtime/vercel/custom-system-CLAUDE.md +1 -4
- package/runtime/vercel/scripts/provision.sh +40 -0
- package/share/agentbox-setup/SKILL.md +9 -8
- 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-O6NYTLES.js.map} +0 -0
- /package/dist/{dist-32EZBYG4.js.map → dist-BQNX7RQE.js.map} +0 -0
- /package/dist/{dist-CX5CGVEB.js.map → dist-TMHSUVTP.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-2GPORKYF.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-R4O5WPHW.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-7UIAO7PC.js";
|
|
203
211
|
import {
|
|
204
212
|
DEFAULT_BOX_IMAGE,
|
|
205
213
|
STATE_DIR,
|
|
@@ -211,11 +219,11 @@ import {
|
|
|
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.0" : "0.0.0-dev";
|
|
223
|
+
var AGENTBOX_COMMIT = true ? "e0ccad3c" : "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-TMHSUVTP.js")).daytonaBackend;
|
|
1858
|
+
case "hetzner":
|
|
1859
|
+
return (await import("./dist-5FQGYRW5.js")).hetznerBackend;
|
|
1860
|
+
case "vercel":
|
|
1861
|
+
return (await import("./dist-PZW3GWWU.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-TMHSUVTP.js")).daytonaProvider;
|
|
2659
2774
|
case "hetzner":
|
|
2660
|
-
return (await import("./dist-
|
|
2775
|
+
return (await import("./dist-5FQGYRW5.js")).hetznerProvider;
|
|
2661
2776
|
case "vercel":
|
|
2662
|
-
return (await import("./dist-
|
|
2777
|
+
return (await import("./dist-PZW3GWWU.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({
|
|
@@ -5126,7 +5357,7 @@ var createCommand = new Command9("create").description("Create and start a new a
|
|
|
5126
5357
|
}
|
|
5127
5358
|
outro5("done");
|
|
5128
5359
|
if (attachClaudeAfter) {
|
|
5129
|
-
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-
|
|
5360
|
+
const { cloudAgentAttach: cloudAgentAttach2 } = await import("./_cloud-attach-O6NYTLES.js");
|
|
5130
5361
|
await cloudAgentAttach2({
|
|
5131
5362
|
box: result.record,
|
|
5132
5363
|
binary: "claude",
|
|
@@ -5169,13 +5400,16 @@ import { Command as Command10 } from "commander";
|
|
|
5169
5400
|
var SIDEBAR_WIDTH = 33;
|
|
5170
5401
|
var MIN_RIGHT_W = 20;
|
|
5171
5402
|
var MIN_RIGHT_H = 4;
|
|
5172
|
-
function computeLayout(cols, rows) {
|
|
5403
|
+
function computeLayout(cols, rows, requestedAlertH = 0) {
|
|
5173
5404
|
const sidebarW = Math.min(SIDEBAR_WIDTH, Math.max(0, cols - MIN_RIGHT_W - 1));
|
|
5174
5405
|
const sepX = sidebarW;
|
|
5175
5406
|
const rightX = sidebarW + 1;
|
|
5176
5407
|
const rightW = Math.max(0, cols - rightX);
|
|
5177
5408
|
const statusY = rows - 1;
|
|
5178
|
-
const
|
|
5409
|
+
const desired = Math.max(0, requestedAlertH);
|
|
5410
|
+
const alertH = statusY - desired >= MIN_RIGHT_H ? desired : 0;
|
|
5411
|
+
const paneH = Math.max(0, statusY - alertH);
|
|
5412
|
+
const alertY = statusY - alertH;
|
|
5179
5413
|
return {
|
|
5180
5414
|
cols,
|
|
5181
5415
|
rows,
|
|
@@ -5183,6 +5417,8 @@ function computeLayout(cols, rows) {
|
|
|
5183
5417
|
sepX,
|
|
5184
5418
|
right: { x: rightX, y: 0, w: rightW, h: paneH },
|
|
5185
5419
|
statusY,
|
|
5420
|
+
alertH,
|
|
5421
|
+
alertY,
|
|
5186
5422
|
tooSmall: rightW < MIN_RIGHT_W || paneH < MIN_RIGHT_H
|
|
5187
5423
|
};
|
|
5188
5424
|
}
|
|
@@ -5478,6 +5714,21 @@ var BLANK = {
|
|
|
5478
5714
|
strike: false
|
|
5479
5715
|
};
|
|
5480
5716
|
var PtySession = class {
|
|
5717
|
+
/** Box this session attaches to. Identifies it in the compositor's pool. */
|
|
5718
|
+
boxId;
|
|
5719
|
+
/** When true, the compositor keeps this session alive (in its pool) across
|
|
5720
|
+
* box switches instead of disposing it — see {@link Compositor.liveSessions}. */
|
|
5721
|
+
keepAlive;
|
|
5722
|
+
/** Agent/shell mode of this attach. The compositor restores `activeMode`
|
|
5723
|
+
* (drives the footer) from this when re-showing a pooled session. */
|
|
5724
|
+
mode;
|
|
5725
|
+
/**
|
|
5726
|
+
* Whether this session is the one currently shown in the right pane. A
|
|
5727
|
+
* kept-alive hidden session (`active === false`) still consumes PTY output
|
|
5728
|
+
* to keep its headless buffer current, but must NOT trigger right-pane
|
|
5729
|
+
* repaints. The compositor flips this on show/hide.
|
|
5730
|
+
*/
|
|
5731
|
+
active = true;
|
|
5481
5732
|
term;
|
|
5482
5733
|
pty;
|
|
5483
5734
|
cleanup;
|
|
@@ -5485,7 +5736,10 @@ var PtySession = class {
|
|
|
5485
5736
|
// Reused per cell read — valid only until the next cell() call (the renderer
|
|
5486
5737
|
// consumes it synchronously within composeRow).
|
|
5487
5738
|
out = { ...BLANK };
|
|
5488
|
-
constructor(spawn5, TerminalClass, command, args, cols, rows, onRenderable, onExit, cleanup) {
|
|
5739
|
+
constructor(spawn5, TerminalClass, boxId, keepAlive, mode, command, args, cols, rows, onRenderable, onExit, cleanup) {
|
|
5740
|
+
this.boxId = boxId;
|
|
5741
|
+
this.keepAlive = keepAlive;
|
|
5742
|
+
this.mode = mode;
|
|
5489
5743
|
this.term = new TerminalClass({
|
|
5490
5744
|
cols,
|
|
5491
5745
|
rows,
|
|
@@ -5501,13 +5755,15 @@ var PtySession = class {
|
|
|
5501
5755
|
env: process.env
|
|
5502
5756
|
});
|
|
5503
5757
|
this.pty.onData((d) => {
|
|
5504
|
-
this.term.write(d, () =>
|
|
5758
|
+
this.term.write(d, () => {
|
|
5759
|
+
if (this.active) onRenderable();
|
|
5760
|
+
});
|
|
5505
5761
|
});
|
|
5506
5762
|
this.term.onData((d) => {
|
|
5507
5763
|
if (!this.disposed) this.pty.write(d);
|
|
5508
5764
|
});
|
|
5509
5765
|
this.pty.onExit(() => {
|
|
5510
|
-
if (!this.disposed) onExit();
|
|
5766
|
+
if (!this.disposed) onExit(this.boxId);
|
|
5511
5767
|
});
|
|
5512
5768
|
}
|
|
5513
5769
|
write(bytes) {
|
|
@@ -5569,6 +5825,7 @@ var SB_AWAITING = SB_BG + "\x1B[38;5;51m\x1B[1m";
|
|
|
5569
5825
|
var SGR_RESET = "\x1B[0m";
|
|
5570
5826
|
var POLL_MS = 1e3;
|
|
5571
5827
|
var FRAME_MS = 16;
|
|
5828
|
+
var KEEP_ALIVE_MAX = 6;
|
|
5572
5829
|
var RESIZE_DEBOUNCE_MS = 120;
|
|
5573
5830
|
var LEADER_LINGER_MS = 1500;
|
|
5574
5831
|
var NOTICE_SPINNER_MS = 120;
|
|
@@ -5581,7 +5838,7 @@ var Compositor = class {
|
|
|
5581
5838
|
constructor(deps, initialId) {
|
|
5582
5839
|
this.deps = deps;
|
|
5583
5840
|
this.selectedId = initialId;
|
|
5584
|
-
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30);
|
|
5841
|
+
this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30, 0);
|
|
5585
5842
|
this.parser = new InputParser({
|
|
5586
5843
|
onEvent: (e) => {
|
|
5587
5844
|
if (e.type === "leader") {
|
|
@@ -5642,7 +5899,17 @@ var Compositor = class {
|
|
|
5642
5899
|
inp = process.stdin;
|
|
5643
5900
|
boxes = [];
|
|
5644
5901
|
selectedId;
|
|
5902
|
+
/** The session currently shown in the right pane (may also be in
|
|
5903
|
+
* {@link liveSessions} when it's keep-alive). */
|
|
5645
5904
|
session = null;
|
|
5905
|
+
/**
|
|
5906
|
+
* Pool of kept-alive sessions, keyed by box id, for providers whose attach
|
|
5907
|
+
* is expensive to reconnect (vercel). Hidden entries keep their PTY + headless
|
|
5908
|
+
* buffer alive so switching back is instant — no probe, no re-spawn. Map
|
|
5909
|
+
* insertion order doubles as LRU recency (re-set on activate); bounded by
|
|
5910
|
+
* {@link KEEP_ALIVE_MAX}. Reconnect-cheap providers never enter this map.
|
|
5911
|
+
*/
|
|
5912
|
+
liveSessions = /* @__PURE__ */ new Map();
|
|
5646
5913
|
placeholder = null;
|
|
5647
5914
|
menu = null;
|
|
5648
5915
|
lifecycleMenu = null;
|
|
@@ -5734,6 +6001,22 @@ var Compositor = class {
|
|
|
5734
6001
|
} catch {
|
|
5735
6002
|
}
|
|
5736
6003
|
this.syncPromptSubscriptions();
|
|
6004
|
+
this.reconcileLiveSessions();
|
|
6005
|
+
}
|
|
6006
|
+
/**
|
|
6007
|
+
* Drop pooled (hidden) sessions whose box is gone or no longer running, so a
|
|
6008
|
+
* paused/stopped/destroyed box can't keep its remote attach process alive.
|
|
6009
|
+
* The *active* session is intentionally skipped — it's torn down on the next
|
|
6010
|
+
* switch/re-resolve via {@link deactivateActive} (which checks box state),
|
|
6011
|
+
* so evicting it here would blank the pane out from under the poll's
|
|
6012
|
+
* re-resolve logic.
|
|
6013
|
+
*/
|
|
6014
|
+
reconcileLiveSessions() {
|
|
6015
|
+
for (const boxId of [...this.liveSessions.keys()]) {
|
|
6016
|
+
if (this.session && this.session.boxId === boxId) continue;
|
|
6017
|
+
const running = this.boxes.some((b) => b.id === boxId && b.state === "running");
|
|
6018
|
+
if (!running) this.evictSession(boxId);
|
|
6019
|
+
}
|
|
5737
6020
|
}
|
|
5738
6021
|
/**
|
|
5739
6022
|
* Diff the current box list against {@link promptStreams}: subscribe to
|
|
@@ -5758,7 +6041,7 @@ var Compositor = class {
|
|
|
5758
6041
|
let changed = this.activePrompts.delete(boxId);
|
|
5759
6042
|
if (this.activeNotices.delete(boxId)) changed = true;
|
|
5760
6043
|
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
5761
|
-
if (changed) this.
|
|
6044
|
+
if (changed) this.redrawForAlert();
|
|
5762
6045
|
}
|
|
5763
6046
|
}
|
|
5764
6047
|
for (const boxId of wanted) {
|
|
@@ -5769,21 +6052,21 @@ var Compositor = class {
|
|
|
5769
6052
|
onPrompt: (ev) => {
|
|
5770
6053
|
if (this.tornDown) return;
|
|
5771
6054
|
this.activePrompts.set(boxId, ev);
|
|
5772
|
-
this.
|
|
6055
|
+
this.redrawForAlert();
|
|
5773
6056
|
},
|
|
5774
6057
|
onResolved: (id) => {
|
|
5775
6058
|
if (this.tornDown) return;
|
|
5776
6059
|
const current = this.activePrompts.get(boxId);
|
|
5777
6060
|
if (current && current.id === id) {
|
|
5778
6061
|
this.activePrompts.delete(boxId);
|
|
5779
|
-
this.
|
|
6062
|
+
this.redrawForAlert();
|
|
5780
6063
|
}
|
|
5781
6064
|
},
|
|
5782
6065
|
onNotice: (ev) => {
|
|
5783
6066
|
if (this.tornDown) return;
|
|
5784
6067
|
this.activeNotices.set(boxId, ev);
|
|
5785
6068
|
this.startNoticeSpinner();
|
|
5786
|
-
this.
|
|
6069
|
+
this.redrawForAlert();
|
|
5787
6070
|
},
|
|
5788
6071
|
onNoticeCleared: (id) => {
|
|
5789
6072
|
if (this.tornDown) return;
|
|
@@ -5791,7 +6074,7 @@ var Compositor = class {
|
|
|
5791
6074
|
if (current && current.id === id) {
|
|
5792
6075
|
this.activeNotices.delete(boxId);
|
|
5793
6076
|
if (this.activeNotices.size === 0) this.stopNoticeSpinner();
|
|
5794
|
-
this.
|
|
6077
|
+
this.redrawForAlert();
|
|
5795
6078
|
}
|
|
5796
6079
|
},
|
|
5797
6080
|
onError: () => {
|
|
@@ -5818,9 +6101,11 @@ var Compositor = class {
|
|
|
5818
6101
|
return this.boxes.find((b) => b.id === this.selectedId);
|
|
5819
6102
|
}
|
|
5820
6103
|
async poll() {
|
|
5821
|
-
const
|
|
6104
|
+
const stateKey = () => JSON.stringify(
|
|
5822
6105
|
this.boxes.map((b) => [b.id, b.state, b.activity, b.sessionTitle])
|
|
5823
6106
|
);
|
|
6107
|
+
const before = stateKey();
|
|
6108
|
+
const beforeAlertH = this.alertHeight();
|
|
5824
6109
|
await this.refreshBoxes();
|
|
5825
6110
|
if (this.busy) {
|
|
5826
6111
|
} else if (!this.boxes.some((b) => b.id === this.selectedId) && this.boxes[0]) {
|
|
@@ -5832,19 +6117,99 @@ var Compositor = class {
|
|
|
5832
6117
|
const reresolve = this.session && !running || this.placeholder && running || this.menu && !running || this.lifecycleMenu != null && box?.state !== this.lifecycleMenu.state;
|
|
5833
6118
|
if (reresolve) await this.spawnActive();
|
|
5834
6119
|
}
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
6120
|
+
const stateChanged = stateKey() !== before;
|
|
6121
|
+
const alertChanged = this.alertHeight() !== beforeAlertH;
|
|
6122
|
+
if (alertChanged) {
|
|
6123
|
+
this.relayout();
|
|
6124
|
+
} else if (stateChanged) {
|
|
5838
6125
|
this.drawChrome();
|
|
5839
6126
|
}
|
|
5840
6127
|
}
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
6128
|
+
/**
|
|
6129
|
+
* Detach the active session from view. Keep-alive sessions (those in
|
|
6130
|
+
* {@link liveSessions}) stay running in the background; everything else is
|
|
6131
|
+
* disposed — matching the pre-pool dispose-on-switch behaviour for docker /
|
|
6132
|
+
* hetzner / daytona.
|
|
6133
|
+
*/
|
|
6134
|
+
deactivateActive() {
|
|
6135
|
+
const s = this.session;
|
|
6136
|
+
if (!s) return;
|
|
6137
|
+
s.active = false;
|
|
6138
|
+
const pooled = this.liveSessions.get(s.boxId) === s;
|
|
6139
|
+
const boxRunning = this.boxes.some((b) => b.id === s.boxId && b.state === "running");
|
|
6140
|
+
if (!pooled || !boxRunning) {
|
|
6141
|
+
if (pooled) this.liveSessions.delete(s.boxId);
|
|
6142
|
+
s.dispose();
|
|
6143
|
+
}
|
|
6144
|
+
this.session = null;
|
|
6145
|
+
}
|
|
6146
|
+
/** Dispose and drop the pooled session for `boxId` (box gone / stopped /
|
|
6147
|
+
* its attach died). Clears the active reference if it was the shown one. */
|
|
6148
|
+
evictSession(boxId) {
|
|
6149
|
+
const pooled = this.liveSessions.get(boxId);
|
|
6150
|
+
if (pooled) {
|
|
6151
|
+
this.liveSessions.delete(boxId);
|
|
6152
|
+
pooled.dispose();
|
|
6153
|
+
}
|
|
6154
|
+
if (this.session && this.session.boxId === boxId) {
|
|
6155
|
+
if (this.session !== pooled) this.session.dispose();
|
|
6156
|
+
this.session = null;
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
/** Bound the pool: evict least-recently-used pooled sessions (Map insertion
|
|
6160
|
+
* order) until at most {@link KEEP_ALIVE_MAX} remain, never the active one. */
|
|
6161
|
+
evictLruIfNeeded() {
|
|
6162
|
+
for (const boxId of this.liveSessions.keys()) {
|
|
6163
|
+
if (this.liveSessions.size <= KEEP_ALIVE_MAX) break;
|
|
6164
|
+
if (this.session && this.session.boxId === boxId) continue;
|
|
6165
|
+
this.evictSession(boxId);
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
/** Dispose the active session plus every pooled one (teardown). */
|
|
6169
|
+
disposeAllSessions() {
|
|
6170
|
+
const active = this.session;
|
|
6171
|
+
if (active && this.liveSessions.get(active.boxId) !== active) active.dispose();
|
|
5844
6172
|
this.session = null;
|
|
6173
|
+
for (const s of this.liveSessions.values()) s.dispose();
|
|
6174
|
+
this.liveSessions.clear();
|
|
6175
|
+
}
|
|
6176
|
+
/**
|
|
6177
|
+
* Show a pooled session in the right pane — the fast switch-back path: no
|
|
6178
|
+
* probe, no re-spawn, instant repaint from its already-current headless
|
|
6179
|
+
* buffer. Re-marks it most-recently-used and re-applies the current layout
|
|
6180
|
+
* size (it may have changed while hidden).
|
|
6181
|
+
*/
|
|
6182
|
+
activateSession(sess) {
|
|
6183
|
+
this.deactivateActive();
|
|
6184
|
+
this.placeholder = null;
|
|
6185
|
+
this.menu = null;
|
|
6186
|
+
this.lifecycleMenu = null;
|
|
6187
|
+
this.createMenu = null;
|
|
6188
|
+
this.pendingConfirm = null;
|
|
6189
|
+
this.session = sess;
|
|
6190
|
+
sess.active = true;
|
|
6191
|
+
this.activeMode = sess.mode;
|
|
6192
|
+
sess.resize(Math.max(1, this.layout.right.w), Math.max(1, this.layout.right.h));
|
|
6193
|
+
this.liveSessions.delete(sess.boxId);
|
|
6194
|
+
this.liveSessions.set(sess.boxId, sess);
|
|
6195
|
+
this.prevRows = null;
|
|
6196
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
6197
|
+
this.scheduleRender();
|
|
6198
|
+
}
|
|
6199
|
+
/**
|
|
6200
|
+
* Show the selected box. If a kept-alive session is pooled for it, re-show it
|
|
6201
|
+
* synchronously (instant). Otherwise fall through to the async resolve+spawn.
|
|
6202
|
+
*/
|
|
6203
|
+
showSelected() {
|
|
6204
|
+
const cached = this.liveSessions.get(this.selectedId);
|
|
6205
|
+
if (cached) {
|
|
6206
|
+
this.activateSession(cached);
|
|
6207
|
+
return;
|
|
6208
|
+
}
|
|
6209
|
+
void this.spawnActive();
|
|
5845
6210
|
}
|
|
5846
6211
|
async spawnActive() {
|
|
5847
|
-
this.
|
|
6212
|
+
this.deactivateActive();
|
|
5848
6213
|
this.placeholder = null;
|
|
5849
6214
|
this.menu = null;
|
|
5850
6215
|
this.lifecycleMenu = null;
|
|
@@ -5858,25 +6223,37 @@ var Compositor = class {
|
|
|
5858
6223
|
}
|
|
5859
6224
|
/** Turn a resolved/started target into the right-pane state. */
|
|
5860
6225
|
applyTarget(target) {
|
|
5861
|
-
this.
|
|
6226
|
+
this.deactivateActive();
|
|
5862
6227
|
this.placeholder = null;
|
|
5863
6228
|
this.menu = null;
|
|
5864
6229
|
this.lifecycleMenu = null;
|
|
5865
6230
|
this.createMenu = null;
|
|
5866
6231
|
this.pendingConfirm = null;
|
|
5867
6232
|
if (target.kind === "attach") {
|
|
5868
|
-
|
|
6233
|
+
const boxId = this.selectedId;
|
|
6234
|
+
const mode = target.mode ?? "claude";
|
|
6235
|
+
const keepAlive = target.keepAlive ?? false;
|
|
6236
|
+
this.activeMode = mode;
|
|
5869
6237
|
this.session = new PtySession(
|
|
5870
6238
|
this.deps.ptySpawn,
|
|
5871
6239
|
this.deps.termCtor,
|
|
6240
|
+
boxId,
|
|
6241
|
+
keepAlive,
|
|
6242
|
+
mode,
|
|
5872
6243
|
target.command,
|
|
5873
6244
|
target.args,
|
|
5874
6245
|
Math.max(1, this.layout.right.w),
|
|
5875
6246
|
Math.max(1, this.layout.right.h),
|
|
5876
6247
|
() => this.scheduleRender(),
|
|
5877
|
-
() => this.onSessionExit(),
|
|
6248
|
+
(id) => this.onSessionExit(id),
|
|
5878
6249
|
target.cleanup
|
|
5879
6250
|
);
|
|
6251
|
+
if (keepAlive) {
|
|
6252
|
+
const prev = this.liveSessions.get(boxId);
|
|
6253
|
+
if (prev && prev !== this.session) prev.dispose();
|
|
6254
|
+
this.liveSessions.set(boxId, this.session);
|
|
6255
|
+
this.evictLruIfNeeded();
|
|
6256
|
+
}
|
|
5880
6257
|
} else if (target.kind === "menu") {
|
|
5881
6258
|
this.menu = { boxName: this.selectedBox()?.name ?? this.selectedId };
|
|
5882
6259
|
} else if (target.kind === "lifecycle-menu") {
|
|
@@ -5891,7 +6268,7 @@ var Compositor = class {
|
|
|
5891
6268
|
this.placeholder = target.lines;
|
|
5892
6269
|
}
|
|
5893
6270
|
this.prevRows = null;
|
|
5894
|
-
this.drawChrome();
|
|
6271
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
5895
6272
|
this.scheduleRender();
|
|
5896
6273
|
}
|
|
5897
6274
|
handleMenuKey(bytes) {
|
|
@@ -6224,8 +6601,9 @@ var Compositor = class {
|
|
|
6224
6601
|
}, 2500);
|
|
6225
6602
|
this.drawChrome();
|
|
6226
6603
|
}
|
|
6227
|
-
onSessionExit() {
|
|
6228
|
-
this.
|
|
6604
|
+
onSessionExit(boxId) {
|
|
6605
|
+
this.evictSession(boxId);
|
|
6606
|
+
if (boxId !== this.selectedId) return;
|
|
6229
6607
|
this.placeholder = ["", " session ended \u2014 Ctrl-a \u2191/\u2193 to switch boxes"];
|
|
6230
6608
|
this.prevRows = null;
|
|
6231
6609
|
this.scheduleRender();
|
|
@@ -6241,7 +6619,7 @@ var Compositor = class {
|
|
|
6241
6619
|
const next = dir === "prev" ? (i - 1 + n) % n : (i + 1) % n;
|
|
6242
6620
|
this.selectedId = this.boxes[next].id;
|
|
6243
6621
|
this.drawChrome();
|
|
6244
|
-
|
|
6622
|
+
this.showSelected();
|
|
6245
6623
|
}
|
|
6246
6624
|
/** Blank the right pane and drop the diff cache (next paint is full). */
|
|
6247
6625
|
clearRightPane() {
|
|
@@ -6341,8 +6719,36 @@ var Compositor = class {
|
|
|
6341
6719
|
}
|
|
6342
6720
|
for (let y = 0; y < sidebar.h; y++)
|
|
6343
6721
|
s += cursorTo2(sepX, y) + SB_HEADER + (y === 0 ? "\u256E" : "\u2502") + SGR_RESET;
|
|
6344
|
-
let status;
|
|
6345
6722
|
const activePromptForSelected = this.activePrompts.get(this.selectedId);
|
|
6723
|
+
const activeNoticeForSelected = this.activeNotices.get(this.selectedId);
|
|
6724
|
+
if (this.layout.alertH > 0) {
|
|
6725
|
+
const bandRows = this.layout.alertH;
|
|
6726
|
+
let bandLines = null;
|
|
6727
|
+
if (activePromptForSelected) {
|
|
6728
|
+
bandLines = renderAlertBand(
|
|
6729
|
+
{ kind: "prompt", prompt: activePromptForSelected },
|
|
6730
|
+
this.layout.cols,
|
|
6731
|
+
bandRows
|
|
6732
|
+
);
|
|
6733
|
+
} else if (activeNoticeForSelected) {
|
|
6734
|
+
bandLines = renderAlertBand(
|
|
6735
|
+
{ kind: "notice", message: activeNoticeForSelected.message, frame: this.noticeFrame },
|
|
6736
|
+
this.layout.cols,
|
|
6737
|
+
bandRows
|
|
6738
|
+
);
|
|
6739
|
+
} else {
|
|
6740
|
+
const q = this.selectedBox()?.claudeQuestion;
|
|
6741
|
+
if (q) {
|
|
6742
|
+
bandLines = renderAlertBand({ kind: "question", question: q }, this.layout.cols, bandRows);
|
|
6743
|
+
}
|
|
6744
|
+
}
|
|
6745
|
+
if (bandLines) {
|
|
6746
|
+
for (let i = 0; i < bandLines.length; i++) {
|
|
6747
|
+
s += cursorTo2(0, this.layout.alertY + i) + bandLines[i] + SGR_RESET;
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
let status;
|
|
6346
6752
|
if (this.pendingConfirm) {
|
|
6347
6753
|
const w = this.layout.cols;
|
|
6348
6754
|
const txt = ` Destroy ${this.pendingConfirm.name}? y = confirm \xB7 any other key = cancel `.slice(0, w).padEnd(w);
|
|
@@ -6351,15 +6757,14 @@ var Compositor = class {
|
|
|
6351
6757
|
const w = this.layout.cols;
|
|
6352
6758
|
const txt = ` ${this.flashMsg} `.slice(0, w).padEnd(w);
|
|
6353
6759
|
status = `\x1B[7m${txt}\x1B[0m`;
|
|
6354
|
-
} else if (activePromptForSelected) {
|
|
6760
|
+
} else if (this.layout.alertH === 0 && activePromptForSelected) {
|
|
6355
6761
|
status = renderFooter(
|
|
6356
6762
|
{ kind: "prompt", prompt: activePromptForSelected },
|
|
6357
6763
|
this.layout.cols
|
|
6358
6764
|
);
|
|
6359
|
-
} else if (this.
|
|
6360
|
-
const notice = this.activeNotices.get(this.selectedId);
|
|
6765
|
+
} else if (this.layout.alertH === 0 && activeNoticeForSelected) {
|
|
6361
6766
|
status = renderFooter(
|
|
6362
|
-
{ kind: "notice", message:
|
|
6767
|
+
{ kind: "notice", message: activeNoticeForSelected.message, frame: this.noticeFrame },
|
|
6363
6768
|
this.layout.cols
|
|
6364
6769
|
);
|
|
6365
6770
|
} else {
|
|
@@ -6382,17 +6787,62 @@ var Compositor = class {
|
|
|
6382
6787
|
if (this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
6383
6788
|
this.resizeTimer = setTimeout(() => {
|
|
6384
6789
|
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();
|
|
6790
|
+
this.relayout();
|
|
6394
6791
|
}, RESIZE_DEBOUNCE_MS);
|
|
6395
6792
|
}
|
|
6793
|
+
/**
|
|
6794
|
+
* Requested band height for the currently-selected box. Returns
|
|
6795
|
+
* `ALERT_BAND_ROWS` when the box has an active relay prompt, an active
|
|
6796
|
+
* notice (checkpoint), or claude is in the `question` state with a payload;
|
|
6797
|
+
* 0 otherwise. The layout silently drops the band to 0 if reserving it
|
|
6798
|
+
* would push the right pane below MIN_RIGHT_H.
|
|
6799
|
+
*/
|
|
6800
|
+
alertHeight() {
|
|
6801
|
+
const id = this.selectedId;
|
|
6802
|
+
if (this.activePrompts.has(id)) return ALERT_BAND_ROWS;
|
|
6803
|
+
if (this.activeNotices.has(id)) return ALERT_BAND_ROWS;
|
|
6804
|
+
const box = this.selectedBox();
|
|
6805
|
+
if (box?.claudeQuestion) return ALERT_BAND_ROWS;
|
|
6806
|
+
return 0;
|
|
6807
|
+
}
|
|
6808
|
+
/**
|
|
6809
|
+
* Recompute the layout against the current alert height, resize the inner
|
|
6810
|
+
* session, and repaint. Called from `scheduleResize` (terminal resize) and
|
|
6811
|
+
* from {@link syncAlertLayout} when the selected box's alert state flips.
|
|
6812
|
+
*/
|
|
6813
|
+
relayout() {
|
|
6814
|
+
this.layout = computeLayout(
|
|
6815
|
+
this.out.columns ?? 100,
|
|
6816
|
+
this.out.rows ?? 30,
|
|
6817
|
+
this.alertHeight()
|
|
6818
|
+
);
|
|
6819
|
+
this.prevRows = null;
|
|
6820
|
+
const r = this.layout.right;
|
|
6821
|
+
if (this.session && !this.layout.tooSmall) {
|
|
6822
|
+
this.session.resize(Math.max(1, r.w), Math.max(1, r.h));
|
|
6823
|
+
}
|
|
6824
|
+
this.out.write(SYNC_BEGIN + "\x1B[2J" + SYNC_END);
|
|
6825
|
+
this.drawChrome();
|
|
6826
|
+
this.render();
|
|
6827
|
+
}
|
|
6828
|
+
/**
|
|
6829
|
+
* If the selected box's alert state implies a different band height than
|
|
6830
|
+
* the current layout, run a full {@link relayout}; otherwise return false
|
|
6831
|
+
* so the caller can take the lighter `drawChrome()` path. Used by all
|
|
6832
|
+
* alert-state transitions (SSE handlers, poll, selection change).
|
|
6833
|
+
*/
|
|
6834
|
+
syncAlertLayout() {
|
|
6835
|
+
if (this.alertHeight() !== this.layout.alertH) {
|
|
6836
|
+
this.relayout();
|
|
6837
|
+
return true;
|
|
6838
|
+
}
|
|
6839
|
+
return false;
|
|
6840
|
+
}
|
|
6841
|
+
/** Common path for alert-state transitions: relayout when the band's
|
|
6842
|
+
* visibility changes, drawChrome only when it doesn't. */
|
|
6843
|
+
redrawForAlert() {
|
|
6844
|
+
if (!this.syncAlertLayout()) this.drawChrome();
|
|
6845
|
+
}
|
|
6396
6846
|
teardown() {
|
|
6397
6847
|
if (this.tornDown) return;
|
|
6398
6848
|
this.tornDown = true;
|
|
@@ -6407,7 +6857,7 @@ var Compositor = class {
|
|
|
6407
6857
|
this.activePrompts.clear();
|
|
6408
6858
|
this.activeNotices.clear();
|
|
6409
6859
|
this.parser.dispose();
|
|
6410
|
-
this.
|
|
6860
|
+
this.disposeAllSessions();
|
|
6411
6861
|
this.inp.off("data", this.onData);
|
|
6412
6862
|
this.out.off("resize", this.onResize);
|
|
6413
6863
|
if (this.inp.isTTY) this.inp.setRawMode(false);
|
|
@@ -6419,6 +6869,9 @@ var Compositor = class {
|
|
|
6419
6869
|
};
|
|
6420
6870
|
|
|
6421
6871
|
// src/commands/dashboard.ts
|
|
6872
|
+
function providerSupportsKeepAlive(provider) {
|
|
6873
|
+
return provider === "vercel";
|
|
6874
|
+
}
|
|
6422
6875
|
function sortBoxes(boxes) {
|
|
6423
6876
|
return [...boxes].sort((a, b) => {
|
|
6424
6877
|
const ap = a.projectRoot ?? "";
|
|
@@ -6455,7 +6908,10 @@ function toSidebar(b) {
|
|
|
6455
6908
|
activity: agent.activity,
|
|
6456
6909
|
sessionTitle: agent.sessionTitle,
|
|
6457
6910
|
index: b.projectIndex,
|
|
6458
|
-
project: b.projectRoot
|
|
6911
|
+
project: b.projectRoot,
|
|
6912
|
+
// Only claude reports an AskUserQuestion payload; gated on claudeActivity
|
|
6913
|
+
// === 'question' upstream (lifecycle.listBoxes) so it's undefined otherwise.
|
|
6914
|
+
claudeQuestion: b.claudeQuestion
|
|
6459
6915
|
};
|
|
6460
6916
|
}
|
|
6461
6917
|
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 +7034,8 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6578
7034
|
command: spec.argv[0],
|
|
6579
7035
|
args: spec.argv.slice(1),
|
|
6580
7036
|
...spec.cleanup ? { cleanup: spec.cleanup } : {},
|
|
6581
|
-
mode: which
|
|
7037
|
+
mode: which,
|
|
7038
|
+
...providerSupportsKeepAlive(record.provider) ? { keepAlive: true } : {}
|
|
6582
7039
|
};
|
|
6583
7040
|
};
|
|
6584
7041
|
const isCloudBox = (box) => (box.provider ?? "docker") !== "docker";
|
|
@@ -6612,7 +7069,12 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6612
7069
|
{ volume: claudeVolume },
|
|
6613
7070
|
{ image: box.image, isolate: claudeVolume !== SHARED_CLAUDE_VOLUME }
|
|
6614
7071
|
);
|
|
6615
|
-
|
|
7072
|
+
const claudeCfg = await loadEffectiveConfig(box.workspacePath);
|
|
7073
|
+
await startClaudeSession({
|
|
7074
|
+
container: box.container,
|
|
7075
|
+
claudeArgs: applyClaudeSkipPermissions([], claudeCfg.effective),
|
|
7076
|
+
boxName: box.name
|
|
7077
|
+
});
|
|
6616
7078
|
const info = await claudeSessionInfo(box.container);
|
|
6617
7079
|
await waitForTmuxPaneContent(box.container, info.sessionName);
|
|
6618
7080
|
return {
|
|
@@ -6629,7 +7091,11 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6629
7091
|
}
|
|
6630
7092
|
await ensureCodexInstalled(box.container);
|
|
6631
7093
|
if (box.codexConfigVolume) await seedCodexHooks(box.codexConfigVolume, box.image);
|
|
6632
|
-
await
|
|
7094
|
+
const codexCfg = await loadEffectiveConfig(box.workspacePath);
|
|
7095
|
+
await startCodexSession({
|
|
7096
|
+
container: box.container,
|
|
7097
|
+
codexArgs: applyCodexSkipPermissions([], codexCfg.effective)
|
|
7098
|
+
});
|
|
6633
7099
|
await waitForTmuxPaneContent(box.container, DEFAULT_CODEX_SESSION);
|
|
6634
7100
|
return {
|
|
6635
7101
|
kind: "attach",
|
|
@@ -6698,7 +7164,10 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6698
7164
|
if (!agent) return { boxId: result.record.id };
|
|
6699
7165
|
if (agent === "codex") {
|
|
6700
7166
|
await ensureCodexInstalled(ctr, { onProgress });
|
|
6701
|
-
await startCodexSession({
|
|
7167
|
+
await startCodexSession({
|
|
7168
|
+
container: ctr,
|
|
7169
|
+
codexArgs: applyCodexSkipPermissions([], cfg.effective)
|
|
7170
|
+
});
|
|
6702
7171
|
await waitForTmuxPaneContent(ctr, DEFAULT_CODEX_SESSION);
|
|
6703
7172
|
return {
|
|
6704
7173
|
boxId: result.record.id,
|
|
@@ -6725,7 +7194,11 @@ var dashboardCommand = new Command10("dashboard").description("Box list + the se
|
|
|
6725
7194
|
};
|
|
6726
7195
|
}
|
|
6727
7196
|
await rebuildPluginNativeDeps(ctr, { volume: result.record.claudeConfigVolume });
|
|
6728
|
-
await startClaudeSession({
|
|
7197
|
+
await startClaudeSession({
|
|
7198
|
+
container: ctr,
|
|
7199
|
+
claudeArgs: applyClaudeSkipPermissions([], cfg.effective),
|
|
7200
|
+
boxName: result.record.name
|
|
7201
|
+
});
|
|
6729
7202
|
const info = await claudeSessionInfo(ctr);
|
|
6730
7203
|
await waitForTmuxPaneContent(ctr, info.sessionName);
|
|
6731
7204
|
return {
|
|
@@ -7801,11 +8274,11 @@ function resolveToken(raw) {
|
|
|
7801
8274
|
// src/lib/drive/tmux.ts
|
|
7802
8275
|
var TMUX_USER = "vscode";
|
|
7803
8276
|
async function captureSession(provider, box, session, opts = {}) {
|
|
7804
|
-
const
|
|
8277
|
+
const argv2 = ["tmux", "capture-pane", opts.ansi ? "-pe" : "-p", "-t", session];
|
|
7805
8278
|
if (opts.rows) {
|
|
7806
|
-
|
|
8279
|
+
argv2.push("-S", String(opts.rows.from), "-E", String(opts.rows.to));
|
|
7807
8280
|
}
|
|
7808
|
-
const res = await provider.exec(box,
|
|
8281
|
+
const res = await provider.exec(box, argv2, { user: TMUX_USER });
|
|
7809
8282
|
if (res.exitCode !== 0) {
|
|
7810
8283
|
throw new Error(failure("capture-pane", session, res.stderr || res.stdout));
|
|
7811
8284
|
}
|
|
@@ -8062,8 +8535,8 @@ function sleep2(ms) {
|
|
|
8062
8535
|
import { log as log28 } from "@clack/prompts";
|
|
8063
8536
|
import { Command as Command23 } from "commander";
|
|
8064
8537
|
import { existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
8065
|
-
import { homedir as
|
|
8066
|
-
import { join as
|
|
8538
|
+
import { homedir as homedir10 } from "os";
|
|
8539
|
+
import { join as join12 } from "path";
|
|
8067
8540
|
var FORK_AGENTS = ["claude", "codex", "opencode"];
|
|
8068
8541
|
var AGENT_COMMAND = {
|
|
8069
8542
|
claude: claudeCommand,
|
|
@@ -8083,12 +8556,12 @@ function resolveSessionArgs(agent, opts) {
|
|
|
8083
8556
|
}
|
|
8084
8557
|
if (opts.session) return ["--resume", opts.session];
|
|
8085
8558
|
if (agent === "codex") return ["--continue"];
|
|
8086
|
-
const dir =
|
|
8559
|
+
const dir = join12(homedir10(), ".claude", "projects", encodeClaudeProjectsDir(opts.workspace));
|
|
8087
8560
|
if (!existsSync4(dir)) return ["--continue"];
|
|
8088
8561
|
const now = Date.now();
|
|
8089
8562
|
const recent = readdirSync(dir).filter((f) => f.endsWith(".jsonl")).map((f) => {
|
|
8090
8563
|
try {
|
|
8091
|
-
return statSync(
|
|
8564
|
+
return statSync(join12(dir, f)).mtimeMs;
|
|
8092
8565
|
} catch {
|
|
8093
8566
|
return 0;
|
|
8094
8567
|
}
|
|
@@ -8166,115 +8639,1003 @@ var forkCommand = new Command23("fork").description(
|
|
|
8166
8639
|
});
|
|
8167
8640
|
|
|
8168
8641
|
// 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
|
|
8642
|
+
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";
|
|
8643
|
+
import { Command as Command25 } from "commander";
|
|
8644
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync, writeFileSync as writeFileSync4 } from "fs";
|
|
8645
|
+
import { homedir as homedir13 } from "os";
|
|
8646
|
+
import { dirname as dirname2, join as join15, resolve as resolve2 } from "path";
|
|
8174
8647
|
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;
|
|
8648
|
+
|
|
8649
|
+
// src/lib/doctor-checks.ts
|
|
8650
|
+
import { accessSync, constants as fsConstants, mkdirSync as mkdirSync3 } from "fs";
|
|
8651
|
+
import { homedir as homedir11 } from "os";
|
|
8652
|
+
import { join as join13 } from "path";
|
|
8653
|
+
import { execa as execa2 } from "execa";
|
|
8654
|
+
var ALL_PROVIDERS = ["docker", "daytona", "hetzner", "vercel"];
|
|
8655
|
+
var NODE_MIN_MAJOR = 20;
|
|
8656
|
+
var NODE_MIN_MINOR = 10;
|
|
8657
|
+
async function probeVersion(bin, args = ["--version"]) {
|
|
8658
|
+
try {
|
|
8659
|
+
const r = await execa2(bin, args, { reject: false });
|
|
8660
|
+
if (r.exitCode !== 0) return null;
|
|
8661
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`.trim().split("\n")[0] ?? "";
|
|
8662
|
+
return out.length > 0 ? out : bin;
|
|
8663
|
+
} catch {
|
|
8664
|
+
return null;
|
|
8205
8665
|
}
|
|
8206
|
-
throw new Error(
|
|
8207
|
-
`could not locate bundled host skills; tried:
|
|
8208
|
-
${candidates.join("\n ")}`
|
|
8209
|
-
);
|
|
8210
8666
|
}
|
|
8211
|
-
function
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
return "managed";
|
|
8216
|
-
}
|
|
8217
|
-
return force ? "forced" : "skip";
|
|
8667
|
+
function parseNodeMajorMinor(v) {
|
|
8668
|
+
const m = /^v?(\d+)\.(\d+)/.exec(v);
|
|
8669
|
+
if (!m) return [0, 0];
|
|
8670
|
+
return [Number(m[1]), Number(m[2])];
|
|
8218
8671
|
}
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8672
|
+
function firstLine(s) {
|
|
8673
|
+
const i = s.indexOf("\n");
|
|
8674
|
+
return i === -1 ? s : s.slice(0, i);
|
|
8675
|
+
}
|
|
8676
|
+
function errSummary(err) {
|
|
8677
|
+
return err instanceof Error ? firstLine(err.message) : String(err);
|
|
8678
|
+
}
|
|
8679
|
+
function checkNode() {
|
|
8680
|
+
const v = process.versions.node;
|
|
8681
|
+
const [maj, min] = parseNodeMajorMinor(v);
|
|
8682
|
+
const ok = maj > NODE_MIN_MAJOR || maj === NODE_MIN_MAJOR && min >= NODE_MIN_MINOR;
|
|
8683
|
+
return {
|
|
8684
|
+
label: "node",
|
|
8685
|
+
status: ok ? "ok" : "fail",
|
|
8686
|
+
detail: ok ? `v${v}` : `v${v} (need >=${String(NODE_MIN_MAJOR)}.${String(NODE_MIN_MINOR)})`,
|
|
8687
|
+
hint: ok ? void 0 : "upgrade Node before continuing"
|
|
8688
|
+
};
|
|
8689
|
+
}
|
|
8690
|
+
function checkPlatform() {
|
|
8691
|
+
return { label: "platform", status: "ok", detail: `${process.platform}/${process.arch}` };
|
|
8692
|
+
}
|
|
8693
|
+
function checkAgentboxHome() {
|
|
8694
|
+
const dir = join13(homedir11(), ".agentbox");
|
|
8226
8695
|
try {
|
|
8227
|
-
|
|
8696
|
+
mkdirSync3(dir, { recursive: true });
|
|
8697
|
+
accessSync(dir, fsConstants.W_OK);
|
|
8698
|
+
return { label: "~/.agentbox", status: "ok", detail: dir };
|
|
8228
8699
|
} catch (err) {
|
|
8229
|
-
|
|
8230
|
-
|
|
8700
|
+
return {
|
|
8701
|
+
label: "~/.agentbox",
|
|
8702
|
+
status: "fail",
|
|
8703
|
+
detail: `not writable: ${err instanceof Error ? err.message : String(err)}`,
|
|
8704
|
+
hint: "check directory permissions"
|
|
8705
|
+
};
|
|
8231
8706
|
}
|
|
8232
|
-
|
|
8233
|
-
|
|
8707
|
+
}
|
|
8708
|
+
async function checkGit() {
|
|
8709
|
+
const v = await probeVersion("git");
|
|
8710
|
+
return v ? { label: "git", status: "ok", detail: v } : {
|
|
8711
|
+
label: "git",
|
|
8712
|
+
status: "warn",
|
|
8713
|
+
detail: "not found",
|
|
8714
|
+
hint: "install git \u2014 required for the workspace git-bundle seed"
|
|
8715
|
+
};
|
|
8716
|
+
}
|
|
8717
|
+
async function checkSsh() {
|
|
8718
|
+
const v = await probeVersion("ssh", ["-V"]);
|
|
8719
|
+
return v ? { label: "ssh", status: "ok", detail: v } : {
|
|
8720
|
+
label: "ssh",
|
|
8721
|
+
status: "warn",
|
|
8722
|
+
detail: "not found",
|
|
8723
|
+
hint: "install ssh \u2014 required for hetzner and cloud attach"
|
|
8724
|
+
};
|
|
8725
|
+
}
|
|
8726
|
+
async function runSystemChecks() {
|
|
8727
|
+
const [git, ssh] = await Promise.all([checkGit(), checkSsh()]);
|
|
8728
|
+
return [checkNode(), checkPlatform(), checkAgentboxHome(), git, ssh];
|
|
8729
|
+
}
|
|
8730
|
+
async function dockerChecks() {
|
|
8731
|
+
const cli = await probeVersion("docker");
|
|
8732
|
+
if (!cli) {
|
|
8733
|
+
return [
|
|
8734
|
+
{
|
|
8735
|
+
label: "docker cli",
|
|
8736
|
+
status: "warn",
|
|
8737
|
+
detail: "not found",
|
|
8738
|
+
hint: "install Docker Desktop, OrbStack, or docker engine"
|
|
8739
|
+
}
|
|
8740
|
+
];
|
|
8741
|
+
}
|
|
8742
|
+
const cliRes = { label: "docker cli", status: "ok", detail: cli };
|
|
8743
|
+
const info = await execa2("docker", ["info"], { reject: false });
|
|
8744
|
+
if (info.exitCode !== 0) {
|
|
8745
|
+
return [
|
|
8746
|
+
cliRes,
|
|
8747
|
+
{
|
|
8748
|
+
label: "docker daemon",
|
|
8749
|
+
status: "warn",
|
|
8750
|
+
detail: "unreachable",
|
|
8751
|
+
hint: "start Docker (Desktop / OrbStack / `systemctl start docker`)"
|
|
8752
|
+
}
|
|
8753
|
+
];
|
|
8754
|
+
}
|
|
8755
|
+
const daemonRes = { label: "docker daemon", status: "ok", detail: "reachable" };
|
|
8756
|
+
const mod = await import("./dist-BQNX7RQE.js");
|
|
8757
|
+
let imgRes;
|
|
8758
|
+
try {
|
|
8759
|
+
const img = await mod.imageInfo(mod.DEFAULT_BOX_IMAGE);
|
|
8760
|
+
imgRes = img.exists ? { label: "box image", status: "ok", detail: `${mod.DEFAULT_BOX_IMAGE} built` } : {
|
|
8761
|
+
label: "box image",
|
|
8762
|
+
status: "warn",
|
|
8763
|
+
detail: `${mod.DEFAULT_BOX_IMAGE} not built`,
|
|
8764
|
+
hint: "run `agentbox prepare --provider docker` (or let the wizard do it)"
|
|
8765
|
+
};
|
|
8766
|
+
} catch (err) {
|
|
8767
|
+
imgRes = {
|
|
8768
|
+
label: "box image",
|
|
8769
|
+
status: "warn",
|
|
8770
|
+
detail: errSummary(err)
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
const volNames = [mod.SHARED_CLAUDE_VOLUME, mod.SHARED_CODEX_VOLUME, mod.SHARED_OPENCODE_VOLUME];
|
|
8774
|
+
const vols = await Promise.all(
|
|
8775
|
+
volNames.map(async (n) => ({ name: n, exists: await mod.volumeExists(n).catch(() => false) }))
|
|
8776
|
+
);
|
|
8777
|
+
const present = vols.filter((v) => v.exists).length;
|
|
8778
|
+
const volRes = {
|
|
8779
|
+
label: "shared volumes",
|
|
8780
|
+
status: "ok",
|
|
8781
|
+
detail: `${String(present)}/${String(vols.length)} present (seeded lazily)`
|
|
8782
|
+
};
|
|
8783
|
+
return [cliRes, daemonRes, imgRes, volRes];
|
|
8784
|
+
}
|
|
8785
|
+
async function daytonaChecks() {
|
|
8786
|
+
try {
|
|
8787
|
+
const mod = await import("./dist-TMHSUVTP.js");
|
|
8788
|
+
const status = await mod.getDaytonaStatus();
|
|
8789
|
+
if (!status.configured) {
|
|
8790
|
+
return [
|
|
8791
|
+
{
|
|
8792
|
+
label: "credentials",
|
|
8793
|
+
status: "warn",
|
|
8794
|
+
detail: status.reason ?? "not configured",
|
|
8795
|
+
hint: "`agentbox daytona login`"
|
|
8796
|
+
}
|
|
8797
|
+
];
|
|
8798
|
+
}
|
|
8799
|
+
const credRes = { label: "credentials", status: "ok", detail: "configured" };
|
|
8800
|
+
const snapRes = status.snapshots.length > 0 ? {
|
|
8801
|
+
label: "base snapshot",
|
|
8802
|
+
status: "ok",
|
|
8803
|
+
detail: `${String(status.snapshots.length)} agentbox snapshot(s)`
|
|
8804
|
+
} : {
|
|
8805
|
+
label: "base snapshot",
|
|
8806
|
+
status: "warn",
|
|
8807
|
+
detail: "none",
|
|
8808
|
+
hint: "`agentbox prepare --provider daytona`"
|
|
8809
|
+
};
|
|
8810
|
+
return [credRes, snapRes];
|
|
8811
|
+
} catch (err) {
|
|
8812
|
+
return [
|
|
8813
|
+
{
|
|
8814
|
+
label: "credentials",
|
|
8815
|
+
status: "warn",
|
|
8816
|
+
detail: errSummary(err)
|
|
8817
|
+
}
|
|
8818
|
+
];
|
|
8819
|
+
}
|
|
8820
|
+
}
|
|
8821
|
+
async function hetznerChecks() {
|
|
8822
|
+
try {
|
|
8823
|
+
const mod = await import("./dist-5FQGYRW5.js");
|
|
8824
|
+
const cred = mod.readHetznerCredStatus();
|
|
8825
|
+
const credRes = cred.source === "none" ? {
|
|
8826
|
+
label: "credentials",
|
|
8827
|
+
status: "warn",
|
|
8828
|
+
detail: "HCLOUD_TOKEN not set",
|
|
8829
|
+
hint: "`agentbox hetzner login`"
|
|
8830
|
+
} : { label: "credentials", status: "ok", detail: `token from ${cred.source}` };
|
|
8831
|
+
const prepared = mod.readPreparedState();
|
|
8832
|
+
const snapRes = prepared.base?.imageId ? {
|
|
8833
|
+
label: "base snapshot",
|
|
8834
|
+
status: "ok",
|
|
8835
|
+
detail: `image ${String(prepared.base.imageId)} (${prepared.base.cliVersion ?? "\u2014"})`
|
|
8836
|
+
} : {
|
|
8837
|
+
label: "base snapshot",
|
|
8838
|
+
status: "warn",
|
|
8839
|
+
detail: "not baked",
|
|
8840
|
+
hint: "`agentbox prepare --provider hetzner`"
|
|
8841
|
+
};
|
|
8842
|
+
return [credRes, snapRes];
|
|
8843
|
+
} catch (err) {
|
|
8844
|
+
return [
|
|
8845
|
+
{
|
|
8846
|
+
label: "credentials",
|
|
8847
|
+
status: "warn",
|
|
8848
|
+
detail: errSummary(err)
|
|
8849
|
+
}
|
|
8850
|
+
];
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8853
|
+
async function vercelChecks() {
|
|
8854
|
+
try {
|
|
8855
|
+
const mod = await import("./dist-PZW3GWWU.js");
|
|
8856
|
+
const cred = mod.readVercelCredStatus();
|
|
8857
|
+
const credRes = cred.auth === "none" ? {
|
|
8858
|
+
label: "credentials",
|
|
8859
|
+
status: "warn",
|
|
8860
|
+
detail: "not configured",
|
|
8861
|
+
hint: "`agentbox vercel login`"
|
|
8862
|
+
} : {
|
|
8863
|
+
label: "credentials",
|
|
8864
|
+
status: "ok",
|
|
8865
|
+
detail: `${cred.auth} (${cred.source})`
|
|
8866
|
+
};
|
|
8867
|
+
const prepared = mod.readPreparedState();
|
|
8868
|
+
const snapRes = prepared.base?.snapshotId ? {
|
|
8869
|
+
label: "base snapshot",
|
|
8870
|
+
status: "ok",
|
|
8871
|
+
detail: `${prepared.base.snapshotId.slice(0, 16)}\u2026 (${prepared.base.cliVersion ?? "\u2014"})`
|
|
8872
|
+
} : {
|
|
8873
|
+
label: "base snapshot",
|
|
8874
|
+
status: "warn",
|
|
8875
|
+
detail: "not baked",
|
|
8876
|
+
hint: "`agentbox prepare --provider vercel`"
|
|
8877
|
+
};
|
|
8878
|
+
return [credRes, snapRes];
|
|
8879
|
+
} catch (err) {
|
|
8880
|
+
return [
|
|
8881
|
+
{
|
|
8882
|
+
label: "credentials",
|
|
8883
|
+
status: "warn",
|
|
8884
|
+
detail: errSummary(err)
|
|
8885
|
+
}
|
|
8886
|
+
];
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
async function runProviderChecks(name) {
|
|
8890
|
+
let results;
|
|
8891
|
+
switch (name) {
|
|
8892
|
+
case "docker":
|
|
8893
|
+
results = await dockerChecks();
|
|
8894
|
+
break;
|
|
8895
|
+
case "daytona":
|
|
8896
|
+
results = await daytonaChecks();
|
|
8897
|
+
break;
|
|
8898
|
+
case "hetzner":
|
|
8899
|
+
results = await hetznerChecks();
|
|
8900
|
+
break;
|
|
8901
|
+
case "vercel":
|
|
8902
|
+
results = await vercelChecks();
|
|
8903
|
+
break;
|
|
8904
|
+
}
|
|
8905
|
+
return { title: name, results };
|
|
8906
|
+
}
|
|
8907
|
+
async function runAllChecks() {
|
|
8908
|
+
const sys = { title: "system", results: await runSystemChecks() };
|
|
8909
|
+
const providerGroups = await Promise.all(ALL_PROVIDERS.map((n) => runProviderChecks(n)));
|
|
8910
|
+
return [sys, ...providerGroups];
|
|
8911
|
+
}
|
|
8912
|
+
function worstInResults(results) {
|
|
8913
|
+
let worst = "ok";
|
|
8914
|
+
for (const r of results) {
|
|
8915
|
+
if (r.status === "fail") return "fail";
|
|
8916
|
+
if (r.status === "warn") worst = "warn";
|
|
8917
|
+
}
|
|
8918
|
+
return worst;
|
|
8919
|
+
}
|
|
8920
|
+
function worstStatus(groups) {
|
|
8921
|
+
let worst = "ok";
|
|
8922
|
+
for (const g of groups) {
|
|
8923
|
+
const w = worstInResults(g.results);
|
|
8924
|
+
if (w === "fail") return "fail";
|
|
8925
|
+
if (w === "warn") worst = "warn";
|
|
8926
|
+
}
|
|
8927
|
+
return worst;
|
|
8928
|
+
}
|
|
8929
|
+
function summaryToken(group) {
|
|
8930
|
+
const worst = worstInResults(group.results);
|
|
8931
|
+
if (group.title === "system") {
|
|
8932
|
+
if (worst === "fail") return "system FAIL";
|
|
8933
|
+
if (worst === "warn") return "system warn";
|
|
8934
|
+
return "system ok";
|
|
8935
|
+
}
|
|
8936
|
+
if (worst === "fail") return `${group.title} FAIL`;
|
|
8937
|
+
if (worst === "warn") {
|
|
8938
|
+
const cred = group.results.find((r) => r.label === "credentials");
|
|
8939
|
+
if (cred && cred.status === "warn") return `${group.title} login needed`;
|
|
8940
|
+
return `${group.title} not prepared`;
|
|
8941
|
+
}
|
|
8942
|
+
return `${group.title} ready`;
|
|
8943
|
+
}
|
|
8944
|
+
var C_GREEN = "\x1B[32m";
|
|
8945
|
+
var C_YELLOW = "\x1B[33m";
|
|
8946
|
+
var C_RED = "\x1B[31m";
|
|
8947
|
+
var C_RESET = "\x1B[0m";
|
|
8948
|
+
var COLOR = !process.env.NO_COLOR;
|
|
8949
|
+
function statusMarker(s) {
|
|
8950
|
+
const glyph = s === "ok" ? "\u2713" : s === "warn" ? "\u26A0" : "\u2717";
|
|
8951
|
+
if (!COLOR) return glyph;
|
|
8952
|
+
const color = s === "ok" ? C_GREEN : s === "warn" ? C_YELLOW : C_RED;
|
|
8953
|
+
return `${color}${glyph}${C_RESET}`;
|
|
8954
|
+
}
|
|
8955
|
+
function formatCompact(groups) {
|
|
8956
|
+
return groups.map((g) => `${statusMarker(worstInResults(g.results))} ${summaryToken(g)}`).join(" \xB7 ");
|
|
8957
|
+
}
|
|
8958
|
+
function pad2(s, width) {
|
|
8959
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
8960
|
+
}
|
|
8961
|
+
function statusBadge(s) {
|
|
8962
|
+
if (s === "ok") return "[ ok ]";
|
|
8963
|
+
if (s === "warn") return "[warn]";
|
|
8964
|
+
return "[FAIL]";
|
|
8965
|
+
}
|
|
8966
|
+
function formatDetailed(groups) {
|
|
8967
|
+
const lines = [];
|
|
8968
|
+
for (const g of groups) {
|
|
8969
|
+
if (lines.length > 0) lines.push("");
|
|
8970
|
+
lines.push(`${g.title}:`);
|
|
8971
|
+
for (const r of g.results) {
|
|
8972
|
+
const badge = statusBadge(r.status);
|
|
8973
|
+
const tail = r.hint ? ` (${r.hint})` : "";
|
|
8974
|
+
lines.push(` ${badge} ${pad2(r.label, 18)} ${r.detail}${tail}`);
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8977
|
+
return lines;
|
|
8978
|
+
}
|
|
8979
|
+
|
|
8980
|
+
// src/lib/first-run.ts
|
|
8981
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
8982
|
+
import { homedir as homedir12 } from "os";
|
|
8983
|
+
import { dirname, join as join14 } from "path";
|
|
8984
|
+
var MARKER_VERSION = 1;
|
|
8985
|
+
function setupMarkerPath() {
|
|
8986
|
+
return join14(homedir12(), ".agentbox", "setup-complete.json");
|
|
8987
|
+
}
|
|
8988
|
+
function isFirstRun() {
|
|
8989
|
+
return !existsSync5(setupMarkerPath());
|
|
8990
|
+
}
|
|
8991
|
+
function markSetupComplete(provider) {
|
|
8992
|
+
const path = setupMarkerPath();
|
|
8993
|
+
mkdirSync4(dirname(path), { recursive: true });
|
|
8994
|
+
const body = {
|
|
8995
|
+
version: MARKER_VERSION,
|
|
8996
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8997
|
+
provider
|
|
8998
|
+
};
|
|
8999
|
+
writeFileSync3(path, JSON.stringify(body, null, 2) + "\n");
|
|
9000
|
+
}
|
|
9001
|
+
|
|
9002
|
+
// src/commands/prepare.ts
|
|
9003
|
+
import { intro as intro5, log as log29, spinner as spinner7 } from "@clack/prompts";
|
|
9004
|
+
import { Command as Command24 } from "commander";
|
|
9005
|
+
async function dockerStatus() {
|
|
9006
|
+
let img;
|
|
9007
|
+
try {
|
|
9008
|
+
img = await imageInfo(DEFAULT_BOX_IMAGE);
|
|
9009
|
+
} catch {
|
|
9010
|
+
return { daemon: "unreachable", volumes: [] };
|
|
9011
|
+
}
|
|
9012
|
+
const names = [SHARED_CLAUDE_VOLUME, SHARED_CODEX_VOLUME, SHARED_OPENCODE_VOLUME];
|
|
9013
|
+
const volumes = await Promise.all(
|
|
9014
|
+
names.map(async (name) => ({ name, exists: await volumeExists(name).catch(() => false) }))
|
|
9015
|
+
);
|
|
9016
|
+
return { daemon: "reachable", image: img, volumes };
|
|
9017
|
+
}
|
|
9018
|
+
function humanBytes(n) {
|
|
9019
|
+
if (n === void 0 || !Number.isFinite(n)) return "\u2014";
|
|
9020
|
+
if (n >= 1024 ** 3) return `${(n / 1024 ** 3).toFixed(2)} GB`;
|
|
9021
|
+
if (n >= 1024 ** 2) return `${(n / 1024 ** 2).toFixed(1)} MB`;
|
|
9022
|
+
if (n >= 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
9023
|
+
return `${String(n)} B`;
|
|
9024
|
+
}
|
|
9025
|
+
function humanAge(iso) {
|
|
9026
|
+
if (!iso) return "\u2014";
|
|
9027
|
+
const t = Date.parse(iso);
|
|
9028
|
+
if (!Number.isFinite(t)) return iso;
|
|
9029
|
+
const ageSec = Math.max(0, (Date.now() - t) / 1e3);
|
|
9030
|
+
if (ageSec < 60) return `${ageSec.toFixed(0)}s ago`;
|
|
9031
|
+
if (ageSec < 3600) return `${(ageSec / 60).toFixed(0)}m ago`;
|
|
9032
|
+
if (ageSec < 86400) return `${(ageSec / 3600).toFixed(1)}h ago`;
|
|
9033
|
+
return `${(ageSec / 86400).toFixed(1)}d ago`;
|
|
9034
|
+
}
|
|
9035
|
+
function pad3(s, width) {
|
|
9036
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
9037
|
+
}
|
|
9038
|
+
async function renderDocker(status) {
|
|
9039
|
+
const out = ["docker:"];
|
|
9040
|
+
if (status.daemon === "unreachable") {
|
|
9041
|
+
out.push(" docker daemon unreachable (is Docker running?)");
|
|
9042
|
+
return out;
|
|
9043
|
+
}
|
|
9044
|
+
if (!status.image?.exists) {
|
|
9045
|
+
out.push(
|
|
9046
|
+
` image ${DEFAULT_BOX_IMAGE} (not built \u2014 run \`agentbox prepare --provider docker\`)`
|
|
9047
|
+
);
|
|
9048
|
+
} else {
|
|
9049
|
+
out.push(
|
|
9050
|
+
` image ${pad3(DEFAULT_BOX_IMAGE, 30)} ${pad3(humanBytes(status.image.sizeBytes), 10)} built ${humanAge(status.image.createdAt)}`
|
|
9051
|
+
);
|
|
9052
|
+
}
|
|
9053
|
+
for (const v of status.volumes) {
|
|
9054
|
+
if (v.exists) {
|
|
9055
|
+
out.push(` vol ${pad3(v.name, 30)} present`);
|
|
9056
|
+
} else {
|
|
9057
|
+
out.push(
|
|
9058
|
+
` vol ${pad3(v.name, 30)} (none \u2014 seeded lazily on first \`agentbox claude/codex/opencode\`)`
|
|
9059
|
+
);
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
return out;
|
|
9063
|
+
}
|
|
9064
|
+
async function daytonaStatus() {
|
|
9065
|
+
try {
|
|
9066
|
+
const mod = await import("./dist-TMHSUVTP.js");
|
|
9067
|
+
return await mod.getDaytonaStatus();
|
|
9068
|
+
} catch (err) {
|
|
9069
|
+
return {
|
|
9070
|
+
configured: false,
|
|
9071
|
+
reason: err instanceof Error ? err.message.split("\n")[0] : String(err)
|
|
9072
|
+
};
|
|
9073
|
+
}
|
|
9074
|
+
}
|
|
9075
|
+
function renderDaytona(status, pinnedImage) {
|
|
9076
|
+
const out = ["daytona:"];
|
|
9077
|
+
if (!status.configured) {
|
|
9078
|
+
out.push(
|
|
9079
|
+
` (not configured \u2014 \`agentbox daytona login\` to set up${status.reason ? `; ${status.reason}` : ""})`
|
|
9080
|
+
);
|
|
9081
|
+
return out;
|
|
9082
|
+
}
|
|
9083
|
+
if (status.reason) out.push(` warn: ${status.reason}`);
|
|
9084
|
+
if (status.snapshots.length === 0) {
|
|
9085
|
+
out.push(" no agentbox snapshots \u2014 run `agentbox prepare --provider daytona`");
|
|
9086
|
+
} else {
|
|
9087
|
+
for (const s of status.snapshots) {
|
|
9088
|
+
const sizeStr = s.sizeGb !== void 0 ? `${s.sizeGb.toFixed(2)} GB` : "\u2014";
|
|
9089
|
+
const pinned = pinnedImage && pinnedImage === s.name ? " (pinned in project)" : "";
|
|
9090
|
+
const tail = s.state === "error" && s.errorReason ? ` error: ${s.errorReason.slice(0, 80)}` : ` ${humanAge(s.createdAt)}`;
|
|
9091
|
+
out.push(
|
|
9092
|
+
` snap ${pad3(s.name, 40)} ${pad3(s.state ?? "\u2014", 10)} ${pad3(sizeStr, 10)}${tail}${pinned}`
|
|
9093
|
+
);
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
if (status.volumes.length === 0) {
|
|
9097
|
+
out.push(" no agentbox volumes \u2014 created lazily on first cloud `agentbox create`");
|
|
9098
|
+
} else {
|
|
9099
|
+
for (const v of status.volumes) {
|
|
9100
|
+
const last = v.lastUsedAt ? ` last used ${humanAge(v.lastUsedAt)}` : "";
|
|
9101
|
+
out.push(` vol ${pad3(v.name, 40)} ${pad3(v.state ?? "\u2014", 10)}${last}`);
|
|
9102
|
+
}
|
|
9103
|
+
}
|
|
9104
|
+
return out;
|
|
9105
|
+
}
|
|
9106
|
+
async function showStatus(opts) {
|
|
9107
|
+
const cfg = await loadEffectiveConfig(process.cwd()).catch(() => null);
|
|
9108
|
+
const pinnedRaw = cfg?.effective.box.image;
|
|
9109
|
+
const pinned = typeof pinnedRaw === "string" && pinnedRaw.length > 0 && pinnedRaw !== DEFAULT_BOX_IMAGE ? pinnedRaw : void 0;
|
|
9110
|
+
const lines = [];
|
|
9111
|
+
const wantDocker = !opts.onlyProvider || opts.onlyProvider === "docker";
|
|
9112
|
+
const wantDaytona = !opts.onlyProvider || opts.onlyProvider === "daytona";
|
|
9113
|
+
if (wantDocker) {
|
|
9114
|
+
const status = await dockerStatus();
|
|
9115
|
+
lines.push(...await renderDocker(status));
|
|
9116
|
+
}
|
|
9117
|
+
if (wantDaytona) {
|
|
9118
|
+
if (lines.length > 0) lines.push("");
|
|
9119
|
+
const status = await daytonaStatus();
|
|
9120
|
+
lines.push(...renderDaytona(status, pinned));
|
|
9121
|
+
}
|
|
9122
|
+
if (pinned) {
|
|
9123
|
+
lines.push("");
|
|
9124
|
+
lines.push(`project pin: box.image = ${pinned}`);
|
|
9125
|
+
}
|
|
9126
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
9127
|
+
}
|
|
9128
|
+
async function runPrepare(providerName, opts = {}) {
|
|
9129
|
+
if (!isKnownProvider(providerName)) {
|
|
9130
|
+
process.stderr.write("error: --provider must be one of: docker, daytona, hetzner, vercel\n");
|
|
9131
|
+
process.exit(1);
|
|
9132
|
+
}
|
|
9133
|
+
if (providerName === "daytona" && !opts.yes && process.stdin.isTTY) {
|
|
9134
|
+
process.stdout.write(
|
|
9135
|
+
"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"
|
|
9136
|
+
);
|
|
9137
|
+
}
|
|
9138
|
+
const provider = await getProvider(providerName);
|
|
9139
|
+
if (typeof provider.prepare !== "function") {
|
|
9140
|
+
log29.error(`provider '${providerName}' does not implement prepare`);
|
|
9141
|
+
process.exit(1);
|
|
9142
|
+
}
|
|
9143
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9144
|
+
const sp = spinner7();
|
|
9145
|
+
sp.start(`preparing ${providerName}\u2026`);
|
|
9146
|
+
try {
|
|
9147
|
+
const result = await provider.prepare({
|
|
9148
|
+
name: opts.name,
|
|
9149
|
+
hostWorkspace: cwd,
|
|
9150
|
+
force: opts.force,
|
|
9151
|
+
onLog: (line) => sp.message(line.slice(0, 80))
|
|
9152
|
+
});
|
|
9153
|
+
if (result.snapshotName !== void 0) {
|
|
9154
|
+
sp.stop(`prepared ${providerName}: snapshot '${result.snapshotName}'`);
|
|
9155
|
+
try {
|
|
9156
|
+
const written = await setConfigValue("project", "box.image", result.snapshotName, cwd);
|
|
9157
|
+
log29.success(`box.image = ${result.snapshotName} (written to ${written.path})`);
|
|
9158
|
+
} catch (err) {
|
|
9159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9160
|
+
log29.warn(
|
|
9161
|
+
`prepared snapshot '${result.snapshotName}', but failed to pin it into the project config: ${msg}
|
|
9162
|
+
Run \`agentbox config set --project box.image ${result.snapshotName}\` manually.`
|
|
9163
|
+
);
|
|
9164
|
+
}
|
|
9165
|
+
} else {
|
|
9166
|
+
sp.stop(`${providerName.slice(0, 1).toUpperCase() + providerName.slice(1)} provider ready`);
|
|
9167
|
+
}
|
|
9168
|
+
if (!opts.suppressStatus) {
|
|
9169
|
+
process.stdout.write("\n");
|
|
9170
|
+
await showStatus({ onlyProvider: providerName });
|
|
9171
|
+
}
|
|
9172
|
+
if (!opts.suppressTip) {
|
|
9173
|
+
log29.info(
|
|
9174
|
+
"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"
|
|
9175
|
+
);
|
|
9176
|
+
}
|
|
9177
|
+
} catch (err) {
|
|
9178
|
+
sp.stop(`prepare failed: ${describeError(err)}`);
|
|
9179
|
+
throw err;
|
|
9180
|
+
}
|
|
9181
|
+
}
|
|
9182
|
+
var prepareCommand = new Command24("prepare").description(
|
|
9183
|
+
"Build base sandbox images / snapshots, or show what is already prepared across providers."
|
|
9184
|
+
).option(
|
|
9185
|
+
"-p, --provider <name>",
|
|
9186
|
+
"provider to prepare (docker | daytona | hetzner | vercel). Omit for status-only."
|
|
9187
|
+
).option("-n, --name <name>", "snapshot name (Daytona only; default: agentbox-base-<timestamp>)").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) => {
|
|
9188
|
+
if (!opts.provider || opts.status) {
|
|
9189
|
+
await showStatus({});
|
|
9190
|
+
return;
|
|
9191
|
+
}
|
|
9192
|
+
const providerName = opts.provider.trim();
|
|
9193
|
+
intro5(`preparing ${providerName} base image`);
|
|
9194
|
+
await runPrepare(providerName, {
|
|
9195
|
+
name: opts.name,
|
|
9196
|
+
force: opts.force,
|
|
9197
|
+
yes: opts.yes
|
|
9198
|
+
});
|
|
9199
|
+
});
|
|
9200
|
+
function describeError(err) {
|
|
9201
|
+
if (!(err instanceof Error)) return String(err);
|
|
9202
|
+
const parts = [err.message];
|
|
9203
|
+
let cause = err.cause;
|
|
9204
|
+
for (let i = 0; i < 5 && cause; i++) {
|
|
9205
|
+
if (cause instanceof Error) {
|
|
9206
|
+
parts.push(`caused by: ${cause.message}`);
|
|
9207
|
+
const code = cause.code;
|
|
9208
|
+
if (typeof code === "string") parts.push(`(${code})`);
|
|
9209
|
+
cause = cause.cause;
|
|
9210
|
+
} else if (typeof cause === "object") {
|
|
9211
|
+
parts.push(`caused by: ${JSON.stringify(cause)}`);
|
|
9212
|
+
break;
|
|
9213
|
+
} else {
|
|
9214
|
+
parts.push(`caused by: ${String(cause)}`);
|
|
9215
|
+
break;
|
|
9216
|
+
}
|
|
9217
|
+
}
|
|
9218
|
+
return parts.join(" \u2014 ");
|
|
9219
|
+
}
|
|
9220
|
+
|
|
9221
|
+
// src/commands/install.ts
|
|
9222
|
+
var MANAGED_SENTINEL = "<!-- agentbox-managed:v1 -->";
|
|
9223
|
+
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";
|
|
9224
|
+
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";
|
|
9225
|
+
var LOGO_WIDTH = LOGO_L1.length;
|
|
9226
|
+
var BANNER = (() => {
|
|
9227
|
+
const art = `${LOGO_L1}
|
|
9228
|
+
${LOGO_L2}`;
|
|
9229
|
+
const tinted = process.env.NO_COLOR ? art : `\x1B[38;5;39m${art}\x1B[0m`;
|
|
9230
|
+
return `
|
|
9231
|
+
${tinted}
|
|
9232
|
+
|
|
9233
|
+
`;
|
|
9234
|
+
})();
|
|
9235
|
+
var SYNC_BEGIN2 = "\x1B[?2026h";
|
|
9236
|
+
var SYNC_END2 = "\x1B[?2026l";
|
|
9237
|
+
var HIDE_CURSOR = "\x1B[?25l";
|
|
9238
|
+
var SHOW_CURSOR = "\x1B[?25h";
|
|
9239
|
+
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
9240
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9241
|
+
function shineColor(dist) {
|
|
9242
|
+
const d = Math.abs(dist);
|
|
9243
|
+
if (d === 0) return 231;
|
|
9244
|
+
if (d === 1) return 159;
|
|
9245
|
+
if (d === 2) return 81;
|
|
9246
|
+
return 39;
|
|
9247
|
+
}
|
|
9248
|
+
function paintLine(line, center) {
|
|
9249
|
+
let out = "";
|
|
9250
|
+
let prev = -1;
|
|
9251
|
+
for (let i = 0; i < line.length; i++) {
|
|
9252
|
+
const ch = line[i];
|
|
9253
|
+
const c = shineColor(i - center);
|
|
9254
|
+
if (c !== prev) {
|
|
9255
|
+
out += `\x1B[38;5;${String(c)}m`;
|
|
9256
|
+
prev = c;
|
|
9257
|
+
}
|
|
9258
|
+
out += ch;
|
|
9259
|
+
}
|
|
9260
|
+
return out + "\x1B[0m";
|
|
9261
|
+
}
|
|
9262
|
+
async function animateBanner() {
|
|
9263
|
+
if (process.env.NO_COLOR || process.env.CI || process.env.AGENTBOX_NO_ANIM || !process.stdout.isTTY) {
|
|
9264
|
+
process.stdout.write(BANNER);
|
|
9265
|
+
return;
|
|
9266
|
+
}
|
|
9267
|
+
const restoreCursor = () => {
|
|
9268
|
+
process.stdout.write(SHOW_CURSOR);
|
|
9269
|
+
};
|
|
9270
|
+
process.once("exit", restoreCursor);
|
|
9271
|
+
const onSigint = () => {
|
|
9272
|
+
restoreCursor();
|
|
9273
|
+
process.exit(130);
|
|
9274
|
+
};
|
|
9275
|
+
process.once("SIGINT", onSigint);
|
|
9276
|
+
const frameMs = 45;
|
|
9277
|
+
const start = -3;
|
|
9278
|
+
const end = LOGO_WIDTH + 2;
|
|
9279
|
+
const down = 4;
|
|
9280
|
+
process.stdout.write(`
|
|
9281
|
+
${HIDE_CURSOR}`);
|
|
9282
|
+
process.stdout.write("\n".repeat(down) + `\x1B[${String(down)}A`);
|
|
9283
|
+
const statusLine2 = (spin) => ` \x1B[38;5;51m${spin}\x1B[0m \x1B[38;5;245mChecking system\u2026\x1B[0m`;
|
|
9284
|
+
for (let center = start; center <= end; center++) {
|
|
9285
|
+
const spin = SPIN[Math.floor((center - start) / 2) % SPIN.length] ?? SPIN[0];
|
|
9286
|
+
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
|
|
9287
|
+
statusLine2(spin) + "\x1B[3A\r" + // back up to the logo's first row
|
|
9288
|
+
SYNC_END2;
|
|
9289
|
+
process.stdout.write(frame);
|
|
9290
|
+
await sleep3(frameMs);
|
|
9291
|
+
}
|
|
9292
|
+
process.stdout.write(SYNC_BEGIN2 + `\x1B[38;5;39m${LOGO_L1}
|
|
9293
|
+
${LOGO_L2}\x1B[0m` + SYNC_END2);
|
|
9294
|
+
await sleep3(250);
|
|
9295
|
+
process.stdout.write(SYNC_BEGIN2 + "\n\x1B[2K\n\x1B[2K" + SHOW_CURSOR + SYNC_END2);
|
|
9296
|
+
process.removeListener("exit", restoreCursor);
|
|
9297
|
+
process.removeListener("SIGINT", onSigint);
|
|
9298
|
+
}
|
|
9299
|
+
var LEGACY_INFO_MARKER = "Drive AgentBox from the host:";
|
|
9300
|
+
function installTargets() {
|
|
9301
|
+
const home = homedir13();
|
|
9302
|
+
const claudeSkills = join15(home, ".claude", "skills");
|
|
9303
|
+
return [
|
|
9304
|
+
{ src: join15("agentbox", "SKILL.md"), dest: join15(claudeSkills, "agentbox", "SKILL.md") },
|
|
9305
|
+
{
|
|
9306
|
+
src: join15("agentbox-info", "SKILL.md"),
|
|
9307
|
+
dest: join15(claudeSkills, "agentbox-info", "SKILL.md")
|
|
9308
|
+
},
|
|
9309
|
+
{
|
|
9310
|
+
src: join15("codex", "agentbox.md"),
|
|
9311
|
+
dest: join15(home, ".codex", "prompts", "agentbox.md"),
|
|
9312
|
+
gateDir: join15(home, ".codex")
|
|
9313
|
+
},
|
|
9314
|
+
{
|
|
9315
|
+
src: join15("opencode", "agentbox.md"),
|
|
9316
|
+
dest: join15(home, ".config", "opencode", "commands", "agentbox.md"),
|
|
9317
|
+
gateDir: join15(home, ".config", "opencode")
|
|
9318
|
+
}
|
|
9319
|
+
];
|
|
9320
|
+
}
|
|
9321
|
+
function resolveHostSkillsDir() {
|
|
9322
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
9323
|
+
const candidates = [
|
|
9324
|
+
resolve2(here, "..", "share", "host-skills"),
|
|
9325
|
+
resolve2(here, "..", "..", "share", "host-skills")
|
|
9326
|
+
];
|
|
9327
|
+
for (const c of candidates) {
|
|
9328
|
+
if (existsSync6(c)) return c;
|
|
9329
|
+
}
|
|
9330
|
+
throw new Error(`could not locate bundled host skills; tried:
|
|
9331
|
+
${candidates.join("\n ")}`);
|
|
9332
|
+
}
|
|
9333
|
+
function writableReason(target, force) {
|
|
9334
|
+
if (!existsSync6(target)) return "new";
|
|
9335
|
+
const existing = readFileSync(target, "utf8");
|
|
9336
|
+
if (existing.includes(MANAGED_SENTINEL) || existing.includes(LEGACY_INFO_MARKER)) {
|
|
9337
|
+
return "managed";
|
|
9338
|
+
}
|
|
9339
|
+
return force ? "forced" : "skip";
|
|
9340
|
+
}
|
|
9341
|
+
function installHostSkills(opts = {}) {
|
|
9342
|
+
const force = opts.force === true;
|
|
9343
|
+
const dryRun = opts.dryRun === true;
|
|
9344
|
+
const quiet = opts.quiet === true;
|
|
9345
|
+
const srcDir = resolveHostSkillsDir();
|
|
9346
|
+
const written = [];
|
|
9347
|
+
const blocked = [];
|
|
9348
|
+
let skipped = 0;
|
|
8234
9349
|
for (const t of installTargets()) {
|
|
8235
|
-
const src =
|
|
8236
|
-
if (!
|
|
8237
|
-
|
|
9350
|
+
const src = join15(srcDir, t.src);
|
|
9351
|
+
if (!existsSync6(src)) {
|
|
9352
|
+
if (!quiet) log30.warn(`bundled file missing (skipped): ${src}`);
|
|
8238
9353
|
skipped++;
|
|
8239
9354
|
continue;
|
|
8240
9355
|
}
|
|
8241
|
-
if (t.gateDir && !
|
|
9356
|
+
if (t.gateDir && !existsSync6(t.gateDir)) continue;
|
|
8242
9357
|
const reason = writableReason(t.dest, force);
|
|
8243
9358
|
if (reason === "skip") {
|
|
8244
|
-
|
|
9359
|
+
if (!quiet) log30.warn(`user-modified file at ${t.dest}, skipping; pass --force to overwrite`);
|
|
9360
|
+
blocked.push(t.dest);
|
|
8245
9361
|
skipped++;
|
|
8246
9362
|
continue;
|
|
8247
9363
|
}
|
|
8248
9364
|
if (dryRun) {
|
|
8249
|
-
|
|
9365
|
+
if (!quiet) log30.info(`would write ${t.dest} (${reason})`);
|
|
8250
9366
|
written.push(t.dest);
|
|
8251
9367
|
continue;
|
|
8252
9368
|
}
|
|
8253
|
-
|
|
8254
|
-
|
|
9369
|
+
mkdirSync5(dirname2(t.dest), { recursive: true });
|
|
9370
|
+
writeFileSync4(t.dest, readFileSync(src, "utf8"));
|
|
8255
9371
|
written.push(t.dest);
|
|
8256
9372
|
}
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
9373
|
+
return { written, skipped, blocked };
|
|
9374
|
+
}
|
|
9375
|
+
var PROVIDER_HINTS = {
|
|
9376
|
+
docker: "builds a ~1GB local image; no login needed",
|
|
9377
|
+
hetzner: "paste an API token from the Hetzner Console",
|
|
9378
|
+
daytona: "approve a browser sign-in link",
|
|
9379
|
+
vercel: "installs the Vercel sandbox CLI, then a browser sign-in"
|
|
9380
|
+
};
|
|
9381
|
+
var PROVIDER_LABEL = {
|
|
9382
|
+
docker: "Docker (local)",
|
|
9383
|
+
hetzner: "Hetzner (cloud VPS)",
|
|
9384
|
+
daytona: "Daytona (cloud sandbox)",
|
|
9385
|
+
vercel: "Vercel (cloud microVM)"
|
|
9386
|
+
};
|
|
9387
|
+
function ensureTty() {
|
|
9388
|
+
if (process.stdin.isTTY && process.stdout.isTTY) return true;
|
|
9389
|
+
process.stderr.write(
|
|
9390
|
+
"agentbox install: an interactive terminal is required. Run `agentbox <provider> login` and `agentbox prepare --provider <name>` instead.\n"
|
|
9391
|
+
);
|
|
9392
|
+
return false;
|
|
9393
|
+
}
|
|
9394
|
+
async function runProviderLogin(name) {
|
|
9395
|
+
if (name === "docker") return true;
|
|
9396
|
+
if (name === "daytona") {
|
|
9397
|
+
const mod2 = await import("./dist-TMHSUVTP.js");
|
|
9398
|
+
const status2 = await mod2.getDaytonaStatus();
|
|
9399
|
+
if (status2.configured) {
|
|
9400
|
+
log30.info("daytona: already configured");
|
|
9401
|
+
const rotate = await confirm14({ message: "Re-authenticate Daytona?", initialValue: false });
|
|
9402
|
+
if (isCancel15(rotate)) return false;
|
|
9403
|
+
if (rotate) await mod2.ensureDaytonaCredentials({ force: true });
|
|
9404
|
+
return true;
|
|
9405
|
+
}
|
|
9406
|
+
await mod2.ensureDaytonaCredentials();
|
|
9407
|
+
return true;
|
|
9408
|
+
}
|
|
9409
|
+
if (name === "hetzner") {
|
|
9410
|
+
const mod2 = await import("./dist-5FQGYRW5.js");
|
|
9411
|
+
const status2 = mod2.readHetznerCredStatus();
|
|
9412
|
+
if (status2.source !== "none") {
|
|
9413
|
+
log30.info("hetzner: already configured");
|
|
9414
|
+
const rotate = await confirm14({ message: "Re-authenticate Hetzner?", initialValue: false });
|
|
9415
|
+
if (isCancel15(rotate)) return false;
|
|
9416
|
+
if (rotate) await mod2.ensureHetznerCredentials({ force: true });
|
|
9417
|
+
return true;
|
|
9418
|
+
}
|
|
9419
|
+
await mod2.ensureHetznerCredentials();
|
|
9420
|
+
return true;
|
|
9421
|
+
}
|
|
9422
|
+
const mod = await import("./dist-PZW3GWWU.js");
|
|
9423
|
+
const status = mod.readVercelCredStatus();
|
|
9424
|
+
if (status.auth !== "none") {
|
|
9425
|
+
log30.info(`vercel: already configured (${status.auth})`);
|
|
9426
|
+
const rotate = await confirm14({ message: "Re-authenticate Vercel?", initialValue: false });
|
|
9427
|
+
if (isCancel15(rotate)) return false;
|
|
9428
|
+
if (rotate) await mod.ensureVercelCredentials({ force: true });
|
|
9429
|
+
return true;
|
|
9430
|
+
}
|
|
9431
|
+
await mod.ensureVercelCredentials();
|
|
9432
|
+
return true;
|
|
9433
|
+
}
|
|
9434
|
+
function tutorialBody(provider) {
|
|
9435
|
+
const startCmd = provider === "docker" ? "agentbox claude" : `agentbox ${provider} claude`;
|
|
9436
|
+
return `Get started:
|
|
9437
|
+
${startCmd} # for claude, codex, opencode
|
|
9438
|
+
-> Setup wizard? -> Yes # install dependencies and setup agentbox.yaml
|
|
9439
|
+
-> Ctrl+a d # to detach from the box and leave claude running
|
|
9440
|
+
agentbox claude attach 1 # resume it later
|
|
9441
|
+
agentbox install # to set up another provider`;
|
|
9442
|
+
}
|
|
9443
|
+
var KNOWN_PROVIDERS = ["docker", "hetzner", "daytona", "vercel"];
|
|
9444
|
+
function isProviderName(s) {
|
|
9445
|
+
return KNOWN_PROVIDERS.includes(s);
|
|
9446
|
+
}
|
|
9447
|
+
async function runInstallWizard(opts = {}) {
|
|
9448
|
+
if (!ensureTty()) return false;
|
|
9449
|
+
await animateBanner();
|
|
9450
|
+
intro6("Check system compatibility");
|
|
9451
|
+
const sysResults = await runSystemChecks();
|
|
9452
|
+
const sysGroup = { title: "system", results: sysResults };
|
|
9453
|
+
process.stdout.write(" " + formatCompact([sysGroup]) + "\n");
|
|
9454
|
+
const hardFail = sysResults.find((r) => r.status === "fail");
|
|
9455
|
+
if (hardFail) {
|
|
9456
|
+
log30.error(`system check failed: ${hardFail.label} \u2014 ${hardFail.detail}`);
|
|
9457
|
+
log30.info("run `agentbox doctor` for full detail");
|
|
9458
|
+
const cont = await confirm14({ message: "Continue anyway?", initialValue: false });
|
|
9459
|
+
if (isCancel15(cont) || !cont) {
|
|
9460
|
+
outro6("aborted");
|
|
9461
|
+
return false;
|
|
9462
|
+
}
|
|
9463
|
+
}
|
|
9464
|
+
let providerName;
|
|
9465
|
+
if (opts.provider) {
|
|
9466
|
+
const candidate = opts.provider.trim();
|
|
9467
|
+
if (!isProviderName(candidate)) {
|
|
9468
|
+
log30.error(`unknown --provider: ${candidate}`);
|
|
9469
|
+
return false;
|
|
9470
|
+
}
|
|
9471
|
+
providerName = candidate;
|
|
9472
|
+
} else {
|
|
9473
|
+
const picked = await select2({
|
|
9474
|
+
message: "Which provider do you want to set up?",
|
|
9475
|
+
initialValue: "docker",
|
|
9476
|
+
options: KNOWN_PROVIDERS.map((p) => ({
|
|
9477
|
+
value: p,
|
|
9478
|
+
label: PROVIDER_LABEL[p],
|
|
9479
|
+
hint: PROVIDER_HINTS[p]
|
|
9480
|
+
}))
|
|
9481
|
+
});
|
|
9482
|
+
if (isCancel15(picked)) {
|
|
9483
|
+
outro6("cancelled");
|
|
9484
|
+
return false;
|
|
9485
|
+
}
|
|
9486
|
+
providerName = picked;
|
|
8260
9487
|
}
|
|
8261
|
-
if (
|
|
8262
|
-
|
|
9488
|
+
if (providerName !== "docker") {
|
|
9489
|
+
const loggedIn = await runProviderLogin(providerName);
|
|
9490
|
+
if (!loggedIn) {
|
|
9491
|
+
outro6("cancelled");
|
|
9492
|
+
return false;
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
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)`;
|
|
9496
|
+
const wantPrepare = opts.yes ? true : await confirm14({ message: prepareMsg, initialValue: true });
|
|
9497
|
+
if (isCancel15(wantPrepare)) {
|
|
9498
|
+
outro6("cancelled");
|
|
9499
|
+
return false;
|
|
9500
|
+
}
|
|
9501
|
+
if (wantPrepare) {
|
|
9502
|
+
try {
|
|
9503
|
+
await runPrepare(providerName, {
|
|
9504
|
+
cwd: process.cwd(),
|
|
9505
|
+
yes: true,
|
|
9506
|
+
suppressStatus: true,
|
|
9507
|
+
suppressTip: true
|
|
9508
|
+
});
|
|
9509
|
+
} catch (err) {
|
|
9510
|
+
log30.warn(
|
|
9511
|
+
`prepare failed: ${err instanceof Error ? err.message : String(err)} \u2014 you can rerun \`agentbox prepare --provider ${providerName}\` later`
|
|
9512
|
+
);
|
|
9513
|
+
}
|
|
9514
|
+
} else {
|
|
9515
|
+
log30.info(
|
|
9516
|
+
`skipped \u2014 the ${providerName} base will build lazily on first \`agentbox ${providerName === "docker" ? "" : providerName + " "}create\``
|
|
9517
|
+
);
|
|
9518
|
+
}
|
|
9519
|
+
const sp = spinner8();
|
|
9520
|
+
sp.start("installing host /agentbox skill\u2026");
|
|
9521
|
+
let skillRes;
|
|
9522
|
+
try {
|
|
9523
|
+
skillRes = installHostSkills({ force: opts.force, dryRun: opts.dryRun, quiet: true });
|
|
9524
|
+
if (skillRes.written.length > 0) {
|
|
9525
|
+
sp.stop(`Agentbox Skills: Installed in ${String(skillRes.written.length)} locations`);
|
|
9526
|
+
} else {
|
|
9527
|
+
sp.stop(`Agentbox Skills: nothing to write (${String(skillRes.skipped)} skipped)`);
|
|
9528
|
+
}
|
|
9529
|
+
if (skillRes.blocked.length > 0) {
|
|
9530
|
+
log30.warn(
|
|
9531
|
+
`user-modified host skill file(s) left in place: ${skillRes.blocked.join(", ")}
|
|
9532
|
+
pass \`agentbox install --skills-only --force\` to overwrite`
|
|
9533
|
+
);
|
|
9534
|
+
}
|
|
9535
|
+
} catch (err) {
|
|
9536
|
+
sp.stop("Agentbox Skills: failed");
|
|
9537
|
+
log30.warn(err instanceof Error ? err.message : String(err));
|
|
9538
|
+
}
|
|
9539
|
+
markSetupComplete(providerName);
|
|
9540
|
+
const providerGroup = await runProviderChecks(providerName);
|
|
9541
|
+
process.stdout.write(" " + formatCompact([sysGroup, providerGroup]) + "\n");
|
|
9542
|
+
note(tutorialBody(providerName), "Next steps");
|
|
9543
|
+
outro6(
|
|
9544
|
+
opts.fromAutoTrigger ? "\u2728 Setup complete \u2014 continuing with your command\u2026" : "\u2728 Setup complete"
|
|
9545
|
+
);
|
|
9546
|
+
return true;
|
|
9547
|
+
}
|
|
9548
|
+
var installCommand = new Command25("install").description(
|
|
9549
|
+
"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."
|
|
9550
|
+
).option(
|
|
9551
|
+
"--skills-only",
|
|
9552
|
+
"only install the host /agentbox skill files (no wizard, no login, no prepare)"
|
|
9553
|
+
).option("--force", "overwrite existing skill files even if not AgentBox-managed").option("--dry-run", "print what would be written without changing anything").option(
|
|
9554
|
+
"-p, --provider <name>",
|
|
9555
|
+
"pre-select the provider to set up (docker | daytona | hetzner | vercel)"
|
|
9556
|
+
).option("-y, --yes", "auto-confirm the prepare step").action(async (opts) => {
|
|
9557
|
+
if (opts.skillsOnly) {
|
|
9558
|
+
intro6("Installing AgentBox host commands...");
|
|
9559
|
+
let res;
|
|
9560
|
+
try {
|
|
9561
|
+
res = installHostSkills({ force: opts.force, dryRun: opts.dryRun });
|
|
9562
|
+
} catch (err) {
|
|
9563
|
+
log30.error(err instanceof Error ? err.message : String(err));
|
|
9564
|
+
process.exit(1);
|
|
9565
|
+
}
|
|
9566
|
+
if (opts.dryRun) {
|
|
9567
|
+
outro6(
|
|
9568
|
+
`dry-run: ${String(res.written.length)} file(s) would be written, ${String(res.skipped)} skipped`
|
|
9569
|
+
);
|
|
9570
|
+
return;
|
|
9571
|
+
}
|
|
9572
|
+
if (res.written.length === 0) {
|
|
9573
|
+
outro6(`nothing installed (${String(res.skipped)} skipped)`);
|
|
9574
|
+
return;
|
|
9575
|
+
}
|
|
9576
|
+
outro6(`installed: ${res.written.join(", ")}`);
|
|
8263
9577
|
return;
|
|
8264
9578
|
}
|
|
8265
|
-
|
|
9579
|
+
const ok = await runInstallWizard({
|
|
9580
|
+
provider: opts.provider,
|
|
9581
|
+
yes: opts.yes,
|
|
9582
|
+
force: opts.force,
|
|
9583
|
+
dryRun: opts.dryRun
|
|
9584
|
+
});
|
|
9585
|
+
if (!ok) process.exit(1);
|
|
9586
|
+
});
|
|
9587
|
+
|
|
9588
|
+
// src/commands/doctor.ts
|
|
9589
|
+
import { Command as Command26 } from "commander";
|
|
9590
|
+
var doctorCommand = new Command26("doctor").description(
|
|
9591
|
+
"Diagnose system compatibility and provider readiness (Node, git, ssh, Docker daemon, provider credentials, prepared snapshots)."
|
|
9592
|
+
).option(
|
|
9593
|
+
"-p, --provider <name>",
|
|
9594
|
+
"limit checks to one provider (docker | daytona | hetzner | vercel)"
|
|
9595
|
+
).action(async (opts) => {
|
|
9596
|
+
let groups;
|
|
9597
|
+
if (opts.provider) {
|
|
9598
|
+
const name = opts.provider.trim();
|
|
9599
|
+
if (!isKnownProvider(name)) {
|
|
9600
|
+
process.stderr.write(
|
|
9601
|
+
"error: --provider must be one of: docker, daytona, hetzner, vercel\n"
|
|
9602
|
+
);
|
|
9603
|
+
process.exit(1);
|
|
9604
|
+
}
|
|
9605
|
+
groups = [
|
|
9606
|
+
{ title: "system", results: await runSystemChecks() },
|
|
9607
|
+
await runProviderChecks(name)
|
|
9608
|
+
];
|
|
9609
|
+
} else {
|
|
9610
|
+
groups = await runAllChecks();
|
|
9611
|
+
}
|
|
9612
|
+
process.stdout.write(formatDetailed(groups).join("\n") + "\n");
|
|
9613
|
+
const worst = worstStatus(groups);
|
|
9614
|
+
if (worst === "fail") {
|
|
9615
|
+
process.stdout.write(
|
|
9616
|
+
"\nOne or more required checks failed. Fix the FAIL items above before continuing.\n"
|
|
9617
|
+
);
|
|
9618
|
+
process.exit(1);
|
|
9619
|
+
}
|
|
9620
|
+
if (worst === "warn") {
|
|
9621
|
+
process.stdout.write(
|
|
9622
|
+
"\nWarnings are providers that need setup. Run `agentbox install` to configure one,\nor `agentbox prepare --status` to see remote snapshot inventory.\n"
|
|
9623
|
+
);
|
|
9624
|
+
} else {
|
|
9625
|
+
process.stdout.write("\nAll checks passed.\n");
|
|
9626
|
+
}
|
|
8266
9627
|
});
|
|
8267
9628
|
|
|
8268
9629
|
// src/commands/git.ts
|
|
8269
|
-
import { Command as
|
|
9630
|
+
import { Command as Command27 } from "commander";
|
|
8270
9631
|
var WORKSPACE = "/workspace";
|
|
8271
9632
|
var TOKEN_TTL_MS = 12e4;
|
|
8272
|
-
async function runInBox(box,
|
|
9633
|
+
async function runInBox(box, argv2) {
|
|
8273
9634
|
const provider = await providerForBox(box);
|
|
8274
|
-
return provider.exec(box,
|
|
9635
|
+
return provider.exec(box, argv2, { cwd: WORKSPACE });
|
|
8275
9636
|
}
|
|
8276
|
-
async function runAndStream(box,
|
|
8277
|
-
const r = await runInBox(box,
|
|
9637
|
+
async function runAndStream(box, argv2) {
|
|
9638
|
+
const r = await runInBox(box, argv2);
|
|
8278
9639
|
if (r.stdout) process.stdout.write(r.stdout);
|
|
8279
9640
|
if (r.stderr) process.stderr.write(r.stderr);
|
|
8280
9641
|
return r.exitCode;
|
|
@@ -8298,33 +9659,33 @@ function buildPredictedGhPrParams(ghArgs) {
|
|
|
8298
9659
|
async function exitWith(code) {
|
|
8299
9660
|
process.exit(code);
|
|
8300
9661
|
}
|
|
8301
|
-
var pushCommand = new
|
|
9662
|
+
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
9663
|
try {
|
|
8303
9664
|
const box = await resolveBoxOrExit(boxRef);
|
|
8304
9665
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8305
9666
|
const tokenArgs = await hostInitiatedArgs(box.id, "git.push", predicted);
|
|
8306
|
-
const
|
|
8307
|
-
if (opts.remote)
|
|
8308
|
-
|
|
8309
|
-
await exitWith(await runAndStream(box,
|
|
9667
|
+
const argv2 = ["agentbox-ctl", "git", "push", ...tokenArgs];
|
|
9668
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9669
|
+
argv2.push(...args);
|
|
9670
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8310
9671
|
} catch (err) {
|
|
8311
9672
|
handleLifecycleError(err);
|
|
8312
9673
|
}
|
|
8313
9674
|
});
|
|
8314
|
-
var fetchCommand = new
|
|
9675
|
+
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
9676
|
try {
|
|
8316
9677
|
const box = await resolveBoxOrExit(boxRef);
|
|
8317
9678
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8318
9679
|
const tokenArgs = await hostInitiatedArgs(box.id, "git.fetch", predicted);
|
|
8319
|
-
const
|
|
8320
|
-
if (opts.remote)
|
|
8321
|
-
|
|
8322
|
-
await exitWith(await runAndStream(box,
|
|
9680
|
+
const argv2 = ["agentbox-ctl", "git", "fetch", ...tokenArgs];
|
|
9681
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9682
|
+
argv2.push(...args);
|
|
9683
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8323
9684
|
} catch (err) {
|
|
8324
9685
|
handleLifecycleError(err);
|
|
8325
9686
|
}
|
|
8326
9687
|
});
|
|
8327
|
-
var pullCommand = new
|
|
9688
|
+
var pullCommand = new Command27("pull").description(
|
|
8328
9689
|
"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
9690
|
).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
9691
|
async (boxRef, branch, args, opts) => {
|
|
@@ -8336,17 +9697,17 @@ var pullCommand = new Command25("pull").description(
|
|
|
8336
9697
|
}
|
|
8337
9698
|
const predicted = buildPredictedGitParams(opts.remote, args);
|
|
8338
9699
|
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,
|
|
9700
|
+
const argv2 = ["agentbox-ctl", "git", "pull", ...tokenArgs];
|
|
9701
|
+
if (opts.remote) argv2.push("--remote", opts.remote);
|
|
9702
|
+
if (opts.ffOnly) argv2.push("--ff-only");
|
|
9703
|
+
argv2.push(...args);
|
|
9704
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8344
9705
|
} catch (err) {
|
|
8345
9706
|
handleLifecycleError(err);
|
|
8346
9707
|
}
|
|
8347
9708
|
}
|
|
8348
9709
|
);
|
|
8349
|
-
var checkoutCommand = new
|
|
9710
|
+
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
9711
|
try {
|
|
8351
9712
|
const box = await resolveBoxOrExit(boxRef);
|
|
8352
9713
|
await exitWith(await runAndStream(box, ["git", "checkout", branch, ...args]));
|
|
@@ -8354,7 +9715,7 @@ var checkoutCommand = new Command25("checkout").description("Change the box's wo
|
|
|
8354
9715
|
handleLifecycleError(err);
|
|
8355
9716
|
}
|
|
8356
9717
|
});
|
|
8357
|
-
var statusCommand = new
|
|
9718
|
+
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
9719
|
try {
|
|
8359
9720
|
const box = await resolveBoxOrExit(boxRef);
|
|
8360
9721
|
await exitWith(await runAndStream(box, ["git", "status", ...args]));
|
|
@@ -8378,7 +9739,7 @@ function injectPrCreateHead2(op, box, args) {
|
|
|
8378
9739
|
return injectPrCreateHead(op, rootWt?.branch, args);
|
|
8379
9740
|
}
|
|
8380
9741
|
function buildPrSubcommand(op) {
|
|
8381
|
-
return new
|
|
9742
|
+
return new Command27(op).description(PR_OP_DESCRIPTIONS[op]).argument("<box>", "box ref").argument(
|
|
8382
9743
|
"[args...]",
|
|
8383
9744
|
"extra flags forwarded to `gh pr <op>` (e.g. --title, --body, --label, --draft, --json)"
|
|
8384
9745
|
).allowExcessArguments(true).allowUnknownOption(true).action(async (boxRef, args) => {
|
|
@@ -8387,25 +9748,25 @@ function buildPrSubcommand(op) {
|
|
|
8387
9748
|
const ghArgs = injectPrCreateHead2(op, box, args);
|
|
8388
9749
|
const predicted = buildPredictedGhPrParams(ghArgs);
|
|
8389
9750
|
const tokenArgs = await hostInitiatedArgs(box.id, `gh.pr.${op}`, predicted);
|
|
8390
|
-
const
|
|
8391
|
-
await exitWith(await runAndStream(box,
|
|
9751
|
+
const argv2 = ["agentbox-ctl", "gh", "pr", op, ...tokenArgs, ...ghArgs];
|
|
9752
|
+
await exitWith(await runAndStream(box, argv2));
|
|
8392
9753
|
} catch (err) {
|
|
8393
9754
|
handleLifecycleError(err);
|
|
8394
9755
|
}
|
|
8395
9756
|
});
|
|
8396
9757
|
}
|
|
8397
|
-
var prCommand = new
|
|
9758
|
+
var prCommand = new Command27("pr").description(
|
|
8398
9759
|
"PR operations against a box's branch via the host `gh` CLI"
|
|
8399
9760
|
);
|
|
8400
9761
|
for (const op of GH_PR_OPS) {
|
|
8401
9762
|
const sub = buildPrSubcommand(op);
|
|
8402
9763
|
prCommand.addCommand(sub, op === "create" ? { isDefault: true } : void 0);
|
|
8403
9764
|
}
|
|
8404
|
-
var gitCommand = new
|
|
9765
|
+
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
9766
|
|
|
8406
9767
|
// src/commands/list.ts
|
|
8407
|
-
import { log as
|
|
8408
|
-
import { Command as
|
|
9768
|
+
import { log as log31 } from "@clack/prompts";
|
|
9769
|
+
import { Command as Command28 } from "commander";
|
|
8409
9770
|
import { pathToFileURL } from "url";
|
|
8410
9771
|
|
|
8411
9772
|
// src/hyperlink.ts
|
|
@@ -8418,6 +9779,38 @@ function hyperlink(label, url, stream) {
|
|
|
8418
9779
|
return `${ESC2}]8;;${url}${ST}${label}${ESC2}]8;;${ST}`;
|
|
8419
9780
|
}
|
|
8420
9781
|
|
|
9782
|
+
// src/lib/cloud-state.ts
|
|
9783
|
+
var PROBE_TIMEOUT_MS = 4e3;
|
|
9784
|
+
async function applyLiveCloudStates(boxes) {
|
|
9785
|
+
await Promise.all(
|
|
9786
|
+
boxes.map(async (b) => {
|
|
9787
|
+
if (!b.provider || b.provider === "docker") return;
|
|
9788
|
+
try {
|
|
9789
|
+
const provider = await providerForBox(b);
|
|
9790
|
+
const state = await withTimeout(provider.probeState(b), PROBE_TIMEOUT_MS);
|
|
9791
|
+
if (state !== null) b.state = state;
|
|
9792
|
+
} catch {
|
|
9793
|
+
}
|
|
9794
|
+
})
|
|
9795
|
+
);
|
|
9796
|
+
}
|
|
9797
|
+
function withTimeout(p, ms) {
|
|
9798
|
+
return new Promise((resolve3) => {
|
|
9799
|
+
const t = setTimeout(() => resolve3(null), ms);
|
|
9800
|
+
if (typeof t.unref === "function") t.unref();
|
|
9801
|
+
p.then(
|
|
9802
|
+
(v) => {
|
|
9803
|
+
clearTimeout(t);
|
|
9804
|
+
resolve3(v);
|
|
9805
|
+
},
|
|
9806
|
+
() => {
|
|
9807
|
+
clearTimeout(t);
|
|
9808
|
+
resolve3(null);
|
|
9809
|
+
}
|
|
9810
|
+
);
|
|
9811
|
+
});
|
|
9812
|
+
}
|
|
9813
|
+
|
|
8421
9814
|
// src/watch.ts
|
|
8422
9815
|
function withWatchOptions(cmd) {
|
|
8423
9816
|
return cmd.option("-w, --watch", "redraw continuously until interrupted (Ctrl-C)").option("--interval <seconds>", "refresh interval for --watch", "2");
|
|
@@ -8433,7 +9826,7 @@ async function watchRender(produce, rawInterval) {
|
|
|
8433
9826
|
process.stdout.write("\x1B[?25l");
|
|
8434
9827
|
process.once("exit", () => process.stdout.write("\x1B[?25h"));
|
|
8435
9828
|
process.once("SIGINT", () => process.exit(0));
|
|
8436
|
-
const
|
|
9829
|
+
const sleep5 = (d) => new Promise((r) => setTimeout(r, d));
|
|
8437
9830
|
for (; ; ) {
|
|
8438
9831
|
let body;
|
|
8439
9832
|
try {
|
|
@@ -8449,7 +9842,7 @@ async function watchRender(produce, rawInterval) {
|
|
|
8449
9842
|
${body.replace(/\n+$/, "")}
|
|
8450
9843
|
`
|
|
8451
9844
|
);
|
|
8452
|
-
await
|
|
9845
|
+
await sleep5(ms);
|
|
8453
9846
|
}
|
|
8454
9847
|
}
|
|
8455
9848
|
|
|
@@ -8501,6 +9894,7 @@ function workspaceCell(path, target, stream) {
|
|
|
8501
9894
|
return { text: hyperlink(display, url, stream), width: display.length };
|
|
8502
9895
|
}
|
|
8503
9896
|
function agentSummary(b) {
|
|
9897
|
+
if (b.state !== "running") return "-";
|
|
8504
9898
|
const agents = [];
|
|
8505
9899
|
if (b.claudeActivity && b.claudeActivity !== "unknown") {
|
|
8506
9900
|
agents.push(`claude:${b.claudeActivity}`);
|
|
@@ -8554,14 +9948,19 @@ function renderTable(boxes, stream) {
|
|
|
8554
9948
|
(row2) => row2.map((cell, i) => padCell(cell ?? plain(""), i)).join(" ").trimEnd()
|
|
8555
9949
|
).join("\n");
|
|
8556
9950
|
}
|
|
8557
|
-
async function scopedBoxes(all) {
|
|
9951
|
+
async function scopedBoxes(all, live) {
|
|
8558
9952
|
const boxes = await listBoxes();
|
|
8559
|
-
if (all)
|
|
9953
|
+
if (all) {
|
|
9954
|
+
if (live) await applyLiveCloudStates(boxes);
|
|
9955
|
+
return { boxes, projectRoot: "", scoped: false };
|
|
9956
|
+
}
|
|
8560
9957
|
const { root } = await findProjectRoot(process.cwd());
|
|
8561
|
-
|
|
9958
|
+
const scoped2 = boxes.filter((b) => b.projectRoot === root);
|
|
9959
|
+
if (live) await applyLiveCloudStates(scoped2);
|
|
9960
|
+
return { boxes: scoped2, projectRoot: root, scoped: true };
|
|
8562
9961
|
}
|
|
8563
|
-
async function buildListText(all) {
|
|
8564
|
-
const { boxes, projectRoot, scoped: scoped2 } = await scopedBoxes(all);
|
|
9962
|
+
async function buildListText(all, live) {
|
|
9963
|
+
const { boxes, projectRoot, scoped: scoped2 } = await scopedBoxes(all, live);
|
|
8565
9964
|
if (boxes.length === 0) {
|
|
8566
9965
|
if (scoped2) {
|
|
8567
9966
|
return `no boxes in this project (${projectRoot}) \u2014 run \`agentbox create\`, or \`agentbox list --global\` to see all`;
|
|
@@ -8575,31 +9974,35 @@ async function buildListText(all) {
|
|
|
8575
9974
|
${table}`;
|
|
8576
9975
|
}
|
|
8577
9976
|
var listCommand2 = withWatchOptions(
|
|
8578
|
-
new
|
|
9977
|
+
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(
|
|
9978
|
+
"--live",
|
|
9979
|
+
"probe live cloud state via the provider SDK (slower; default: last host-known state)"
|
|
9980
|
+
)
|
|
8579
9981
|
).action(async (opts) => {
|
|
8580
9982
|
if (opts.json && opts.watch) {
|
|
8581
|
-
|
|
9983
|
+
log31.error("cannot combine --json with --watch");
|
|
8582
9984
|
process.exit(2);
|
|
8583
9985
|
}
|
|
8584
9986
|
const all = opts.global ?? false;
|
|
9987
|
+
const live = opts.live ?? false;
|
|
8585
9988
|
if (opts.watch) {
|
|
8586
|
-
await watchRender(() => buildListText(all), opts.interval);
|
|
9989
|
+
await watchRender(() => buildListText(all, live), opts.interval);
|
|
8587
9990
|
return;
|
|
8588
9991
|
}
|
|
8589
9992
|
if (opts.json) {
|
|
8590
|
-
const { boxes } = await scopedBoxes(all);
|
|
9993
|
+
const { boxes } = await scopedBoxes(all, live);
|
|
8591
9994
|
process.stdout.write(JSON.stringify(boxes, null, 2) + "\n");
|
|
8592
9995
|
return;
|
|
8593
9996
|
}
|
|
8594
|
-
process.stdout.write(await buildListText(all) + "\n");
|
|
9997
|
+
process.stdout.write(await buildListText(all, live) + "\n");
|
|
8595
9998
|
});
|
|
8596
9999
|
|
|
8597
10000
|
// src/commands/logs.ts
|
|
8598
|
-
import { log as
|
|
8599
|
-
import { Command as
|
|
10001
|
+
import { log as log32 } from "@clack/prompts";
|
|
10002
|
+
import { Command as Command29 } from "commander";
|
|
8600
10003
|
import { spawn as spawn3 } from "child_process";
|
|
8601
10004
|
var DAEMON_LOG_PATH = "/var/log/agentbox/ctl-daemon.log";
|
|
8602
|
-
var logsCommand = new
|
|
10005
|
+
var logsCommand = new Command29("logs").description("Print recent log lines from a box service; -f to stream").argument(
|
|
8603
10006
|
"[box]",
|
|
8604
10007
|
"box ref (optional when cwd has exactly 1 box): project index, id, id prefix, name, or container"
|
|
8605
10008
|
).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 +10020,9 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8617
10020
|
service = boxArg;
|
|
8618
10021
|
}
|
|
8619
10022
|
if (!service && !opts.daemon) {
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
10023
|
+
log32.error("missing <service> argument");
|
|
10024
|
+
log32.info("usage: agentbox logs [box] <service> [-n N] [-f]");
|
|
10025
|
+
log32.info(" agentbox logs [box] --daemon [-n N] [-f]");
|
|
8623
10026
|
process.exit(2);
|
|
8624
10027
|
}
|
|
8625
10028
|
const box = await resolveBoxOrExit(idOrName);
|
|
@@ -8630,7 +10033,7 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8630
10033
|
if (!opts.follow) {
|
|
8631
10034
|
const proc = await provider.exec(box, args, { user: "vscode" });
|
|
8632
10035
|
if (proc.exitCode !== 0) {
|
|
8633
|
-
|
|
10036
|
+
log32.error(
|
|
8634
10037
|
`${opts.daemon ? "daemon log" : "agentbox-ctl logs"} failed: ${proc.stderr || proc.stdout}`
|
|
8635
10038
|
);
|
|
8636
10039
|
process.exit(1);
|
|
@@ -8686,12 +10089,12 @@ var logsCommand = new Command27("logs").description("Print recent log lines from
|
|
|
8686
10089
|
});
|
|
8687
10090
|
|
|
8688
10091
|
// 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
|
|
10092
|
+
import { log as log33 } from "@clack/prompts";
|
|
10093
|
+
import { execa as execa3 } from "execa";
|
|
10094
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
10095
|
+
import { homedir as homedir14 } from "os";
|
|
10096
|
+
import { join as join16 } from "path";
|
|
10097
|
+
import { Command as Command30 } from "commander";
|
|
8695
10098
|
|
|
8696
10099
|
// src/commands/path.ts
|
|
8697
10100
|
async function runPath(box, opts) {
|
|
@@ -8713,7 +10116,7 @@ async function runPath(box, opts) {
|
|
|
8713
10116
|
}
|
|
8714
10117
|
|
|
8715
10118
|
// src/commands/open.ts
|
|
8716
|
-
var openCommand = new
|
|
10119
|
+
var openCommand = new Command30("open").description("Open a box's /workspace in Finder (docker: rsync'd snapshot; cloud: sshfs mount)").argument(
|
|
8717
10120
|
"[box]",
|
|
8718
10121
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
8719
10122
|
).option("--no-refresh", "skip the rsync; open whatever's already on disk (docker only)").option(
|
|
@@ -8752,7 +10155,7 @@ var openCommand = new Command28("open").description("Open a box's /workspace in
|
|
|
8752
10155
|
}
|
|
8753
10156
|
});
|
|
8754
10157
|
async function runCloudOpen(box, provider, opts) {
|
|
8755
|
-
const mountRoot =
|
|
10158
|
+
const mountRoot = join16(homedir14(), ".agentbox", "mounts", box.name);
|
|
8756
10159
|
if (opts.unmount) {
|
|
8757
10160
|
const ok = await tryUnmount(mountRoot);
|
|
8758
10161
|
if (ok) process.stdout.write(`unmounted ${mountRoot}
|
|
@@ -8789,14 +10192,14 @@ async function runCloudOpen(box, provider, opts) {
|
|
|
8789
10192
|
user: target.user,
|
|
8790
10193
|
identityFile: target.identityFile
|
|
8791
10194
|
});
|
|
8792
|
-
if (!
|
|
8793
|
-
|
|
10195
|
+
if (!existsSync7(mountRoot)) {
|
|
10196
|
+
mkdirSync6(mountRoot, { recursive: true, mode: 493 });
|
|
8794
10197
|
} else if (await isMounted(mountRoot)) {
|
|
8795
|
-
|
|
10198
|
+
log33.info(`re-mounting (stale mount detected at ${mountRoot})`);
|
|
8796
10199
|
await tryUnmount(mountRoot);
|
|
8797
10200
|
}
|
|
8798
|
-
|
|
8799
|
-
const mount = await
|
|
10201
|
+
log33.info(`mounting ${alias}:/workspace at ${mountRoot}`);
|
|
10202
|
+
const mount = await execa3(
|
|
8800
10203
|
sshfsBin,
|
|
8801
10204
|
[
|
|
8802
10205
|
`${alias}:/workspace`,
|
|
@@ -8815,35 +10218,35 @@ async function runCloudOpen(box, provider, opts) {
|
|
|
8815
10218
|
if (mount.exitCode !== 0) {
|
|
8816
10219
|
throw new Error(`sshfs mount failed (exit ${String(mount.exitCode)}): ${mount.stderr || mount.stdout}`);
|
|
8817
10220
|
}
|
|
8818
|
-
await
|
|
10221
|
+
await execa3("open", [mountRoot], { reject: false });
|
|
8819
10222
|
process.stdout.write(`opened ${mountRoot}
|
|
8820
10223
|
`);
|
|
8821
10224
|
process.stdout.write(`unmount later with: agentbox open ${box.name} --unmount
|
|
8822
10225
|
`);
|
|
8823
10226
|
}
|
|
8824
10227
|
async function locateBinary(name) {
|
|
8825
|
-
const r = await
|
|
10228
|
+
const r = await execa3("which", [name], { reject: false });
|
|
8826
10229
|
if (r.exitCode !== 0) return null;
|
|
8827
10230
|
const path = (r.stdout ?? "").trim();
|
|
8828
10231
|
return path.length > 0 ? path : null;
|
|
8829
10232
|
}
|
|
8830
10233
|
async function isMounted(path) {
|
|
8831
|
-
const r = await
|
|
10234
|
+
const r = await execa3("sh", ["-c", `mount | grep -F " on ${path} "`], { reject: false });
|
|
8832
10235
|
return r.exitCode === 0;
|
|
8833
10236
|
}
|
|
8834
10237
|
async function tryUnmount(path) {
|
|
8835
10238
|
if (await isMounted(path)) {
|
|
8836
|
-
const u = await
|
|
10239
|
+
const u = await execa3("umount", [path], { reject: false });
|
|
8837
10240
|
if (u.exitCode === 0) return true;
|
|
8838
|
-
const d = await
|
|
10241
|
+
const d = await execa3("diskutil", ["unmount", path], { reject: false });
|
|
8839
10242
|
return d.exitCode === 0;
|
|
8840
10243
|
}
|
|
8841
10244
|
return false;
|
|
8842
10245
|
}
|
|
8843
10246
|
|
|
8844
10247
|
// src/commands/pause.ts
|
|
8845
|
-
import { Command as
|
|
8846
|
-
var pauseCommand = new
|
|
10248
|
+
import { Command as Command31 } from "commander";
|
|
10249
|
+
var pauseCommand = new Command31("pause").description(
|
|
8847
10250
|
"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
10251
|
).argument(
|
|
8849
10252
|
"[box]",
|
|
@@ -8865,220 +10268,9 @@ var pauseCommand = new Command29("pause").description(
|
|
|
8865
10268
|
}
|
|
8866
10269
|
});
|
|
8867
10270
|
|
|
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
10271
|
// src/commands/prune.ts
|
|
9080
|
-
import { confirm as
|
|
9081
|
-
import { Command as
|
|
10272
|
+
import { confirm as confirm15, isCancel as isCancel16, log as log34 } from "@clack/prompts";
|
|
10273
|
+
import { Command as Command32 } from "commander";
|
|
9082
10274
|
function totalRemovals(r, projectConfigs) {
|
|
9083
10275
|
return r.removedRecords.length + r.removedContainers.length + r.removedVolumes.length + r.removedSnapshotDirs.length + r.removedBoxDirs.length + projectConfigs.length;
|
|
9084
10276
|
}
|
|
@@ -9124,7 +10316,7 @@ async function liveProjectRoots() {
|
|
|
9124
10316
|
return [];
|
|
9125
10317
|
}
|
|
9126
10318
|
}
|
|
9127
|
-
var pruneCommand = new
|
|
10319
|
+
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
10320
|
"--all",
|
|
9129
10321
|
"also remove orphan agentbox-* containers, volumes, snapshot dirs, and orphan per-project config dirs"
|
|
9130
10322
|
).option("-y, --yes", "skip the confirmation prompt").option(
|
|
@@ -9154,8 +10346,8 @@ var pruneCommand = new Command31("prune").description("Clean up orphan state.jso
|
|
|
9154
10346
|
${summary(preview, previewProjects)}`);
|
|
9155
10347
|
if (dryRun) return;
|
|
9156
10348
|
if (!opts.yes) {
|
|
9157
|
-
const ok = await
|
|
9158
|
-
if (
|
|
10349
|
+
const ok = await confirm15({ message: "Proceed with prune?", initialValue: true });
|
|
10350
|
+
if (isCancel16(ok) || !ok) {
|
|
9159
10351
|
log34.info("cancelled");
|
|
9160
10352
|
return;
|
|
9161
10353
|
}
|
|
@@ -9173,19 +10365,9 @@ var CLOUD_PRUNE_PROVIDERS = ["daytona", "hetzner", "vercel"];
|
|
|
9173
10365
|
function isCloudPruneProvider(name) {
|
|
9174
10366
|
return CLOUD_PRUNE_PROVIDERS.includes(name);
|
|
9175
10367
|
}
|
|
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
10368
|
async function pruneCloud(provider, opts) {
|
|
9187
10369
|
const dryRun = opts.dryRun ?? false;
|
|
9188
|
-
const backend = await
|
|
10370
|
+
const backend = await cloudBackendForProvider(provider);
|
|
9189
10371
|
if (!backend.list) {
|
|
9190
10372
|
log34.error(`${provider} backend doesn't expose \`list()\`; cannot enumerate sandboxes for prune`);
|
|
9191
10373
|
process.exit(2);
|
|
@@ -9218,11 +10400,11 @@ async function pruneCloud(provider, opts) {
|
|
|
9218
10400
|
}
|
|
9219
10401
|
if (dryRun) return;
|
|
9220
10402
|
if (!opts.yes) {
|
|
9221
|
-
const ok = await
|
|
10403
|
+
const ok = await confirm15({
|
|
9222
10404
|
message: `Delete ${String(orphans.length)} orphan sandbox(es)?`,
|
|
9223
10405
|
initialValue: false
|
|
9224
10406
|
});
|
|
9225
|
-
if (
|
|
10407
|
+
if (isCancel16(ok) || !ok) {
|
|
9226
10408
|
log34.info("cancelled");
|
|
9227
10409
|
return;
|
|
9228
10410
|
}
|
|
@@ -9249,10 +10431,10 @@ async function pruneCloud(provider, opts) {
|
|
|
9249
10431
|
// src/commands/queue.ts
|
|
9250
10432
|
import { readFile as readFile4, stat as stat5 } from "fs/promises";
|
|
9251
10433
|
import { intro as intro7, log as log35, outro as outro7 } from "@clack/prompts";
|
|
9252
|
-
import { Command as
|
|
10434
|
+
import { Command as Command33 } from "commander";
|
|
9253
10435
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["done", "failed", "cancelled"]);
|
|
9254
|
-
var queueCommand = new
|
|
9255
|
-
var queueListCommand = new
|
|
10436
|
+
var queueCommand = new Command33("queue").description("Inspect and manage background `agentbox claude|codex|opencode -i` jobs");
|
|
10437
|
+
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
10438
|
const jobs = await loadQueue();
|
|
9257
10439
|
const cfg = await loadQueueConfig();
|
|
9258
10440
|
const visible = opts.all === true ? jobs : jobs.filter((j) => !TERMINAL_STATUSES.has(j.status));
|
|
@@ -9275,17 +10457,17 @@ var queueListCommand = new Command32("list").description("List queued, running,
|
|
|
9275
10457
|
const widths = headers.map(
|
|
9276
10458
|
(h) => Math.max(h.length, ...rows.map((r) => String(r[h]).length))
|
|
9277
10459
|
);
|
|
9278
|
-
const
|
|
9279
|
-
process.stdout.write(headers.map((h, i) =>
|
|
10460
|
+
const pad4 = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
10461
|
+
process.stdout.write(headers.map((h, i) => pad4(h, widths[i])).join(" ") + "\n");
|
|
9280
10462
|
process.stdout.write(widths.map((w) => "-".repeat(w)).join(" ") + "\n");
|
|
9281
10463
|
for (const r of rows) {
|
|
9282
10464
|
process.stdout.write(
|
|
9283
|
-
headers.map((h, i) =>
|
|
10465
|
+
headers.map((h, i) => pad4(String(r[h]), widths[i])).join(" ") + "\n"
|
|
9284
10466
|
);
|
|
9285
10467
|
}
|
|
9286
10468
|
log35.info(`queue.maxConcurrent = ${String(cfg.maxConcurrent)} (queue.enabled=${String(cfg.enabled)})`);
|
|
9287
10469
|
});
|
|
9288
|
-
var queueShowCommand = new
|
|
10470
|
+
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
10471
|
const job = await readJob(id);
|
|
9290
10472
|
if (!job) {
|
|
9291
10473
|
log35.error(`no job with id ${id}`);
|
|
@@ -9307,7 +10489,7 @@ var queueShowCommand = new Command32("show").description("Dump a job manifest an
|
|
|
9307
10489
|
log35.info(`(no log at ${job.logPath} yet)`);
|
|
9308
10490
|
}
|
|
9309
10491
|
});
|
|
9310
|
-
var queueCancelCommand = new
|
|
10492
|
+
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
10493
|
intro7(`Cancelling queue job ${id}...`);
|
|
9312
10494
|
const job = await readJob(id);
|
|
9313
10495
|
if (!job) {
|
|
@@ -9329,7 +10511,7 @@ var queueCancelCommand = new Command32("cancel").description("Cancel a queued jo
|
|
|
9329
10511
|
await writeJob(cancelled);
|
|
9330
10512
|
outro7(`job ${id} cancelled`);
|
|
9331
10513
|
});
|
|
9332
|
-
var queueClearCommand = new
|
|
10514
|
+
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
10515
|
const targets = /* @__PURE__ */ new Set();
|
|
9334
10516
|
if (opts.all === true || opts.done === true) targets.add("done");
|
|
9335
10517
|
if (opts.all === true || opts.failed === true) targets.add("failed");
|
|
@@ -9358,7 +10540,7 @@ var QUEUE_WAIT_EVENTS = [
|
|
|
9358
10540
|
var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
|
|
9359
10541
|
var DEFAULT_QUEUE_WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9360
10542
|
var QUEUE_POLL_INTERVAL_MS = 500;
|
|
9361
|
-
var queueWaitForCommand = new
|
|
10543
|
+
var queueWaitForCommand = new Command33("wait-for").description(
|
|
9362
10544
|
`Block until a queue / box event fires. <event> one of: ${QUEUE_WAIT_EVENTS.join(" | ")}.`
|
|
9363
10545
|
).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
10546
|
if (!QUEUE_WAIT_EVENTS.includes(eventRaw)) {
|
|
@@ -9446,11 +10628,11 @@ async function pollUntil(deadline, probe) {
|
|
|
9446
10628
|
if (result !== void 0) return result;
|
|
9447
10629
|
const remaining = deadline - Date.now();
|
|
9448
10630
|
if (remaining <= 0) break;
|
|
9449
|
-
await
|
|
10631
|
+
await sleep4(Math.min(QUEUE_POLL_INTERVAL_MS, remaining));
|
|
9450
10632
|
}
|
|
9451
10633
|
throw new QueueWaitTimeout();
|
|
9452
10634
|
}
|
|
9453
|
-
function
|
|
10635
|
+
function sleep4(ms) {
|
|
9454
10636
|
return new Promise((r) => setTimeout(r, ms));
|
|
9455
10637
|
}
|
|
9456
10638
|
function parsePositiveInt3(raw, label) {
|
|
@@ -9483,8 +10665,8 @@ function truncate(s, max) {
|
|
|
9483
10665
|
}
|
|
9484
10666
|
|
|
9485
10667
|
// src/commands/relay.ts
|
|
9486
|
-
import { log as log36, spinner as
|
|
9487
|
-
import { Command as
|
|
10668
|
+
import { log as log36, spinner as spinner9 } from "@clack/prompts";
|
|
10669
|
+
import { Command as Command34 } from "commander";
|
|
9488
10670
|
async function rehydrateFromState() {
|
|
9489
10671
|
const state = await readState();
|
|
9490
10672
|
await rehydrateRelayRegistry(
|
|
@@ -9508,12 +10690,14 @@ function renderStatus(s) {
|
|
|
9508
10690
|
if (s.running && s.health) {
|
|
9509
10691
|
return [
|
|
9510
10692
|
"relay: running",
|
|
9511
|
-
` pid:
|
|
9512
|
-
` port:
|
|
9513
|
-
` url:
|
|
9514
|
-
`
|
|
9515
|
-
`
|
|
9516
|
-
`
|
|
10693
|
+
` pid: ${s.pid === null ? "?" : String(s.pid)}`,
|
|
10694
|
+
` port: ${String(s.port)}`,
|
|
10695
|
+
` url: ${s.endpoint.hostUrl}`,
|
|
10696
|
+
` version: ${s.health.version ?? "(unknown \u2014 relay predates version field)"}`,
|
|
10697
|
+
` commit: ${s.health.commit ?? "(unknown)"}`,
|
|
10698
|
+
` boxes: ${String(s.health.boxes)}`,
|
|
10699
|
+
` events: ${String(s.health.events)}`,
|
|
10700
|
+
` log: ${s.logFile}`
|
|
9517
10701
|
].join("\n");
|
|
9518
10702
|
}
|
|
9519
10703
|
if (s.pidAlive) {
|
|
@@ -9524,7 +10708,7 @@ function renderStatus(s) {
|
|
|
9524
10708
|
}
|
|
9525
10709
|
return ["relay: not running", ` log: ${s.logFile}`].join("\n");
|
|
9526
10710
|
}
|
|
9527
|
-
var statusSub = new
|
|
10711
|
+
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
10712
|
try {
|
|
9529
10713
|
const s = await getRelayStatus();
|
|
9530
10714
|
if (opts.json) {
|
|
@@ -9536,9 +10720,9 @@ var statusSub = new Command33("status").description("Show whether the host relay
|
|
|
9536
10720
|
handleLifecycleError(err);
|
|
9537
10721
|
}
|
|
9538
10722
|
});
|
|
9539
|
-
var stopSub = new
|
|
10723
|
+
var stopSub = new Command34("stop").description("Stop the host relay process (idempotent)").action(async () => {
|
|
9540
10724
|
try {
|
|
9541
|
-
const s =
|
|
10725
|
+
const s = spinner9();
|
|
9542
10726
|
s.start("stopping relay");
|
|
9543
10727
|
const result = await stopRelay();
|
|
9544
10728
|
s.stop(
|
|
@@ -9548,9 +10732,9 @@ var stopSub = new Command33("stop").description("Stop the host relay process (id
|
|
|
9548
10732
|
handleLifecycleError(err);
|
|
9549
10733
|
}
|
|
9550
10734
|
});
|
|
9551
|
-
var startSub = new
|
|
10735
|
+
var startSub = new Command34("start").description("Start the host relay if not already running (idempotent)").action(async () => {
|
|
9552
10736
|
try {
|
|
9553
|
-
const s =
|
|
10737
|
+
const s = spinner9();
|
|
9554
10738
|
s.start("starting relay");
|
|
9555
10739
|
const ep = await ensureRelay();
|
|
9556
10740
|
await rehydrateFromState();
|
|
@@ -9559,15 +10743,15 @@ var startSub = new Command33("start").description("Start the host relay if not a
|
|
|
9559
10743
|
handleLifecycleError(err);
|
|
9560
10744
|
}
|
|
9561
10745
|
});
|
|
9562
|
-
var restartSub = new
|
|
10746
|
+
var restartSub = new Command34("restart").description("Stop then start the host relay").action(async () => {
|
|
9563
10747
|
try {
|
|
9564
|
-
const s =
|
|
10748
|
+
const s = spinner9();
|
|
9565
10749
|
s.start("stopping relay");
|
|
9566
10750
|
const stopped = await stopRelay();
|
|
9567
10751
|
s.stop(
|
|
9568
10752
|
stopped.stopped ? `stopped relay (pid ${String(stopped.pid)})` : "relay was not running"
|
|
9569
10753
|
);
|
|
9570
|
-
const s2 =
|
|
10754
|
+
const s2 = spinner9();
|
|
9571
10755
|
s2.start("starting relay");
|
|
9572
10756
|
try {
|
|
9573
10757
|
const ep = await ensureRelay();
|
|
@@ -9582,11 +10766,11 @@ var restartSub = new Command33("restart").description("Stop then start the host
|
|
|
9582
10766
|
handleLifecycleError(err);
|
|
9583
10767
|
}
|
|
9584
10768
|
});
|
|
9585
|
-
var relayCommand = new
|
|
10769
|
+
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
10770
|
|
|
9587
10771
|
// src/commands/_run-queued-job.ts
|
|
9588
|
-
import { Command as
|
|
9589
|
-
var runQueuedJobCommand = new
|
|
10772
|
+
import { Command as Command35 } from "commander";
|
|
10773
|
+
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
10774
|
const log45 = openCommandLog(`queue-${id}`);
|
|
9591
10775
|
log45.write(`worker pid=${String(process.pid)} starting for job ${id}`);
|
|
9592
10776
|
let job = null;
|
|
@@ -9682,7 +10866,7 @@ async function runDockerJob(job, log45, onBoxCreated) {
|
|
|
9682
10866
|
log45.write(`starting claude session`);
|
|
9683
10867
|
await startClaudeSession({
|
|
9684
10868
|
container: result.record.container,
|
|
9685
|
-
claudeArgs: promptedArgs,
|
|
10869
|
+
claudeArgs: applyClaudeSkipPermissions(promptedArgs, cfg.effective),
|
|
9686
10870
|
sessionName: cfg.effective.claude.sessionName,
|
|
9687
10871
|
boxName: result.record.name
|
|
9688
10872
|
});
|
|
@@ -9694,7 +10878,7 @@ async function runDockerJob(job, log45, onBoxCreated) {
|
|
|
9694
10878
|
log45.write(`starting codex session`);
|
|
9695
10879
|
await startCodexSession({
|
|
9696
10880
|
container: result.record.container,
|
|
9697
|
-
codexArgs: promptedArgs,
|
|
10881
|
+
codexArgs: applyCodexSkipPermissions(promptedArgs, cfg.effective),
|
|
9698
10882
|
sessionName: cfg.effective.codex.sessionName
|
|
9699
10883
|
});
|
|
9700
10884
|
} else if (job.agent === "opencode") {
|
|
@@ -9729,13 +10913,20 @@ function buildOverridesFromJob(job) {
|
|
|
9729
10913
|
else if (job.agent === "codex") out.codex = { sessionName: opts.sessionName };
|
|
9730
10914
|
else if (job.agent === "opencode") out.opencode = { sessionName: opts.sessionName };
|
|
9731
10915
|
}
|
|
10916
|
+
if (opts.dangerouslySkipPermissions !== void 0) {
|
|
10917
|
+
if (job.agent === "claude-code") {
|
|
10918
|
+
out.claude = { ...out.claude, dangerouslySkipPermissions: opts.dangerouslySkipPermissions };
|
|
10919
|
+
} else if (job.agent === "codex") {
|
|
10920
|
+
out.codex = { ...out.codex, dangerouslySkipPermissions: opts.dangerouslySkipPermissions };
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
9732
10923
|
return out;
|
|
9733
10924
|
}
|
|
9734
10925
|
|
|
9735
10926
|
// src/commands/screen.ts
|
|
9736
10927
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
9737
10928
|
import { log as log37 } from "@clack/prompts";
|
|
9738
|
-
import { Command as
|
|
10929
|
+
import { Command as Command36 } from "commander";
|
|
9739
10930
|
var SIGNED_URL_TTL_MIN = 1;
|
|
9740
10931
|
var SIGNED_URL_TTL_MAX = 86400;
|
|
9741
10932
|
function parseTtlOrExit(raw) {
|
|
@@ -9748,7 +10939,7 @@ function parseTtlOrExit(raw) {
|
|
|
9748
10939
|
}
|
|
9749
10940
|
return n;
|
|
9750
10941
|
}
|
|
9751
|
-
var screenCommand = new
|
|
10942
|
+
var screenCommand = new Command36("screen").description("Open a box's VNC (noVNC) viewer in the browser (auto-unpause/start)").argument(
|
|
9752
10943
|
"[box]",
|
|
9753
10944
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
9754
10945
|
).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 +11004,28 @@ var screenCommand = new Command35("screen").description("Open a box's VNC (noVNC
|
|
|
9813
11004
|
} else if (state === "missing") {
|
|
9814
11005
|
throw new Error(`cloud sandbox for ${box.name} is missing; was it deleted?`);
|
|
9815
11006
|
}
|
|
11007
|
+
const persisted = await readBoxStatus(box);
|
|
11008
|
+
const hasWebService = persisted?.services.some((s) => s.expose) ?? false;
|
|
11009
|
+
if (hasWebService) {
|
|
11010
|
+
try {
|
|
11011
|
+
const webUrl = await p.resolveUrl(box, { kind: "web" });
|
|
11012
|
+
const q = `'${webUrl.replace(/'/g, "'\\''")}'`;
|
|
11013
|
+
const br = await p.exec(box, ["bash", "-lc", `agent-browser open --headed ${q}`], {
|
|
11014
|
+
user: "vscode"
|
|
11015
|
+
});
|
|
11016
|
+
if (br.exitCode === 0) {
|
|
11017
|
+
log37.info(`opened ${webUrl} in the in-box browser (visible in the VNC view)`);
|
|
11018
|
+
} else {
|
|
11019
|
+
log37.warn(
|
|
11020
|
+
`could not open in-box browser (continuing): ${br.stderr.trim() || br.stdout.trim() || `exit ${String(br.exitCode)}`}`
|
|
11021
|
+
);
|
|
11022
|
+
}
|
|
11023
|
+
} catch (err) {
|
|
11024
|
+
log37.warn(
|
|
11025
|
+
`in-box browser skipped: ${err instanceof Error ? err.message : String(err)}`
|
|
11026
|
+
);
|
|
11027
|
+
}
|
|
11028
|
+
}
|
|
9816
11029
|
const base = await p.resolveUrl(box, { kind: "vnc", ttl });
|
|
9817
11030
|
url = `${base.replace(/\/$/, "")}/vnc.html?autoconnect=1&password=${encodeURIComponent(box.vncPassword)}`;
|
|
9818
11031
|
}
|
|
@@ -9835,7 +11048,7 @@ var screenCommand = new Command35("screen").description("Open a box's VNC (noVNC
|
|
|
9835
11048
|
// src/commands/shell.ts
|
|
9836
11049
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9837
11050
|
import { log as log39 } from "@clack/prompts";
|
|
9838
|
-
import { Command as
|
|
11051
|
+
import { Command as Command37 } from "commander";
|
|
9839
11052
|
|
|
9840
11053
|
// src/commands/_provider-guard.ts
|
|
9841
11054
|
import { log as log38 } from "@clack/prompts";
|
|
@@ -9950,7 +11163,7 @@ async function startOrAttachShell(box, cfg) {
|
|
|
9950
11163
|
});
|
|
9951
11164
|
process.exit(code);
|
|
9952
11165
|
}
|
|
9953
|
-
var shellCommand = new
|
|
11166
|
+
var shellCommand = new Command37("shell").description(
|
|
9954
11167
|
"Open an interactive shell in a box, in a detachable tmux session (auto-unpause/start)"
|
|
9955
11168
|
).argument(
|
|
9956
11169
|
"[box]",
|
|
@@ -10041,7 +11254,7 @@ var shellCommand = new Command36("shell").description(
|
|
|
10041
11254
|
handleLifecycleError(err);
|
|
10042
11255
|
}
|
|
10043
11256
|
});
|
|
10044
|
-
var shellAttachCommand = new
|
|
11257
|
+
var shellAttachCommand = new Command37("attach").description(
|
|
10045
11258
|
"Attach to a shell tmux session in a box, starting one if none is running (auto-unpause/start)"
|
|
10046
11259
|
).argument(
|
|
10047
11260
|
"[box]",
|
|
@@ -10079,7 +11292,7 @@ function renderShellTable(sessions) {
|
|
|
10079
11292
|
for (const r of rows) process.stdout.write(`${fmt(r)}
|
|
10080
11293
|
`);
|
|
10081
11294
|
}
|
|
10082
|
-
var shellLsCommand = new
|
|
11295
|
+
var shellLsCommand = new Command37("ls").description("List the shell tmux sessions running in a box").argument(
|
|
10083
11296
|
"[box]",
|
|
10084
11297
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10085
11298
|
).action(async (idOrName) => {
|
|
@@ -10102,7 +11315,7 @@ var shellLsCommand = new Command36("ls").description("List the shell tmux sessio
|
|
|
10102
11315
|
handleLifecycleError(err);
|
|
10103
11316
|
}
|
|
10104
11317
|
});
|
|
10105
|
-
var shellKillCommand = new
|
|
11318
|
+
var shellKillCommand = new Command37("kill").description("Kill a shell tmux session in a box (the shell and anything running in it)").argument(
|
|
10106
11319
|
"[box]",
|
|
10107
11320
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10108
11321
|
).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 +11353,8 @@ shellCommand.addCommand(shellLsCommand);
|
|
|
10140
11353
|
shellCommand.addCommand(shellKillCommand);
|
|
10141
11354
|
|
|
10142
11355
|
// src/commands/start.ts
|
|
10143
|
-
import { Command as
|
|
10144
|
-
var startCommand = new
|
|
11356
|
+
import { Command as Command38 } from "commander";
|
|
11357
|
+
var startCommand = new Command38("start").description(
|
|
10145
11358
|
"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
11359
|
).argument(
|
|
10147
11360
|
"[box]",
|
|
@@ -10165,7 +11378,7 @@ var startCommand = new Command37("start").description(
|
|
|
10165
11378
|
|
|
10166
11379
|
// src/commands/status.ts
|
|
10167
11380
|
import { log as log41 } from "@clack/prompts";
|
|
10168
|
-
import { Command as
|
|
11381
|
+
import { Command as Command39 } from "commander";
|
|
10169
11382
|
|
|
10170
11383
|
// src/endpoints-render.ts
|
|
10171
11384
|
function renderEndpointLines(endpoints, stream) {
|
|
@@ -10377,7 +11590,7 @@ async function runInspect(box, opts) {
|
|
|
10377
11590
|
|
|
10378
11591
|
// src/commands/status.ts
|
|
10379
11592
|
var statusCommand2 = withWatchOptions(
|
|
10380
|
-
new
|
|
11593
|
+
new Command39("status").description("Show service + task status from a box's agentbox-ctl daemon").argument(
|
|
10381
11594
|
"[box]",
|
|
10382
11595
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10383
11596
|
).option("-j, --json", "machine-readable JSON output").option("--inspect", "show detailed box info (volumes, limits, paths) instead of service/task status")
|
|
@@ -10551,8 +11764,8 @@ function renderPersisted2(s, state) {
|
|
|
10551
11764
|
}
|
|
10552
11765
|
|
|
10553
11766
|
// src/commands/stop.ts
|
|
10554
|
-
import { Command as
|
|
10555
|
-
var stopCommand = new
|
|
11767
|
+
import { Command as Command40 } from "commander";
|
|
11768
|
+
var stopCommand = new Command40("stop").description(
|
|
10556
11769
|
"Stop a box (Docker: docker stop; preserves upper + node_modules volumes. Cloud: backend.stop \u2014 sandbox stays in your account, disk preserved)."
|
|
10557
11770
|
).argument(
|
|
10558
11771
|
"[box]",
|
|
@@ -10581,7 +11794,7 @@ restart with: agentbox start ${box.name}
|
|
|
10581
11794
|
});
|
|
10582
11795
|
|
|
10583
11796
|
// src/commands/top.ts
|
|
10584
|
-
import { Command as
|
|
11797
|
+
import { Command as Command41 } from "commander";
|
|
10585
11798
|
var COLS = ["BOX", "STATE", "CPU%", "MEM USAGE / LIMIT", "MEM%", "PIDS", "DISK", "NET I/O"];
|
|
10586
11799
|
function row(name, state, s) {
|
|
10587
11800
|
const mem = `${fmtBytes(s.memUsedBytes)} / ${fmtBytes(s.memLimitBytes)}`;
|
|
@@ -10614,6 +11827,7 @@ async function selectBoxes(idOrName, opts) {
|
|
|
10614
11827
|
}
|
|
10615
11828
|
async function snapshot(idOrName, opts) {
|
|
10616
11829
|
const boxes = await selectBoxes(idOrName, opts);
|
|
11830
|
+
if (opts.live) await applyLiveCloudStates(boxes);
|
|
10617
11831
|
const stats = await Promise.all(
|
|
10618
11832
|
boxes.map((b) => {
|
|
10619
11833
|
if ((b.provider ?? "docker") !== "docker") return emptyStats(b.provider ?? "cloud");
|
|
@@ -10654,10 +11868,10 @@ async function renderProjectFooters() {
|
|
|
10654
11868
|
|
|
10655
11869
|
SYSTEM: ${parts.join(" - ")}` : "";
|
|
10656
11870
|
}
|
|
10657
|
-
var topCommand = new
|
|
11871
|
+
var topCommand = new Command41("top").description("Live resource monitor (cpu/mem/pids/disk) for a box, the project, or every box").argument(
|
|
10658
11872
|
"[box]",
|
|
10659
11873
|
"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) => {
|
|
11874
|
+
).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
11875
|
try {
|
|
10662
11876
|
if (opts.json) {
|
|
10663
11877
|
const { boxes, stats } = await snapshot(idOrName, opts);
|
|
@@ -10687,8 +11901,8 @@ var topCommand = new Command40("top").description("Live resource monitor (cpu/me
|
|
|
10687
11901
|
});
|
|
10688
11902
|
|
|
10689
11903
|
// src/commands/unpause.ts
|
|
10690
|
-
import { Command as
|
|
10691
|
-
var unpauseCommand = new
|
|
11904
|
+
import { Command as Command42 } from "commander";
|
|
11905
|
+
var unpauseCommand = new Command42("unpause").description(
|
|
10692
11906
|
"Resume a paused box. Docker: `docker unpause` (sub-second). Cloud: backend.resume (re-hydrates from archive \u2014 slower first time)."
|
|
10693
11907
|
).argument(
|
|
10694
11908
|
"[box]",
|
|
@@ -10712,8 +11926,8 @@ var unpauseCommand = new Command41("unpause").description(
|
|
|
10712
11926
|
|
|
10713
11927
|
// src/commands/update.ts
|
|
10714
11928
|
import { spawn as spawn4 } from "child_process";
|
|
10715
|
-
import { confirm as
|
|
10716
|
-
import { Command as
|
|
11929
|
+
import { confirm as confirm16, intro as intro8, isCancel as isCancel17, log as log42, outro as outro8, spinner as spinner10 } from "@clack/prompts";
|
|
11930
|
+
import { Command as Command43 } from "commander";
|
|
10717
11931
|
|
|
10718
11932
|
// src/exec-method.ts
|
|
10719
11933
|
function detectExecutionMethod(input) {
|
|
@@ -10757,7 +11971,7 @@ function runInherit(cmd, args) {
|
|
|
10757
11971
|
child.on("close", (code) => resolveP(code ?? 0));
|
|
10758
11972
|
});
|
|
10759
11973
|
}
|
|
10760
|
-
var updateCommand = new
|
|
11974
|
+
var updateCommand = new Command43("self-update").description(
|
|
10761
11975
|
"Update agentbox: self-update via npm/pnpm (unless run via npx), wipe the box image so it rebuilds, and reload the relay"
|
|
10762
11976
|
).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
11977
|
try {
|
|
@@ -10780,8 +11994,8 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10780
11994
|
return;
|
|
10781
11995
|
}
|
|
10782
11996
|
if (!opts.yes) {
|
|
10783
|
-
const ok = await
|
|
10784
|
-
if (
|
|
11997
|
+
const ok = await confirm16({ message: "Proceed with update?", initialValue: true });
|
|
11998
|
+
if (isCancel17(ok) || !ok) {
|
|
10785
11999
|
log42.info("cancelled");
|
|
10786
12000
|
return;
|
|
10787
12001
|
}
|
|
@@ -10803,13 +12017,13 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10803
12017
|
log42.success(`updated ${PKG} via ${cmd.cmd}`);
|
|
10804
12018
|
}
|
|
10805
12019
|
}
|
|
10806
|
-
const s =
|
|
12020
|
+
const s = spinner10();
|
|
10807
12021
|
s.start(`removing image ${DEFAULT_BOX_IMAGE}`);
|
|
10808
12022
|
const removed = await removeImage(DEFAULT_BOX_IMAGE);
|
|
10809
12023
|
s.stop(
|
|
10810
12024
|
removed ? `removed image ${DEFAULT_BOX_IMAGE} (rebuilds on next create/claude)` : `image ${DEFAULT_BOX_IMAGE} not present (nothing to remove)`
|
|
10811
12025
|
);
|
|
10812
|
-
const sr =
|
|
12026
|
+
const sr = spinner10();
|
|
10813
12027
|
sr.start("stopping relay");
|
|
10814
12028
|
const stop = await stopRelay();
|
|
10815
12029
|
sr.stop(
|
|
@@ -10820,7 +12034,7 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10820
12034
|
"relay will restart automatically (with the updated build) on your next `agentbox create` / `agentbox claude`"
|
|
10821
12035
|
);
|
|
10822
12036
|
} else {
|
|
10823
|
-
const sr2 =
|
|
12037
|
+
const sr2 = spinner10();
|
|
10824
12038
|
sr2.start("restarting relay");
|
|
10825
12039
|
try {
|
|
10826
12040
|
const ep = await ensureRelay();
|
|
@@ -10841,7 +12055,7 @@ var updateCommand = new Command42("self-update").description(
|
|
|
10841
12055
|
// src/commands/url.ts
|
|
10842
12056
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
10843
12057
|
import { log as log43 } from "@clack/prompts";
|
|
10844
|
-
import { Command as
|
|
12058
|
+
import { Command as Command44 } from "commander";
|
|
10845
12059
|
var SIGNED_URL_TTL_MIN2 = 1;
|
|
10846
12060
|
var SIGNED_URL_TTL_MAX2 = 86400;
|
|
10847
12061
|
function parseTtlOrExit2(raw) {
|
|
@@ -10854,7 +12068,7 @@ function parseTtlOrExit2(raw) {
|
|
|
10854
12068
|
}
|
|
10855
12069
|
return n;
|
|
10856
12070
|
}
|
|
10857
|
-
var urlCommand = new
|
|
12071
|
+
var urlCommand = new Command44("url").description(
|
|
10858
12072
|
"Open a box's web app URL in the browser, even when no service declares `expose:` (auto-unpause/start)"
|
|
10859
12073
|
).argument(
|
|
10860
12074
|
"[box]",
|
|
@@ -10933,8 +12147,8 @@ var urlCommand = new Command43("url").description(
|
|
|
10933
12147
|
|
|
10934
12148
|
// src/commands/wait.ts
|
|
10935
12149
|
import { log as log44 } from "@clack/prompts";
|
|
10936
|
-
import { Command as
|
|
10937
|
-
var waitCommand = new
|
|
12150
|
+
import { Command as Command45 } from "commander";
|
|
12151
|
+
var waitCommand = new Command45("wait").description("Block until the box reports all autostart units ready").argument(
|
|
10938
12152
|
"[box]",
|
|
10939
12153
|
"box ref: project index, id, id prefix, name, or container (default: the only box in this project)"
|
|
10940
12154
|
).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 +12188,14 @@ var SUGARED_COMMANDS = ["create", "claude", "codex", "opencode"];
|
|
|
10974
12188
|
function isSugared(name) {
|
|
10975
12189
|
return SUGARED_COMMANDS.includes(name);
|
|
10976
12190
|
}
|
|
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 =
|
|
12191
|
+
function rewriteProviderPrefix(argv2) {
|
|
12192
|
+
if (argv2.length < 4) return [...argv2];
|
|
12193
|
+
const provider = argv2[2];
|
|
12194
|
+
const subcmd = argv2[3];
|
|
12195
|
+
if (typeof provider !== "string" || typeof subcmd !== "string") return [...argv2];
|
|
12196
|
+
if (!isKnownProvider(provider) || !isSugared(subcmd)) return [...argv2];
|
|
12197
|
+
const head = argv2.slice(0, 2);
|
|
12198
|
+
const rest = argv2.slice(4);
|
|
10985
12199
|
return [...head, subcmd, "--provider", provider, ...rest];
|
|
10986
12200
|
}
|
|
10987
12201
|
|
|
@@ -10989,7 +12203,7 @@ function rewriteProviderPrefix(argv) {
|
|
|
10989
12203
|
process.env.DOCKER_CLI_HINTS ??= "false";
|
|
10990
12204
|
process.env.AGENTBOX_CLI_VERSION = AGENTBOX_VERSION;
|
|
10991
12205
|
process.env.AGENTBOX_CLI_COMMIT = AGENTBOX_COMMIT;
|
|
10992
|
-
var program = new
|
|
12206
|
+
var program = new Command46();
|
|
10993
12207
|
program.name("agentbox").description("Launch coding agents in isolated sandboxes").version(AGENTBOX_VERSION);
|
|
10994
12208
|
program.enablePositionalOptions();
|
|
10995
12209
|
program.addCommand(createCommand);
|
|
@@ -11031,10 +12245,43 @@ program.addCommand(vercelCommand);
|
|
|
11031
12245
|
program.addCommand(dockerCommand);
|
|
11032
12246
|
program.addCommand(updateCommand);
|
|
11033
12247
|
program.addCommand(installCommand);
|
|
12248
|
+
program.addCommand(doctorCommand);
|
|
11034
12249
|
program.configureHelp({ visibleCommands: () => [] });
|
|
11035
12250
|
program.addHelpText("after", () => "\n" + buildGroupedHelp(program));
|
|
11036
12251
|
await applyEngineOverrideAtStartup();
|
|
11037
|
-
|
|
12252
|
+
var argv = rewriteProviderPrefix(process.argv);
|
|
12253
|
+
var FIRST_RUN_EXEMPT = /* @__PURE__ */ new Set([
|
|
12254
|
+
"install",
|
|
12255
|
+
"doctor",
|
|
12256
|
+
"help",
|
|
12257
|
+
"relay",
|
|
12258
|
+
"_run-queued-job",
|
|
12259
|
+
"drive",
|
|
12260
|
+
"screen"
|
|
12261
|
+
]);
|
|
12262
|
+
function isFirstRunHookEligible(args) {
|
|
12263
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
12264
|
+
const rest = args.slice(2);
|
|
12265
|
+
if (rest.length === 0) return false;
|
|
12266
|
+
for (const a of rest) {
|
|
12267
|
+
if (a === "--help" || a === "-h" || a === "--version" || a === "-V") return false;
|
|
12268
|
+
}
|
|
12269
|
+
const first = rest[0];
|
|
12270
|
+
if (typeof first !== "string" || first.startsWith("-")) return false;
|
|
12271
|
+
if (FIRST_RUN_EXEMPT.has(first)) return false;
|
|
12272
|
+
return true;
|
|
12273
|
+
}
|
|
12274
|
+
if (isFirstRun() && isFirstRunHookEligible(argv)) {
|
|
12275
|
+
try {
|
|
12276
|
+
await runInstallWizard({ fromAutoTrigger: true });
|
|
12277
|
+
} catch (err) {
|
|
12278
|
+
process.stderr.write(
|
|
12279
|
+
`install wizard failed: ${err instanceof Error ? err.message : String(err)}
|
|
12280
|
+
`
|
|
12281
|
+
);
|
|
12282
|
+
}
|
|
12283
|
+
}
|
|
12284
|
+
program.parseAsync(argv).catch((err) => {
|
|
11038
12285
|
console.error(err);
|
|
11039
12286
|
process.exit(1);
|
|
11040
12287
|
});
|