@madarco/agentbox 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-J35IH7W5.js → chunk-BBZMA2K6.js} +61 -23
- package/dist/chunk-BBZMA2K6.js.map +1 -0
- package/dist/{chunk-SOMIKEN2.js → chunk-HHMWQNLF.js} +272 -214
- package/dist/chunk-HHMWQNLF.js.map +1 -0
- package/dist/{chunk-IDR4HVIC.js → chunk-HPZMD5DE.js} +2 -2
- package/dist/chunk-HPZMD5DE.js.map +1 -0
- package/dist/{chunk-NSIECUCS.js → chunk-HTTKML3C.js} +705 -289
- package/dist/chunk-HTTKML3C.js.map +1 -0
- package/dist/{chunk-WR5FFGE5.js → chunk-KJNZP6I3.js} +218 -128
- package/dist/chunk-KJNZP6I3.js.map +1 -0
- package/dist/{chunk-FQD6ZWYW.js → chunk-M7I247BK.js} +68 -65
- package/dist/chunk-M7I247BK.js.map +1 -0
- package/dist/create-6PWXI6HO-OWAMHBAK.js +15 -0
- package/dist/index.js +2394 -1283
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js → lifecycle-EMXR46DI-DUVBXNTV.js} +5 -5
- package/dist/{state-ZSP3ORXW-WI6KOIG3.js → state-KD7M46ZP-KHFTHFUS.js} +2 -2
- package/dist/stats-SZXOJE3D-N7OODCHW.js +19 -0
- package/package.json +3 -2
- package/runtime/docker/Dockerfile.box +65 -25
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +52 -55
- package/runtime/docker/packages/ctl/dist/bin.cjs +272 -160
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +52 -0
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +21 -15
- package/runtime/relay/bin.cjs +407 -12
- package/share/agentbox-setup/SKILL.md +52 -55
- package/dist/chunk-FQD6ZWYW.js.map +0 -1
- package/dist/chunk-IDR4HVIC.js.map +0 -1
- package/dist/chunk-J35IH7W5.js.map +0 -1
- package/dist/chunk-NSIECUCS.js.map +0 -1
- package/dist/chunk-SOMIKEN2.js.map +0 -1
- package/dist/chunk-WR5FFGE5.js.map +0 -1
- package/dist/create-4BQY2UYU-CGSW3RGE.js +0 -15
- package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +0 -19
- /package/dist/{create-4BQY2UYU-CGSW3RGE.js.map → create-6PWXI6HO-OWAMHBAK.js.map} +0 -0
- /package/dist/{lifecycle-LURNDNYO-UWQYPNPX.js.map → lifecycle-EMXR46DI-DUVBXNTV.js.map} +0 -0
- /package/dist/{state-ZSP3ORXW-WI6KOIG3.js.map → state-KD7M46ZP-KHFTHFUS.js.map} +0 -0
- /package/dist/{stats-GZFLPYTU-DBJ2DVBJ.js.map → stats-SZXOJE3D-N7OODCHW.js.map} +0 -0
|
@@ -10371,8 +10371,27 @@ var {
|
|
|
10371
10371
|
} = import_index.default;
|
|
10372
10372
|
|
|
10373
10373
|
// src/client.ts
|
|
10374
|
+
var import_node_child_process = require("child_process");
|
|
10375
|
+
var import_node_fs = require("fs");
|
|
10374
10376
|
var import_node_net = require("net");
|
|
10375
|
-
async function
|
|
10377
|
+
async function tryReviveDaemon(socketPath) {
|
|
10378
|
+
if (process.env.AGENTBOX !== "1") return false;
|
|
10379
|
+
try {
|
|
10380
|
+
const child = (0, import_node_child_process.spawn)("agentbox-ctl", ["daemon"], {
|
|
10381
|
+
detached: true,
|
|
10382
|
+
stdio: "ignore"
|
|
10383
|
+
});
|
|
10384
|
+
child.unref();
|
|
10385
|
+
} catch {
|
|
10386
|
+
return false;
|
|
10387
|
+
}
|
|
10388
|
+
for (let i = 0; i < 50; i++) {
|
|
10389
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
10390
|
+
if ((0, import_node_fs.existsSync)(socketPath)) return true;
|
|
10391
|
+
}
|
|
10392
|
+
return false;
|
|
10393
|
+
}
|
|
10394
|
+
async function connectOnce(opts) {
|
|
10376
10395
|
const sock = (0, import_node_net.createConnection)(opts.socketPath);
|
|
10377
10396
|
await new Promise((resolve, reject) => {
|
|
10378
10397
|
const timer = setTimeout(() => {
|
|
@@ -10390,6 +10409,17 @@ async function connect(opts) {
|
|
|
10390
10409
|
});
|
|
10391
10410
|
return sock;
|
|
10392
10411
|
}
|
|
10412
|
+
async function connect(opts) {
|
|
10413
|
+
try {
|
|
10414
|
+
return await connectOnce(opts);
|
|
10415
|
+
} catch (err) {
|
|
10416
|
+
const code = err.code;
|
|
10417
|
+
if (code !== "ECONNREFUSED" && code !== "ENOENT") throw err;
|
|
10418
|
+
const revived = await tryReviveDaemon(opts.socketPath);
|
|
10419
|
+
if (!revived) throw err;
|
|
10420
|
+
return await connectOnce(opts);
|
|
10421
|
+
}
|
|
10422
|
+
}
|
|
10393
10423
|
async function sendOneShot(opts, req) {
|
|
10394
10424
|
const sock = await connect(opts);
|
|
10395
10425
|
sock.write(`${JSON.stringify(req)}
|
|
@@ -10545,6 +10575,108 @@ var claudeStateCommand = new Command("claude-state").description("Report Claude
|
|
|
10545
10575
|
process.exit(0);
|
|
10546
10576
|
});
|
|
10547
10577
|
|
|
10578
|
+
// src/relay-rpc.ts
|
|
10579
|
+
var import_node_http = require("http");
|
|
10580
|
+
var import_node_https = require("https");
|
|
10581
|
+
function postRpc(method, params, opts = {}) {
|
|
10582
|
+
const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
|
|
10583
|
+
const urlStr = process.env.AGENTBOX_RELAY_URL;
|
|
10584
|
+
const token = process.env.AGENTBOX_RELAY_TOKEN;
|
|
10585
|
+
if (!urlStr || !token) {
|
|
10586
|
+
process.stderr.write(
|
|
10587
|
+
`${prefix}: AGENTBOX_RELAY_URL / AGENTBOX_RELAY_TOKEN not set; no relay configured for this box.
|
|
10588
|
+
`
|
|
10589
|
+
);
|
|
10590
|
+
return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
|
|
10591
|
+
}
|
|
10592
|
+
let url;
|
|
10593
|
+
try {
|
|
10594
|
+
url = new URL(urlStr);
|
|
10595
|
+
} catch {
|
|
10596
|
+
process.stderr.write(`${prefix}: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
10597
|
+
`);
|
|
10598
|
+
return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
|
|
10599
|
+
}
|
|
10600
|
+
const body = JSON.stringify({ method, params });
|
|
10601
|
+
const isHttps = url.protocol === "https:";
|
|
10602
|
+
const transport = isHttps ? import_node_https.request : import_node_http.request;
|
|
10603
|
+
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
10604
|
+
return new Promise((resolve) => {
|
|
10605
|
+
const req = transport(
|
|
10606
|
+
{
|
|
10607
|
+
host: url.hostname,
|
|
10608
|
+
port,
|
|
10609
|
+
method: "POST",
|
|
10610
|
+
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
10611
|
+
headers: {
|
|
10612
|
+
"Content-Type": "application/json",
|
|
10613
|
+
"Content-Length": Buffer.byteLength(body).toString(),
|
|
10614
|
+
Authorization: `Bearer ${token}`
|
|
10615
|
+
}
|
|
10616
|
+
},
|
|
10617
|
+
(res) => {
|
|
10618
|
+
const chunks = [];
|
|
10619
|
+
res.on("data", (c) => chunks.push(c));
|
|
10620
|
+
res.on("end", () => {
|
|
10621
|
+
const status2 = res.statusCode ?? 0;
|
|
10622
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
10623
|
+
let parsed = null;
|
|
10624
|
+
try {
|
|
10625
|
+
const v = JSON.parse(text);
|
|
10626
|
+
if (v && typeof v === "object" && typeof v.exitCode === "number") {
|
|
10627
|
+
parsed = v;
|
|
10628
|
+
}
|
|
10629
|
+
} catch {
|
|
10630
|
+
parsed = null;
|
|
10631
|
+
}
|
|
10632
|
+
resolve({ status: status2, parsed, raw: text, internalExitCode: null });
|
|
10633
|
+
});
|
|
10634
|
+
}
|
|
10635
|
+
);
|
|
10636
|
+
req.on("error", (err) => {
|
|
10637
|
+
process.stderr.write(`${prefix}: ${String(err.message ?? err)}
|
|
10638
|
+
`);
|
|
10639
|
+
resolve({ status: 0, parsed: null, raw: "", internalExitCode: 126 });
|
|
10640
|
+
});
|
|
10641
|
+
req.write(body);
|
|
10642
|
+
req.end();
|
|
10643
|
+
});
|
|
10644
|
+
}
|
|
10645
|
+
async function postRpcAndExit(method, params, opts = {}) {
|
|
10646
|
+
const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
|
|
10647
|
+
const out = await postRpc(method, params, opts);
|
|
10648
|
+
if (out.internalExitCode !== null) return out.internalExitCode;
|
|
10649
|
+
if (out.parsed) {
|
|
10650
|
+
if (out.parsed.stdout) process.stdout.write(out.parsed.stdout);
|
|
10651
|
+
if (out.parsed.stderr) process.stderr.write(out.parsed.stderr);
|
|
10652
|
+
return out.parsed.exitCode;
|
|
10653
|
+
}
|
|
10654
|
+
process.stderr.write(`${prefix}: relay returned ${String(out.status)}: ${out.raw}
|
|
10655
|
+
`);
|
|
10656
|
+
return out.status >= 200 && out.status < 300 ? 0 : 1;
|
|
10657
|
+
}
|
|
10658
|
+
|
|
10659
|
+
// src/commands/cp.ts
|
|
10660
|
+
var cpCommand = new Command("cp").description("Copy a file/dir between this box and the host (gated by user prompt on the host wrapper)").addCommand(
|
|
10661
|
+
new Command("toHost").description("Copy box:<boxPath> -> host:<hostPath>").argument("<boxPath>", "source path inside the container").argument("<hostPath>", "destination path on the host").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (boxPath, hostPath, opts) => {
|
|
10662
|
+
const params = { boxPath, hostPath };
|
|
10663
|
+
if (opts.recursive === false) params.recursive = false;
|
|
10664
|
+
const code = await postRpcAndExit("cp.toHost", params, {
|
|
10665
|
+
errorPrefix: "agentbox-ctl cp"
|
|
10666
|
+
});
|
|
10667
|
+
process.exit(code);
|
|
10668
|
+
})
|
|
10669
|
+
).addCommand(
|
|
10670
|
+
new Command("fromHost").description("Copy host:<hostPath> -> box:<boxPath>").argument("<hostPath>", "source path on the host").argument("<boxPath>", "destination path inside the container").option("--no-recursive", "reserved; current implementation is always recursive (docker cp -a)").action(async (hostPath, boxPath, opts) => {
|
|
10671
|
+
const params = { boxPath, hostPath };
|
|
10672
|
+
if (opts.recursive === false) params.recursive = false;
|
|
10673
|
+
const code = await postRpcAndExit("cp.fromHost", params, {
|
|
10674
|
+
errorPrefix: "agentbox-ctl cp"
|
|
10675
|
+
});
|
|
10676
|
+
process.exit(code);
|
|
10677
|
+
})
|
|
10678
|
+
);
|
|
10679
|
+
|
|
10548
10680
|
// src/config.ts
|
|
10549
10681
|
var import_promises = require("fs/promises");
|
|
10550
10682
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -10975,9 +11107,9 @@ function describeCommand(cmd) {
|
|
|
10975
11107
|
}
|
|
10976
11108
|
|
|
10977
11109
|
// src/supervisor.ts
|
|
10978
|
-
var
|
|
11110
|
+
var import_node_child_process2 = require("child_process");
|
|
10979
11111
|
var import_node_events = require("events");
|
|
10980
|
-
var
|
|
11112
|
+
var import_node_fs2 = require("fs");
|
|
10981
11113
|
var import_promises3 = require("fs/promises");
|
|
10982
11114
|
var import_node_path = require("path");
|
|
10983
11115
|
|
|
@@ -11088,8 +11220,8 @@ function startProbe(probe, ctx) {
|
|
|
11088
11220
|
}
|
|
11089
11221
|
|
|
11090
11222
|
// src/relay-client.ts
|
|
11091
|
-
var
|
|
11092
|
-
var
|
|
11223
|
+
var import_node_http2 = require("http");
|
|
11224
|
+
var import_node_https2 = require("https");
|
|
11093
11225
|
var RelayClient = class {
|
|
11094
11226
|
url;
|
|
11095
11227
|
token;
|
|
@@ -11115,7 +11247,7 @@ var RelayClient = class {
|
|
|
11115
11247
|
const url = this.url;
|
|
11116
11248
|
const body = JSON.stringify({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), payload });
|
|
11117
11249
|
const isHttps = url.protocol === "https:";
|
|
11118
|
-
const transport = isHttps ?
|
|
11250
|
+
const transport = isHttps ? import_node_https2.request : import_node_http2.request;
|
|
11119
11251
|
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
11120
11252
|
const req = transport(
|
|
11121
11253
|
{
|
|
@@ -11237,12 +11369,26 @@ function spawnArgs(cmd) {
|
|
|
11237
11369
|
if (typeof cmd === "string") return { bin: "bash", args: ["-c", cmd] };
|
|
11238
11370
|
return { bin: cmd[0], args: cmd.slice(1) };
|
|
11239
11371
|
}
|
|
11372
|
+
var cachedLoginPath;
|
|
11373
|
+
function loginShellPath() {
|
|
11374
|
+
if (cachedLoginPath !== void 0) return cachedLoginPath;
|
|
11375
|
+
try {
|
|
11376
|
+
const out = (0, import_node_child_process2.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
|
|
11377
|
+
encoding: "utf8",
|
|
11378
|
+
timeout: 5e3
|
|
11379
|
+
}).trim();
|
|
11380
|
+
cachedLoginPath = out || (process.env.PATH ?? "");
|
|
11381
|
+
} catch {
|
|
11382
|
+
cachedLoginPath = process.env.PATH ?? "";
|
|
11383
|
+
}
|
|
11384
|
+
return cachedLoginPath;
|
|
11385
|
+
}
|
|
11240
11386
|
var ServiceRunner = class extends import_node_events.EventEmitter {
|
|
11241
11387
|
constructor(spec, opts) {
|
|
11242
11388
|
super();
|
|
11243
11389
|
this.spec = spec;
|
|
11244
11390
|
this.opts = opts;
|
|
11245
|
-
this.spawnFn = opts.spawn ??
|
|
11391
|
+
this.spawnFn = opts.spawn ?? import_node_child_process2.spawn;
|
|
11246
11392
|
this.setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
11247
11393
|
this.clearTimer = opts.clearTimer ?? ((h) => {
|
|
11248
11394
|
clearTimeout(h);
|
|
@@ -11339,7 +11485,7 @@ var ServiceRunner = class extends import_node_events.EventEmitter {
|
|
|
11339
11485
|
const spec = this.spec;
|
|
11340
11486
|
const cwd = resolveCwd(spec.cwd, this.opts.cwd);
|
|
11341
11487
|
if (!this.logStream) {
|
|
11342
|
-
this.logStream = (0,
|
|
11488
|
+
this.logStream = (0, import_node_fs2.createWriteStream)((0, import_node_path.join)(this.opts.logDir, `${spec.name}.log`), {
|
|
11343
11489
|
flags: "a"
|
|
11344
11490
|
});
|
|
11345
11491
|
this.logStream.on("error", (err) => {
|
|
@@ -11351,7 +11497,7 @@ var ServiceRunner = class extends import_node_events.EventEmitter {
|
|
|
11351
11497
|
try {
|
|
11352
11498
|
child = this.spawnFn(bin, args, {
|
|
11353
11499
|
cwd,
|
|
11354
|
-
env: { ...process.env, ...spec.env ?? {} },
|
|
11500
|
+
env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
|
|
11355
11501
|
stdio: ["ignore", "pipe", "pipe"]
|
|
11356
11502
|
});
|
|
11357
11503
|
} catch (err) {
|
|
@@ -11473,7 +11619,7 @@ var TaskRunner = class extends import_node_events.EventEmitter {
|
|
|
11473
11619
|
super();
|
|
11474
11620
|
this.spec = spec;
|
|
11475
11621
|
this.opts = opts;
|
|
11476
|
-
this.spawnFn = opts.spawn ??
|
|
11622
|
+
this.spawnFn = opts.spawn ?? import_node_child_process2.spawn;
|
|
11477
11623
|
}
|
|
11478
11624
|
spec;
|
|
11479
11625
|
opts;
|
|
@@ -11537,7 +11683,7 @@ var TaskRunner = class extends import_node_events.EventEmitter {
|
|
|
11537
11683
|
const spec = this.spec;
|
|
11538
11684
|
const cwd = resolveCwd(spec.cwd, this.opts.cwd);
|
|
11539
11685
|
if (!this.logStream) {
|
|
11540
|
-
this.logStream = (0,
|
|
11686
|
+
this.logStream = (0, import_node_fs2.createWriteStream)((0, import_node_path.join)(this.opts.logDir, `${spec.name}.log`), {
|
|
11541
11687
|
flags: "a"
|
|
11542
11688
|
});
|
|
11543
11689
|
this.logStream.on("error", (err) => {
|
|
@@ -11549,7 +11695,7 @@ var TaskRunner = class extends import_node_events.EventEmitter {
|
|
|
11549
11695
|
try {
|
|
11550
11696
|
child = this.spawnFn(bin, args, {
|
|
11551
11697
|
cwd,
|
|
11552
|
-
env: { ...process.env, ...spec.env ?? {} },
|
|
11698
|
+
env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
|
|
11553
11699
|
stdio: ["ignore", "pipe", "pipe"]
|
|
11554
11700
|
});
|
|
11555
11701
|
} catch (err) {
|
|
@@ -11853,6 +11999,14 @@ var Supervisor = class extends import_node_events.EventEmitter {
|
|
|
11853
11999
|
changed.push(spec.name);
|
|
11854
12000
|
}
|
|
11855
12001
|
}
|
|
12002
|
+
for (const [name, unit] of this.units) {
|
|
12003
|
+
if (unit.kind !== "task") continue;
|
|
12004
|
+
const task = unit;
|
|
12005
|
+
if (task.getState() === "skipped") {
|
|
12006
|
+
this.failed.delete(name);
|
|
12007
|
+
task.resetForRerun();
|
|
12008
|
+
}
|
|
12009
|
+
}
|
|
11856
12010
|
this.applyWebProxy();
|
|
11857
12011
|
this.schedule();
|
|
11858
12012
|
return { added, removed, changed };
|
|
@@ -12013,10 +12167,10 @@ var import_promises4 = require("fs/promises");
|
|
|
12013
12167
|
var import_node_path2 = require("path");
|
|
12014
12168
|
|
|
12015
12169
|
// src/status-reporter.ts
|
|
12016
|
-
var
|
|
12170
|
+
var import_node_child_process4 = require("child_process");
|
|
12017
12171
|
|
|
12018
12172
|
// src/tmux.ts
|
|
12019
|
-
var
|
|
12173
|
+
var import_node_child_process3 = require("child_process");
|
|
12020
12174
|
var import_node_os = require("os");
|
|
12021
12175
|
var MAX_TITLE_LEN = 120;
|
|
12022
12176
|
function sanitizePaneTitle(raw, ctx) {
|
|
@@ -12030,7 +12184,7 @@ function sanitizePaneTitle(raw, ctx) {
|
|
|
12030
12184
|
}
|
|
12031
12185
|
function runTool(cmd, args) {
|
|
12032
12186
|
return new Promise((resolve) => {
|
|
12033
|
-
const child = (0,
|
|
12187
|
+
const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
12034
12188
|
let stdout = "";
|
|
12035
12189
|
let stderr = "";
|
|
12036
12190
|
child.stdout.on("data", (b) => stdout += b.toString("utf8"));
|
|
@@ -12174,7 +12328,7 @@ async function collectPorts(supervisor) {
|
|
|
12174
12328
|
}
|
|
12175
12329
|
function run(cmd, args) {
|
|
12176
12330
|
return new Promise((resolve) => {
|
|
12177
|
-
const child = (0,
|
|
12331
|
+
const child = (0, import_node_child_process4.spawn)(cmd, args, { stdio: ["ignore", "pipe", "ignore"] });
|
|
12178
12332
|
let stdout = "";
|
|
12179
12333
|
child.stdout.on("data", (b) => stdout += b.toString("utf8"));
|
|
12180
12334
|
child.on("error", () => resolve({ exitCode: 127, stdout }));
|
|
@@ -12221,7 +12375,8 @@ async function startServer(opts) {
|
|
|
12221
12375
|
resolve();
|
|
12222
12376
|
});
|
|
12223
12377
|
});
|
|
12224
|
-
await (0, import_promises4.chmod)(opts.socketPath, 432)
|
|
12378
|
+
await (0, import_promises4.chmod)(opts.socketPath, 432).catch(() => {
|
|
12379
|
+
});
|
|
12225
12380
|
return server;
|
|
12226
12381
|
}
|
|
12227
12382
|
async function handleConnection(sock, opts) {
|
|
@@ -12427,174 +12582,127 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
|
|
|
12427
12582
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
12428
12583
|
});
|
|
12429
12584
|
|
|
12430
|
-
// src/commands/
|
|
12431
|
-
var
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12585
|
+
// src/commands/download.ts
|
|
12586
|
+
var KINDS = ["workspace", "env", "config", "claude"];
|
|
12587
|
+
function isKind(v) {
|
|
12588
|
+
return KINDS.includes(v);
|
|
12589
|
+
}
|
|
12590
|
+
var downloadCommand = new Command("download").description(
|
|
12591
|
+
"Download box contents to the host (gated by user prompt). Kinds: workspace (default), env, config, claude"
|
|
12592
|
+
).argument("[kind]", `one of: ${KINDS.join(", ")}`, "workspace").action(async (kindArg) => {
|
|
12593
|
+
if (!isKind(kindArg)) {
|
|
12437
12594
|
process.stderr.write(
|
|
12438
|
-
|
|
12439
|
-
);
|
|
12440
|
-
return 65;
|
|
12441
|
-
}
|
|
12442
|
-
let url;
|
|
12443
|
-
try {
|
|
12444
|
-
url = new URL(urlStr);
|
|
12445
|
-
} catch {
|
|
12446
|
-
process.stderr.write(`agentbox-ctl checkpoint: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
12447
|
-
`);
|
|
12448
|
-
return 65;
|
|
12449
|
-
}
|
|
12450
|
-
const body = JSON.stringify({ method: "checkpoint.create", params });
|
|
12451
|
-
const isHttps = url.protocol === "https:";
|
|
12452
|
-
const transport = isHttps ? import_node_https2.request : import_node_http2.request;
|
|
12453
|
-
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
12454
|
-
return new Promise((resolve) => {
|
|
12455
|
-
const req = transport(
|
|
12456
|
-
{
|
|
12457
|
-
host: url.hostname,
|
|
12458
|
-
port,
|
|
12459
|
-
method: "POST",
|
|
12460
|
-
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
12461
|
-
headers: {
|
|
12462
|
-
"Content-Type": "application/json",
|
|
12463
|
-
"Content-Length": Buffer.byteLength(body).toString(),
|
|
12464
|
-
Authorization: `Bearer ${token}`
|
|
12465
|
-
}
|
|
12466
|
-
},
|
|
12467
|
-
(res) => {
|
|
12468
|
-
const chunks = [];
|
|
12469
|
-
res.on("data", (c) => chunks.push(c));
|
|
12470
|
-
res.on("end", () => {
|
|
12471
|
-
const status2 = res.statusCode ?? 0;
|
|
12472
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
12473
|
-
let parsed = null;
|
|
12474
|
-
try {
|
|
12475
|
-
parsed = JSON.parse(text);
|
|
12476
|
-
} catch {
|
|
12477
|
-
parsed = null;
|
|
12478
|
-
}
|
|
12479
|
-
if (parsed && typeof parsed.exitCode === "number") {
|
|
12480
|
-
if (parsed.stdout) process.stdout.write(parsed.stdout);
|
|
12481
|
-
if (parsed.stderr) process.stderr.write(parsed.stderr);
|
|
12482
|
-
resolve(parsed.exitCode);
|
|
12483
|
-
return;
|
|
12484
|
-
}
|
|
12485
|
-
process.stderr.write(
|
|
12486
|
-
`agentbox-ctl checkpoint: relay returned ${String(status2)}: ${text}
|
|
12595
|
+
`agentbox-ctl download: unknown kind "${kindArg}"; expected one of: ${KINDS.join(", ")}
|
|
12487
12596
|
`
|
|
12488
|
-
);
|
|
12489
|
-
resolve(status2 >= 200 && status2 < 300 ? 0 : 1);
|
|
12490
|
-
});
|
|
12491
|
-
}
|
|
12492
12597
|
);
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
req.write(body);
|
|
12499
|
-
req.end();
|
|
12598
|
+
process.exit(64);
|
|
12599
|
+
}
|
|
12600
|
+
const params = { kind: kindArg };
|
|
12601
|
+
const code = await postRpcAndExit(`download.${kindArg}`, params, {
|
|
12602
|
+
errorPrefix: "agentbox-ctl download"
|
|
12500
12603
|
});
|
|
12501
|
-
}
|
|
12502
|
-
var checkpointCommand = new Command("checkpoint").description("Capture this box as a project checkpoint (host-side, via the agentbox relay)").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").action(async (opts) => {
|
|
12503
|
-
const params = {};
|
|
12504
|
-
if (opts.name) params.name = opts.name;
|
|
12505
|
-
if (opts.merged === true) params.merged = true;
|
|
12506
|
-
if (opts.setDefault === true) params.setDefault = true;
|
|
12507
|
-
const code = await rpc(params);
|
|
12508
12604
|
process.exit(code);
|
|
12509
12605
|
});
|
|
12510
12606
|
|
|
12511
|
-
// src/commands/
|
|
12512
|
-
var
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
);
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
} catch {
|
|
12527
|
-
process.stderr.write(`agentbox-ctl git: invalid AGENTBOX_RELAY_URL: ${urlStr}
|
|
12528
|
-
`);
|
|
12529
|
-
return 65;
|
|
12607
|
+
// src/commands/checkpoint.ts
|
|
12608
|
+
var checkpointCommand = new Command("checkpoint").description("Capture this box as a project checkpoint (host-side, via the agentbox relay)").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(
|
|
12609
|
+
"--replace",
|
|
12610
|
+
"if a checkpoint with the same name exists, rm it first (idempotent recapture; safe to retry when the previous run's stdout was lost)"
|
|
12611
|
+
).action(
|
|
12612
|
+
async (opts) => {
|
|
12613
|
+
const params = {};
|
|
12614
|
+
if (opts.name) params.name = opts.name;
|
|
12615
|
+
if (opts.merged === true) params.merged = true;
|
|
12616
|
+
if (opts.setDefault === true) params.setDefault = true;
|
|
12617
|
+
if (opts.replace === true) params.replace = true;
|
|
12618
|
+
const code = await postRpcAndExit("checkpoint.create", params, {
|
|
12619
|
+
errorPrefix: "agentbox-ctl checkpoint"
|
|
12620
|
+
});
|
|
12621
|
+
process.exit(code);
|
|
12530
12622
|
}
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12623
|
+
);
|
|
12624
|
+
|
|
12625
|
+
// src/commands/git.ts
|
|
12626
|
+
var import_node_child_process5 = require("child_process");
|
|
12627
|
+
function buildParams(opts, extra) {
|
|
12628
|
+
const params = { path: opts.cwd ?? process.cwd() };
|
|
12534
12629
|
if (opts.remote) params.remote = opts.remote;
|
|
12535
12630
|
if (extra.length > 0) params.args = extra;
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
|
|
12631
|
+
return params;
|
|
12632
|
+
}
|
|
12633
|
+
function runLocalGit(args, cwd) {
|
|
12540
12634
|
return new Promise((resolve) => {
|
|
12541
|
-
const
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
port,
|
|
12545
|
-
method: "POST",
|
|
12546
|
-
path: `${url.pathname.replace(/\/$/, "")}/rpc`,
|
|
12547
|
-
headers: {
|
|
12548
|
-
"Content-Type": "application/json",
|
|
12549
|
-
"Content-Length": Buffer.byteLength(body).toString(),
|
|
12550
|
-
Authorization: `Bearer ${token}`
|
|
12551
|
-
}
|
|
12552
|
-
},
|
|
12553
|
-
(res) => {
|
|
12554
|
-
const chunks = [];
|
|
12555
|
-
res.on("data", (c) => chunks.push(c));
|
|
12556
|
-
res.on("end", () => {
|
|
12557
|
-
const status2 = res.statusCode ?? 0;
|
|
12558
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
12559
|
-
let parsed = null;
|
|
12560
|
-
try {
|
|
12561
|
-
parsed = JSON.parse(text);
|
|
12562
|
-
} catch {
|
|
12563
|
-
parsed = null;
|
|
12564
|
-
}
|
|
12565
|
-
if (parsed && typeof parsed.exitCode === "number") {
|
|
12566
|
-
if (parsed.stdout) process.stdout.write(parsed.stdout);
|
|
12567
|
-
if (parsed.stderr) process.stderr.write(parsed.stderr);
|
|
12568
|
-
resolve(parsed.exitCode);
|
|
12569
|
-
return;
|
|
12570
|
-
}
|
|
12571
|
-
process.stderr.write(`agentbox-ctl git: relay returned ${String(status2)}: ${text}
|
|
12572
|
-
`);
|
|
12573
|
-
resolve(status2 >= 200 && status2 < 300 ? 0 : 1);
|
|
12574
|
-
});
|
|
12575
|
-
}
|
|
12576
|
-
);
|
|
12577
|
-
req.on("error", (err) => {
|
|
12635
|
+
const child = (0, import_node_child_process5.spawn)("git", args, { cwd, stdio: "inherit" });
|
|
12636
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
12637
|
+
child.on("error", (err) => {
|
|
12578
12638
|
process.stderr.write(`agentbox-ctl git: ${String(err.message ?? err)}
|
|
12579
12639
|
`);
|
|
12580
12640
|
resolve(126);
|
|
12581
12641
|
});
|
|
12582
|
-
req.write(body);
|
|
12583
|
-
req.end();
|
|
12584
12642
|
});
|
|
12585
12643
|
}
|
|
12586
12644
|
var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
|
|
12587
|
-
new Command("
|
|
12588
|
-
const code = await
|
|
12645
|
+
new Command("push").description("Run `git push` on the host main repo against this box's branch (user is prompted on the host wrapper to confirm)").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git push").action(async (args, opts) => {
|
|
12646
|
+
const code = await postRpcAndExit("git.push", buildParams(opts, args), {
|
|
12647
|
+
errorPrefix: "agentbox-ctl git"
|
|
12648
|
+
});
|
|
12589
12649
|
process.exit(code);
|
|
12590
12650
|
})
|
|
12591
12651
|
).addCommand(
|
|
12592
|
-
new Command("
|
|
12593
|
-
const code = await
|
|
12652
|
+
new Command("fetch").description("Run `git fetch` on the host main repo (refs land in the shared .git)").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git fetch").action(async (args, opts) => {
|
|
12653
|
+
const code = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
12654
|
+
errorPrefix: "agentbox-ctl git"
|
|
12655
|
+
});
|
|
12594
12656
|
process.exit(code);
|
|
12595
12657
|
})
|
|
12658
|
+
).addCommand(
|
|
12659
|
+
new Command("pull").description(
|
|
12660
|
+
"Fetch via the relay (host creds), then merge into the in-container working tree locally"
|
|
12661
|
+
).option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "container path identifying which registered worktree to use").option("--ff-only", "pass --ff-only to the local merge").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git fetch").action(
|
|
12662
|
+
async (args, opts) => {
|
|
12663
|
+
const fetchCode = await postRpcAndExit("git.fetch", buildParams(opts, args), {
|
|
12664
|
+
errorPrefix: "agentbox-ctl git"
|
|
12665
|
+
});
|
|
12666
|
+
if (fetchCode !== 0) process.exit(fetchCode);
|
|
12667
|
+
const remote = opts.remote ?? "origin";
|
|
12668
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
12669
|
+
const mergeArgs = ["merge"];
|
|
12670
|
+
if (opts.ffOnly) mergeArgs.push("--ff-only");
|
|
12671
|
+
mergeArgs.push(`${remote}/HEAD`);
|
|
12672
|
+
const mergeCode = await runLocalGit(mergeArgs, cwd);
|
|
12673
|
+
process.exit(mergeCode);
|
|
12674
|
+
}
|
|
12675
|
+
)
|
|
12676
|
+
);
|
|
12677
|
+
|
|
12678
|
+
// src/commands/notify.ts
|
|
12679
|
+
async function reportState(opts, state) {
|
|
12680
|
+
try {
|
|
12681
|
+
await claudeState({ socketPath: opts.socket, timeoutMs: 1500 }, state);
|
|
12682
|
+
} catch {
|
|
12683
|
+
}
|
|
12684
|
+
}
|
|
12685
|
+
var notifyCommand = new Command("notify").description(
|
|
12686
|
+
"Signal that the in-box agent is waiting for user input (highlights the box in the dashboard)"
|
|
12687
|
+
).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--message <text>", "reserved for future use; accepted but ignored in v1").action(async (opts) => {
|
|
12688
|
+
await reportState(opts, "waiting");
|
|
12689
|
+
process.exit(0);
|
|
12690
|
+
}).addCommand(
|
|
12691
|
+
new Command("clear").description("Clear the waiting state (alias for `claude-state idle`)").option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).action(async (opts) => {
|
|
12692
|
+
await reportState(opts, "idle");
|
|
12693
|
+
process.exit(0);
|
|
12694
|
+
})
|
|
12596
12695
|
);
|
|
12597
12696
|
|
|
12697
|
+
// src/commands/open.ts
|
|
12698
|
+
var openCommand = new Command("open").description("Open a URL in the host's default browser (via the agentbox relay)").argument("<url>", "http(s) URL to open on the host").action(async (url) => {
|
|
12699
|
+
const params = { url };
|
|
12700
|
+
const code = await postRpcAndExit("browser.open", params, {
|
|
12701
|
+
errorPrefix: "agentbox-ctl open"
|
|
12702
|
+
});
|
|
12703
|
+
process.exit(code);
|
|
12704
|
+
});
|
|
12705
|
+
|
|
12598
12706
|
// src/render.ts
|
|
12599
12707
|
function renderStatusTable(rows) {
|
|
12600
12708
|
if (rows.length === 0) return "(no services configured)";
|
|
@@ -12783,6 +12891,10 @@ program2.addCommand(waitReadyCommand);
|
|
|
12783
12891
|
program2.addCommand(runTaskCommand);
|
|
12784
12892
|
program2.addCommand(gitCommand);
|
|
12785
12893
|
program2.addCommand(checkpointCommand);
|
|
12894
|
+
program2.addCommand(cpCommand);
|
|
12895
|
+
program2.addCommand(downloadCommand);
|
|
12896
|
+
program2.addCommand(notifyCommand);
|
|
12897
|
+
program2.addCommand(openCommand);
|
|
12786
12898
|
program2.parseAsync(process.argv).catch((err) => {
|
|
12787
12899
|
const msg = err instanceof Error ? err.message : String(err);
|
|
12788
12900
|
process.stderr.write(`agentbox-ctl: ${msg}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-`docker commit` cleanup: strip ephemeral / disposable state so the
|
|
3
|
+
# captured checkpoint image is closer to "warm project state, nothing else".
|
|
4
|
+
#
|
|
5
|
+
# Invoked by the host via `docker exec --user root <container>
|
|
6
|
+
# /usr/local/bin/agentbox-checkpoint-cleanup` right before
|
|
7
|
+
# `docker commit`. Best-effort: every step is allowed to fail (a checkpoint
|
|
8
|
+
# capture should never block on cleanup hiccups).
|
|
9
|
+
#
|
|
10
|
+
# What we DELIBERATELY keep:
|
|
11
|
+
# - /workspace the actual point of the checkpoint
|
|
12
|
+
# - /home/vscode/.npm warm npm cache (next install is fast)
|
|
13
|
+
# - /home/vscode/.cache pnpm/yarn/Cargo/etc. caches
|
|
14
|
+
# - /var/lib/docker in-box dockerd's data root
|
|
15
|
+
# - /home/vscode/.claude the named volume is bind-mounted; image
|
|
16
|
+
# layer never sees it anyway
|
|
17
|
+
set +e
|
|
18
|
+
|
|
19
|
+
# apt: drop downloaded .deb cache and the package index. The index is ~50MB
|
|
20
|
+
# and gets refreshed on the next `apt-get update`; the .deb cache is reusable
|
|
21
|
+
# only if we don't change versions, which we usually do.
|
|
22
|
+
apt-get clean 2>/dev/null
|
|
23
|
+
rm -rf /var/lib/apt/lists/* 2>/dev/null
|
|
24
|
+
|
|
25
|
+
# Throwaway scratch dirs. Preserve /tmp/claude-* — that is the live in-box
|
|
26
|
+
# Claude Code session's working tree (its per-task stdout/stderr files). The
|
|
27
|
+
# agent that triggered this checkpoint *is* that session; deleting its task
|
|
28
|
+
# output mid-run makes its harness see ENOENT, treat the command as failed,
|
|
29
|
+
# and retry the checkpoint (observed: 5 duplicate auto-named checkpoints).
|
|
30
|
+
# Stale claude-* dirs baked into the image are tiny and Claude Code prunes
|
|
31
|
+
# them itself on the next session start.
|
|
32
|
+
find /tmp /var/tmp -mindepth 1 -maxdepth 1 ! -name 'claude-*' -exec rm -rf {} + 2>/dev/null
|
|
33
|
+
|
|
34
|
+
# Logs: truncate (don't delete) so the original file modes / ownerships stay
|
|
35
|
+
# intact for the next run. Targets common rotated archives too.
|
|
36
|
+
find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
|
|
37
|
+
-exec truncate -s0 {} + 2>/dev/null
|
|
38
|
+
find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
|
|
39
|
+
|
|
40
|
+
# Bash history (root + vscode). Re-assert vscode ownership: `: >` run as root
|
|
41
|
+
# (re)creates the file root-owned 0644 when it didn't exist, which the uid-1000
|
|
42
|
+
# vscode user cannot append to, silently dropping all shell history.
|
|
43
|
+
: > /root/.bash_history 2>/dev/null
|
|
44
|
+
: > /home/vscode/.bash_history 2>/dev/null
|
|
45
|
+
chown vscode:vscode /home/vscode/.bash_history 2>/dev/null
|
|
46
|
+
chmod 600 /home/vscode/.bash_history 2>/dev/null
|
|
47
|
+
|
|
48
|
+
# Anthropic's installer writes a transient marker; redundant once the binary
|
|
49
|
+
# is in place. Safe to wipe.
|
|
50
|
+
rm -rf /home/vscode/.claude-installer 2>/dev/null
|
|
51
|
+
|
|
52
|
+
exit 0
|