@madarco/agentbox 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{chunk-3NCUES35.js → chunk-6VTAPD4H.js} +123 -112
  2. package/dist/chunk-6VTAPD4H.js.map +1 -0
  3. package/dist/{chunk-J35IH7W5.js → chunk-7J5AJLWG.js} +61 -23
  4. package/dist/chunk-7J5AJLWG.js.map +1 -0
  5. package/dist/{chunk-3JKQNOXP.js → chunk-FJNIFTWK.js} +66 -65
  6. package/dist/chunk-FJNIFTWK.js.map +1 -0
  7. package/dist/{chunk-IDR4HVIC.js → chunk-HPZMD5DE.js} +2 -2
  8. package/dist/chunk-HPZMD5DE.js.map +1 -0
  9. package/dist/{chunk-MOC54XL6.js → chunk-PXUBE5KS.js} +376 -245
  10. package/dist/chunk-PXUBE5KS.js.map +1 -0
  11. package/dist/{chunk-SOMIKEN2.js → chunk-RFC5F5HR.js} +272 -214
  12. package/dist/chunk-RFC5F5HR.js.map +1 -0
  13. package/dist/create-AHZ3GVEZ-TGEDL7UX.js +15 -0
  14. package/dist/index.js +2760 -1857
  15. package/dist/index.js.map +1 -1
  16. package/dist/{lifecycle-YTMZYKOE-TD5S5FTS.js → lifecycle-LFOL6YFM-TCHDX3J5.js} +5 -5
  17. package/dist/{state-ZSP3ORXW-WI6KOIG3.js → state-KD7M46ZP-KHFTHFUS.js} +2 -2
  18. package/dist/stats-Z4BVJODD-HEC4TMUZ.js +19 -0
  19. package/package.json +3 -2
  20. package/runtime/docker/Dockerfile.box +53 -20
  21. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +39 -50
  22. package/runtime/docker/packages/ctl/dist/bin.cjs +219 -148
  23. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +42 -0
  24. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +26 -15
  25. package/runtime/relay/bin.cjs +288 -12
  26. package/share/agentbox-setup/SKILL.md +39 -50
  27. package/dist/chunk-3JKQNOXP.js.map +0 -1
  28. package/dist/chunk-3NCUES35.js.map +0 -1
  29. package/dist/chunk-IDR4HVIC.js.map +0 -1
  30. package/dist/chunk-J35IH7W5.js.map +0 -1
  31. package/dist/chunk-MOC54XL6.js.map +0 -1
  32. package/dist/chunk-SOMIKEN2.js.map +0 -1
  33. package/dist/create-SE6H4B5U-IWAZHJHV.js +0 -15
  34. package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +0 -19
  35. /package/dist/{create-SE6H4B5U-IWAZHJHV.js.map → create-AHZ3GVEZ-TGEDL7UX.js.map} +0 -0
  36. /package/dist/{lifecycle-YTMZYKOE-TD5S5FTS.js.map → lifecycle-LFOL6YFM-TCHDX3J5.js.map} +0 -0
  37. /package/dist/{state-ZSP3ORXW-WI6KOIG3.js.map → state-KD7M46ZP-KHFTHFUS.js.map} +0 -0
  38. /package/dist/{stats-GZFLPYTU-DBJ2DVBJ.js.map → stats-Z4BVJODD-HEC4TMUZ.js.map} +0 -0
@@ -10545,6 +10545,108 @@ var claudeStateCommand = new Command("claude-state").description("Report Claude
10545
10545
  process.exit(0);
10546
10546
  });
10547
10547
 
10548
+ // src/relay-rpc.ts
10549
+ var import_node_http = require("http");
10550
+ var import_node_https = require("https");
10551
+ function postRpc(method, params, opts = {}) {
10552
+ const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
10553
+ const urlStr = process.env.AGENTBOX_RELAY_URL;
10554
+ const token = process.env.AGENTBOX_RELAY_TOKEN;
10555
+ if (!urlStr || !token) {
10556
+ process.stderr.write(
10557
+ `${prefix}: AGENTBOX_RELAY_URL / AGENTBOX_RELAY_TOKEN not set; no relay configured for this box.
10558
+ `
10559
+ );
10560
+ return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
10561
+ }
10562
+ let url;
10563
+ try {
10564
+ url = new URL(urlStr);
10565
+ } catch {
10566
+ process.stderr.write(`${prefix}: invalid AGENTBOX_RELAY_URL: ${urlStr}
10567
+ `);
10568
+ return Promise.resolve({ status: 0, parsed: null, raw: "", internalExitCode: 65 });
10569
+ }
10570
+ const body = JSON.stringify({ method, params });
10571
+ const isHttps = url.protocol === "https:";
10572
+ const transport = isHttps ? import_node_https.request : import_node_http.request;
10573
+ const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
10574
+ return new Promise((resolve) => {
10575
+ const req = transport(
10576
+ {
10577
+ host: url.hostname,
10578
+ port,
10579
+ method: "POST",
10580
+ path: `${url.pathname.replace(/\/$/, "")}/rpc`,
10581
+ headers: {
10582
+ "Content-Type": "application/json",
10583
+ "Content-Length": Buffer.byteLength(body).toString(),
10584
+ Authorization: `Bearer ${token}`
10585
+ }
10586
+ },
10587
+ (res) => {
10588
+ const chunks = [];
10589
+ res.on("data", (c) => chunks.push(c));
10590
+ res.on("end", () => {
10591
+ const status2 = res.statusCode ?? 0;
10592
+ const text = Buffer.concat(chunks).toString("utf8");
10593
+ let parsed = null;
10594
+ try {
10595
+ const v = JSON.parse(text);
10596
+ if (v && typeof v === "object" && typeof v.exitCode === "number") {
10597
+ parsed = v;
10598
+ }
10599
+ } catch {
10600
+ parsed = null;
10601
+ }
10602
+ resolve({ status: status2, parsed, raw: text, internalExitCode: null });
10603
+ });
10604
+ }
10605
+ );
10606
+ req.on("error", (err) => {
10607
+ process.stderr.write(`${prefix}: ${String(err.message ?? err)}
10608
+ `);
10609
+ resolve({ status: 0, parsed: null, raw: "", internalExitCode: 126 });
10610
+ });
10611
+ req.write(body);
10612
+ req.end();
10613
+ });
10614
+ }
10615
+ async function postRpcAndExit(method, params, opts = {}) {
10616
+ const prefix = opts.errorPrefix ?? "agentbox-ctl rpc";
10617
+ const out = await postRpc(method, params, opts);
10618
+ if (out.internalExitCode !== null) return out.internalExitCode;
10619
+ if (out.parsed) {
10620
+ if (out.parsed.stdout) process.stdout.write(out.parsed.stdout);
10621
+ if (out.parsed.stderr) process.stderr.write(out.parsed.stderr);
10622
+ return out.parsed.exitCode;
10623
+ }
10624
+ process.stderr.write(`${prefix}: relay returned ${String(out.status)}: ${out.raw}
10625
+ `);
10626
+ return out.status >= 200 && out.status < 300 ? 0 : 1;
10627
+ }
10628
+
10629
+ // src/commands/cp.ts
10630
+ 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(
10631
+ 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) => {
10632
+ const params = { boxPath, hostPath };
10633
+ if (opts.recursive === false) params.recursive = false;
10634
+ const code = await postRpcAndExit("cp.toHost", params, {
10635
+ errorPrefix: "agentbox-ctl cp"
10636
+ });
10637
+ process.exit(code);
10638
+ })
10639
+ ).addCommand(
10640
+ 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) => {
10641
+ const params = { boxPath, hostPath };
10642
+ if (opts.recursive === false) params.recursive = false;
10643
+ const code = await postRpcAndExit("cp.fromHost", params, {
10644
+ errorPrefix: "agentbox-ctl cp"
10645
+ });
10646
+ process.exit(code);
10647
+ })
10648
+ );
10649
+
10548
10650
  // src/config.ts
10549
10651
  var import_promises = require("fs/promises");
10550
10652
  var import_yaml = __toESM(require_dist(), 1);
@@ -11088,8 +11190,8 @@ function startProbe(probe, ctx) {
11088
11190
  }
11089
11191
 
11090
11192
  // src/relay-client.ts
11091
- var import_node_http = require("http");
11092
- var import_node_https = require("https");
11193
+ var import_node_http2 = require("http");
11194
+ var import_node_https2 = require("https");
11093
11195
  var RelayClient = class {
11094
11196
  url;
11095
11197
  token;
@@ -11115,7 +11217,7 @@ var RelayClient = class {
11115
11217
  const url = this.url;
11116
11218
  const body = JSON.stringify({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), payload });
11117
11219
  const isHttps = url.protocol === "https:";
11118
- const transport = isHttps ? import_node_https.request : import_node_http.request;
11220
+ const transport = isHttps ? import_node_https2.request : import_node_http2.request;
11119
11221
  const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
11120
11222
  const req = transport(
11121
11223
  {
@@ -11237,6 +11339,20 @@ function spawnArgs(cmd) {
11237
11339
  if (typeof cmd === "string") return { bin: "bash", args: ["-c", cmd] };
11238
11340
  return { bin: cmd[0], args: cmd.slice(1) };
11239
11341
  }
11342
+ var cachedLoginPath;
11343
+ function loginShellPath() {
11344
+ if (cachedLoginPath !== void 0) return cachedLoginPath;
11345
+ try {
11346
+ const out = (0, import_node_child_process.execFileSync)("bash", ["-lc", 'printf %s "$PATH"'], {
11347
+ encoding: "utf8",
11348
+ timeout: 5e3
11349
+ }).trim();
11350
+ cachedLoginPath = out || (process.env.PATH ?? "");
11351
+ } catch {
11352
+ cachedLoginPath = process.env.PATH ?? "";
11353
+ }
11354
+ return cachedLoginPath;
11355
+ }
11240
11356
  var ServiceRunner = class extends import_node_events.EventEmitter {
11241
11357
  constructor(spec, opts) {
11242
11358
  super();
@@ -11351,7 +11467,7 @@ var ServiceRunner = class extends import_node_events.EventEmitter {
11351
11467
  try {
11352
11468
  child = this.spawnFn(bin, args, {
11353
11469
  cwd,
11354
- env: { ...process.env, ...spec.env ?? {} },
11470
+ env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
11355
11471
  stdio: ["ignore", "pipe", "pipe"]
11356
11472
  });
11357
11473
  } catch (err) {
@@ -11549,7 +11665,7 @@ var TaskRunner = class extends import_node_events.EventEmitter {
11549
11665
  try {
11550
11666
  child = this.spawnFn(bin, args, {
11551
11667
  cwd,
11552
- env: { ...process.env, ...spec.env ?? {} },
11668
+ env: { ...process.env, PATH: loginShellPath(), ...spec.env ?? {} },
11553
11669
  stdio: ["ignore", "pipe", "pipe"]
11554
11670
  });
11555
11671
  } catch (err) {
@@ -11853,6 +11969,14 @@ var Supervisor = class extends import_node_events.EventEmitter {
11853
11969
  changed.push(spec.name);
11854
11970
  }
11855
11971
  }
11972
+ for (const [name, unit] of this.units) {
11973
+ if (unit.kind !== "task") continue;
11974
+ const task = unit;
11975
+ if (task.getState() === "skipped") {
11976
+ this.failed.delete(name);
11977
+ task.resetForRerun();
11978
+ }
11979
+ }
11856
11980
  this.applyWebProxy();
11857
11981
  this.schedule();
11858
11982
  return { added, removed, changed };
@@ -12427,172 +12551,116 @@ var daemonCommand = new Command("daemon").description("Run the agentbox-ctl supe
12427
12551
  process.on("SIGINT", () => void shutdown("SIGINT"));
12428
12552
  });
12429
12553
 
12430
- // src/commands/checkpoint.ts
12431
- var import_node_http2 = require("http");
12432
- var import_node_https2 = require("https");
12433
- async function rpc(params) {
12434
- const urlStr = process.env.AGENTBOX_RELAY_URL;
12435
- const token = process.env.AGENTBOX_RELAY_TOKEN;
12436
- if (!urlStr || !token) {
12554
+ // src/commands/download.ts
12555
+ var KINDS = ["workspace", "env", "config", "claude"];
12556
+ function isKind(v) {
12557
+ return KINDS.includes(v);
12558
+ }
12559
+ var downloadCommand = new Command("download").description(
12560
+ "Download box contents to the host (gated by user prompt). Kinds: workspace (default), env, config, claude"
12561
+ ).argument("[kind]", `one of: ${KINDS.join(", ")}`, "workspace").action(async (kindArg) => {
12562
+ if (!isKind(kindArg)) {
12437
12563
  process.stderr.write(
12438
- "agentbox-ctl checkpoint: AGENTBOX_RELAY_URL / AGENTBOX_RELAY_TOKEN not set; no relay configured for this box.\n"
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}
12564
+ `agentbox-ctl download: unknown kind "${kindArg}"; expected one of: ${KINDS.join(", ")}
12487
12565
  `
12488
- );
12489
- resolve(status2 >= 200 && status2 < 300 ? 0 : 1);
12490
- });
12491
- }
12492
12566
  );
12493
- req.on("error", (err) => {
12494
- process.stderr.write(`agentbox-ctl checkpoint: ${String(err.message ?? err)}
12495
- `);
12496
- resolve(126);
12497
- });
12498
- req.write(body);
12499
- req.end();
12567
+ process.exit(64);
12568
+ }
12569
+ const params = { kind: kindArg };
12570
+ const code = await postRpcAndExit(`download.${kindArg}`, params, {
12571
+ errorPrefix: "agentbox-ctl download"
12500
12572
  });
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
12573
  process.exit(code);
12509
12574
  });
12510
12575
 
12511
- // src/commands/git.ts
12512
- var import_node_http3 = require("http");
12513
- var import_node_https3 = require("https");
12514
- async function rpc2(method, opts, extra) {
12515
- const urlStr = process.env.AGENTBOX_RELAY_URL;
12516
- const token = process.env.AGENTBOX_RELAY_TOKEN;
12517
- if (!urlStr || !token) {
12518
- process.stderr.write(
12519
- "agentbox-ctl git: AGENTBOX_RELAY_URL / AGENTBOX_RELAY_TOKEN not set; no relay configured for this box.\n"
12520
- );
12521
- return 65;
12522
- }
12523
- let url;
12524
- try {
12525
- url = new URL(urlStr);
12526
- } catch {
12527
- process.stderr.write(`agentbox-ctl git: invalid AGENTBOX_RELAY_URL: ${urlStr}
12528
- `);
12529
- return 65;
12576
+ // src/commands/checkpoint.ts
12577
+ 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(
12578
+ "--replace",
12579
+ "if a checkpoint with the same name exists, rm it first (idempotent recapture; safe to retry when the previous run's stdout was lost)"
12580
+ ).action(
12581
+ async (opts) => {
12582
+ const params = {};
12583
+ if (opts.name) params.name = opts.name;
12584
+ if (opts.merged === true) params.merged = true;
12585
+ if (opts.setDefault === true) params.setDefault = true;
12586
+ if (opts.replace === true) params.replace = true;
12587
+ const code = await postRpcAndExit("checkpoint.create", params, {
12588
+ errorPrefix: "agentbox-ctl checkpoint"
12589
+ });
12590
+ process.exit(code);
12530
12591
  }
12531
- const params = {
12532
- path: opts.cwd ?? process.cwd()
12533
- };
12592
+ );
12593
+
12594
+ // src/commands/git.ts
12595
+ var import_node_child_process4 = require("child_process");
12596
+ function buildParams(opts, extra) {
12597
+ const params = { path: opts.cwd ?? process.cwd() };
12534
12598
  if (opts.remote) params.remote = opts.remote;
12535
12599
  if (extra.length > 0) params.args = extra;
12536
- const body = JSON.stringify({ method, params });
12537
- const isHttps = url.protocol === "https:";
12538
- const transport = isHttps ? import_node_https3.request : import_node_http3.request;
12539
- const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;
12600
+ return params;
12601
+ }
12602
+ function runLocalGit(args, cwd) {
12540
12603
  return new Promise((resolve) => {
12541
- const req = transport(
12542
- {
12543
- host: url.hostname,
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) => {
12604
+ const child = (0, import_node_child_process4.spawn)("git", args, { cwd, stdio: "inherit" });
12605
+ child.on("close", (code) => resolve(code ?? 1));
12606
+ child.on("error", (err) => {
12578
12607
  process.stderr.write(`agentbox-ctl git: ${String(err.message ?? err)}
12579
12608
  `);
12580
12609
  resolve(126);
12581
12610
  });
12582
- req.write(body);
12583
- req.end();
12584
12611
  });
12585
12612
  }
12586
12613
  var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
12587
- new Command("pull").description("Run `git pull` on the host worktree for this box").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "path inside the container identifying which worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git pull").action(async (args, opts) => {
12588
- const code = await rpc2("git.pull", opts, args);
12614
+ 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) => {
12615
+ const code = await postRpcAndExit("git.push", buildParams(opts, args), {
12616
+ errorPrefix: "agentbox-ctl git"
12617
+ });
12589
12618
  process.exit(code);
12590
12619
  })
12591
12620
  ).addCommand(
12592
- new Command("push").description("Run `git push` on the host worktree for this box").option("--remote <name>", "remote name (default: origin)").option("--cwd <path>", "path inside the container identifying which worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("[args...]", "additional args forwarded to git push").action(async (args, opts) => {
12593
- const code = await rpc2("git.push", opts, args);
12621
+ 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) => {
12622
+ const code = await postRpcAndExit("git.fetch", buildParams(opts, args), {
12623
+ errorPrefix: "agentbox-ctl git"
12624
+ });
12594
12625
  process.exit(code);
12595
12626
  })
12627
+ ).addCommand(
12628
+ new Command("pull").description(
12629
+ "Fetch via the relay (host creds), then merge into the in-container working tree locally"
12630
+ ).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(
12631
+ async (args, opts) => {
12632
+ const fetchCode = await postRpcAndExit("git.fetch", buildParams(opts, args), {
12633
+ errorPrefix: "agentbox-ctl git"
12634
+ });
12635
+ if (fetchCode !== 0) process.exit(fetchCode);
12636
+ const remote = opts.remote ?? "origin";
12637
+ const cwd = opts.cwd ?? process.cwd();
12638
+ const mergeArgs = ["merge"];
12639
+ if (opts.ffOnly) mergeArgs.push("--ff-only");
12640
+ mergeArgs.push(`${remote}/HEAD`);
12641
+ const mergeCode = await runLocalGit(mergeArgs, cwd);
12642
+ process.exit(mergeCode);
12643
+ }
12644
+ )
12645
+ );
12646
+
12647
+ // src/commands/notify.ts
12648
+ async function reportState(opts, state) {
12649
+ try {
12650
+ await claudeState({ socketPath: opts.socket, timeoutMs: 1500 }, state);
12651
+ } catch {
12652
+ }
12653
+ }
12654
+ var notifyCommand = new Command("notify").description(
12655
+ "Signal that the in-box agent is waiting for user input (highlights the box in the dashboard)"
12656
+ ).option("--socket <path>", "unix socket path", DEFAULT_SOCKET_PATH).option("--message <text>", "reserved for future use; accepted but ignored in v1").action(async (opts) => {
12657
+ await reportState(opts, "waiting");
12658
+ process.exit(0);
12659
+ }).addCommand(
12660
+ 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) => {
12661
+ await reportState(opts, "idle");
12662
+ process.exit(0);
12663
+ })
12596
12664
  );
12597
12665
 
12598
12666
  // src/render.ts
@@ -12783,6 +12851,9 @@ program2.addCommand(waitReadyCommand);
12783
12851
  program2.addCommand(runTaskCommand);
12784
12852
  program2.addCommand(gitCommand);
12785
12853
  program2.addCommand(checkpointCommand);
12854
+ program2.addCommand(cpCommand);
12855
+ program2.addCommand(downloadCommand);
12856
+ program2.addCommand(notifyCommand);
12786
12857
  program2.parseAsync(process.argv).catch((err) => {
12787
12858
  const msg = err instanceof Error ? err.message : String(err);
12788
12859
  process.stderr.write(`agentbox-ctl: ${msg}
@@ -0,0 +1,42 @@
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.
26
+ rm -rf /tmp/* /tmp/.[!.]* /var/tmp/* /var/tmp/.[!.]* 2>/dev/null
27
+
28
+ # Logs: truncate (don't delete) so the original file modes / ownerships stay
29
+ # intact for the next run. Targets common rotated archives too.
30
+ find /var/log -type f \( -name '*.log' -o -name '*.gz' -o -name '*.1' \) \
31
+ -exec truncate -s0 {} + 2>/dev/null
32
+ find /var/log/agentbox -type f -exec truncate -s0 {} + 2>/dev/null
33
+
34
+ # Bash history (root + vscode).
35
+ : > /root/.bash_history 2>/dev/null
36
+ : > /home/vscode/.bash_history 2>/dev/null
37
+
38
+ # Anthropic's installer writes a transient marker; redundant once the binary
39
+ # is in place. Safe to wipe.
40
+ rm -rf /home/vscode/.claude-installer 2>/dev/null
41
+
42
+ exit 0
@@ -1,21 +1,32 @@
1
1
  # AgentBox sandbox
2
2
 
3
3
  You are running inside an AgentBox sandbox: a Linux Docker container with
4
- docker-in-docker (run `docker` directly, no sudo). The host filesystem is
5
- mounted read-only at /host-src; /workspace is a FUSE overlay where your
6
- writes go to a per-box volume.
4
+ docker-in-docker (run `docker` directly, no sudo).
5
+ Your user is `vscode` and you can use passwordless sudo to run commands as root.
6
+ `/workspace` is your own per-box git worktree on branch `agentbox/<box-name>`:
7
+ writes there stay in the container's writable layer and don't touch the host's working
8
+ tree.
7
9
 
8
- This container has no SSH credentials and no host gitconfig identity.
9
- For git operations that need the user (push, pull from private remotes),
10
- use `agentbox-ctl git pull|push -- <args>` — it RPCs to the host, which
11
- runs git with the real SSH agent and gitconfig. Local commits work as
12
- normal because the main `.git/` is bind-mounted at the same absolute
13
- path as on the host.
10
+ You can save the current filesystem state to be reused by future boxes by
11
+ running `agentbox-ctl checkpoint --set-default`.
14
12
 
15
- Your `~/.claude` and `/workspace` env files live only in this box. If
16
- you install a skill/plugin (or otherwise change `~/.claude`), tell the
17
- user to run `agentbox pull claude` on the host to copy it back. If you
18
- create or change `.env`/`.envrc`/secrets files, tell them to run
19
- `agentbox pull env`. Both are additive and never overwrite host files.
13
+ The main `.git/` is bind-mounted at the same absolute path as on
14
+ the host, so local commits show up in the host's `git log` immediately.
15
+ No SSH creds, no host gitconfig identity. For ops that need the user
16
+ (push, fetch from private remotes), use `agentbox-ctl git push|fetch|pull
17
+ -- <args>` it RPCs to the host, which runs git with the real SSH agent.
20
18
 
21
- Box identity is in /etc/agentbox/box.env and the AGENTBOX_* env vars.
19
+ For ad-hoc file transfers between this box and the host, use
20
+ `agentbox-ctl cp toHost <boxPath> <hostPath>` and
21
+ `agentbox-ctl cp fromHost <hostPath> <boxPath>`. They RPC to the host and
22
+ ask the user for confirmation on the wrapper that runs `agentbox claude`;
23
+ deny returns exit 10 (`denied by user`).
24
+ Don't put any timeout on the command, it will run forever and the user will be notified through multiple channels.
25
+
26
+ If you install a skill/plugin, change `~/.claude`, or write
27
+ `.env`/`.envrc`/secrets/`agentbox.yaml`, you can pull those onto the host
28
+ yourself with `agentbox-ctl download claude` / `download env` /
29
+ `download config` (also user-confirmed; additive; never overwrites host
30
+ files, don't put any timeout on the command).
31
+
32
+ Box identity: /etc/agentbox/box.env and the AGENTBOX_* env vars.