@madarco/agentbox 0.11.3 → 0.12.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 (35) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/{_cloud-attach-XWCVLO5V.js → _cloud-attach-XKO4SHR3.js} +3 -3
  3. package/dist/{chunk-ZGVMN54V.js → chunk-2LF5YILI.js} +21 -3
  4. package/dist/chunk-2LF5YILI.js.map +1 -0
  5. package/dist/{chunk-MXXXKJYS.js → chunk-DHJ7OMIP.js} +234 -83
  6. package/dist/chunk-DHJ7OMIP.js.map +1 -0
  7. package/dist/{chunk-GYJ62GFL.js → chunk-HFV6THYG.js} +6 -6
  8. package/dist/{chunk-ZJXTIH6C.js → chunk-IZXPJPPV.js} +1347 -852
  9. package/dist/chunk-IZXPJPPV.js.map +1 -0
  10. package/dist/{dist-RAZP76VX.js → dist-24PY2ZMO.js} +3 -3
  11. package/dist/{dist-PTJ6CEQY.js → dist-47LVLYUV.js} +4 -4
  12. package/dist/{dist-ASLPRUQR.js → dist-RZZSSUNB.js} +28 -2
  13. package/dist/{dist-WMQDMTWS.js → dist-SWUOU34W.js} +8 -5
  14. package/dist/dist-SWUOU34W.js.map +1 -0
  15. package/dist/index.js +1324 -736
  16. package/dist/index.js.map +1 -1
  17. package/package.json +5 -5
  18. package/runtime/docker/packages/ctl/dist/bin.cjs +335 -4
  19. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +86 -5
  20. package/runtime/hetzner/ctl.cjs +335 -4
  21. package/runtime/hetzner/gh-shim +86 -5
  22. package/runtime/relay/bin.cjs +285 -2
  23. package/runtime/vercel/ctl.cjs +335 -4
  24. package/runtime/vercel/gh-shim +86 -5
  25. package/share/host-skills/agentbox/SKILL.md +16 -5
  26. package/share/host-skills/agentbox-info/SKILL.md +3 -1
  27. package/dist/chunk-MXXXKJYS.js.map +0 -1
  28. package/dist/chunk-ZGVMN54V.js.map +0 -1
  29. package/dist/chunk-ZJXTIH6C.js.map +0 -1
  30. package/dist/dist-WMQDMTWS.js.map +0 -1
  31. /package/dist/{_cloud-attach-XWCVLO5V.js.map → _cloud-attach-XKO4SHR3.js.map} +0 -0
  32. /package/dist/{chunk-GYJ62GFL.js.map → chunk-HFV6THYG.js.map} +0 -0
  33. /package/dist/{dist-RAZP76VX.js.map → dist-24PY2ZMO.js.map} +0 -0
  34. /package/dist/{dist-PTJ6CEQY.js.map → dist-47LVLYUV.js.map} +0 -0
  35. /package/dist/{dist-ASLPRUQR.js.map → dist-RZZSSUNB.js.map} +0 -0
@@ -18509,6 +18509,35 @@ var KEY_REGISTRY = [
18509
18509
  description: "Per-provider override of `box.defaultCheckpoint` for vercel. Wins over the global when set; set via `agentbox checkpoint set-default --provider vercel`.",
18510
18510
  advanced: true
18511
18511
  },
18512
+ {
18513
+ key: "box.size",
18514
+ type: "string",
18515
+ description: "Default VM size for cloud providers. Provider-interpreted: hetzner = server type (e.g. `cx33`); daytona = `cpu-memory-disk` GB (e.g. `4-8-20`). Used as fallback when no per-provider override is set. Docker/Vercel ignore it."
18516
+ },
18517
+ {
18518
+ key: "box.sizeDocker",
18519
+ type: "string",
18520
+ description: "Per-provider override of `box.size` for docker. Reserved \u2014 docker sizing is controlled via `box.memory` / `box.cpus` / `box.disk`.",
18521
+ advanced: true
18522
+ },
18523
+ {
18524
+ key: "box.sizeDaytona",
18525
+ type: "string",
18526
+ description: "Per-provider override of `box.size` for daytona. `cpu-memory-disk` GB spec (e.g. `4-8-20`). Only honored on the image/Dockerfile create path; Daytona rejects custom resources on snapshot-resume.",
18527
+ advanced: true
18528
+ },
18529
+ {
18530
+ key: "box.sizeHetzner",
18531
+ type: "string",
18532
+ description: "Per-provider override of `box.size` for hetzner. Server type string (e.g. `cx23`, `cx33`, `cx43`).",
18533
+ advanced: true
18534
+ },
18535
+ {
18536
+ key: "box.sizeVercel",
18537
+ type: "string",
18538
+ description: "Per-provider override of `box.size` for vercel. Reserved \u2014 vercel sizing is controlled via `box.vercelVcpus`.",
18539
+ advanced: true
18540
+ },
18512
18541
  {
18513
18542
  key: "checkpoint.maxLayers",
18514
18543
  type: "int",
@@ -18525,6 +18554,11 @@ var KEY_REGISTRY = [
18525
18554
  type: "bool",
18526
18555
  description: "Copy host env/config files (.env*, secrets.toml, agentbox.yaml, ...) into /workspace at box create time (gitignore-bypassing)."
18527
18556
  },
18557
+ {
18558
+ key: "box.resyncOnStart",
18559
+ type: "bool",
18560
+ description: "Merge the host's current branch into the box and overlay the host's uncommitted/untracked changes when starting an agent session (keeps the box's version on conflict and warns the agent)."
18561
+ },
18528
18562
  {
18529
18563
  key: "box.vnc",
18530
18564
  type: "bool",
@@ -18548,7 +18582,31 @@ var KEY_REGISTRY = [
18548
18582
  {
18549
18583
  key: "box.image",
18550
18584
  type: "string",
18551
- description: "Box image ref (advanced).",
18585
+ description: "Generic box image ref (fallback). Used as fallback when no per-provider override is set; the default `agentbox/box:dev` is treated as a sentinel by cloud backends (boot from their prepared base snapshot instead).",
18586
+ advanced: true
18587
+ },
18588
+ {
18589
+ key: "box.imageDocker",
18590
+ type: "string",
18591
+ description: "Per-provider override of `box.image` for docker (local docker image ref, e.g. `agentbox/box:dev`). Wins over the generic when set.",
18592
+ advanced: true
18593
+ },
18594
+ {
18595
+ key: "box.imageDaytona",
18596
+ type: "string",
18597
+ description: "Per-provider override of `box.image` for daytona (named snapshot, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider daytona`.",
18598
+ advanced: true
18599
+ },
18600
+ {
18601
+ key: "box.imageHetzner",
18602
+ type: "string",
18603
+ description: "Per-provider override of `box.image` for hetzner (image description, e.g. `agentbox-base-<fingerprint>`). Written by `agentbox prepare --provider hetzner`.",
18604
+ advanced: true
18605
+ },
18606
+ {
18607
+ key: "box.imageVercel",
18608
+ type: "string",
18609
+ description: "Per-provider override of `box.image` for vercel (snapshot id, e.g. `snap_\u2026`). Written by `agentbox prepare --provider vercel`.",
18552
18610
  advanced: true
18553
18611
  },
18554
18612
  {
@@ -19163,6 +19221,8 @@ var GH_PR_OPS = [
19163
19221
  "create",
19164
19222
  "view",
19165
19223
  "list",
19224
+ "diff",
19225
+ "checks",
19166
19226
  "comment",
19167
19227
  "review",
19168
19228
  "merge",
@@ -19173,7 +19233,84 @@ var GH_PR_OPS = [
19173
19233
  function isGhPrOp(value) {
19174
19234
  return GH_PR_OPS.includes(value);
19175
19235
  }
19176
- var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set(["view", "list"]);
19236
+ var GH_PR_READ_ONLY_OPS = /* @__PURE__ */ new Set([
19237
+ "view",
19238
+ "list",
19239
+ "diff",
19240
+ "checks"
19241
+ ]);
19242
+ var GH_RUN_OPS = ["list", "view", "rerun"];
19243
+ function isGhRunOp(value) {
19244
+ return GH_RUN_OPS.includes(value);
19245
+ }
19246
+ var GH_RUN_READ_ONLY_OPS = /* @__PURE__ */ new Set(["list", "view"]);
19247
+ var PR_REVIEW_COMMENT = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments(\?.*)?$/;
19248
+ var PR_REVIEW_COMMENT_REPLY = /^repos\/[^/]+\/[^/]+\/pulls\/\d+\/comments\/\d+\/replies(\?.*)?$/;
19249
+ var GH_API_WRITE_ALLOWED_ENDPOINTS = [
19250
+ PR_REVIEW_COMMENT,
19251
+ PR_REVIEW_COMMENT_REPLY
19252
+ ];
19253
+ var GH_API_ALLOWED_ENDPOINTS = [...GH_API_WRITE_ALLOWED_ENDPOINTS];
19254
+ function isAllowedGhApiEndpoint(endpoint) {
19255
+ return GH_API_ALLOWED_ENDPOINTS.some((re) => re.test(normalizeGhApiEndpoint(endpoint)));
19256
+ }
19257
+ function isWriteAllowedGhApiEndpoint(endpoint) {
19258
+ return GH_API_WRITE_ALLOWED_ENDPOINTS.some((re) => re.test(normalizeGhApiEndpoint(endpoint)));
19259
+ }
19260
+ function normalizeGhApiEndpoint(endpoint) {
19261
+ return endpoint.replace(/^\/+/, "");
19262
+ }
19263
+ var GH_API_ENDPOINT_REFUSAL = {
19264
+ exitCode: 65,
19265
+ stdout: "",
19266
+ stderr: "gh api: endpoint not allowlisted. Proxied: GET on repos/:owner/:repo/pulls/:number/comments (and /:id/replies); POST to those endpoints to add a review comment.\n"
19267
+ };
19268
+ function refuseGhApiCall(endpoint, args) {
19269
+ const refuse = (reason) => ({
19270
+ exitCode: 65,
19271
+ stdout: "",
19272
+ stderr: `gh api: ${reason}
19273
+ `
19274
+ });
19275
+ let explicitMethod = null;
19276
+ let hasFieldFlag = false;
19277
+ for (let i2 = 0; i2 < args.length; i2++) {
19278
+ const arg = args[i2] ?? "";
19279
+ if (arg === "-X" || arg === "--method") {
19280
+ explicitMethod = args[i2 + 1] ?? "";
19281
+ i2++;
19282
+ continue;
19283
+ }
19284
+ if (arg.startsWith("--method=")) {
19285
+ explicitMethod = arg.slice("--method=".length);
19286
+ continue;
19287
+ }
19288
+ if (arg.startsWith("-X") && arg.length > 2) {
19289
+ explicitMethod = arg.slice(2).replace(/^=/, "");
19290
+ continue;
19291
+ }
19292
+ if (arg === "--input" || arg.startsWith("--input=")) {
19293
+ return refuse("'--input' (stdin/file body) isn't supported through the relay; use -f/-F fields");
19294
+ }
19295
+ if (arg === "-f" || arg === "-F" || arg === "--field" || arg === "--raw-field") {
19296
+ hasFieldFlag = true;
19297
+ i2++;
19298
+ continue;
19299
+ }
19300
+ if (arg.startsWith("-f") || arg.startsWith("-F") || arg.startsWith("--field=") || arg.startsWith("--raw-field=")) {
19301
+ hasFieldFlag = true;
19302
+ }
19303
+ }
19304
+ const method = (explicitMethod ?? (hasFieldFlag ? "POST" : "GET")).toUpperCase();
19305
+ if (method === "GET") return null;
19306
+ if (method === "POST") {
19307
+ if (isWriteAllowedGhApiEndpoint(endpoint)) return null;
19308
+ return refuse(
19309
+ `POST is only proxied to PR review-comment endpoints (repos/:o/:r/pulls/:n/comments[/:id/replies]), not '${endpoint}'`
19310
+ );
19311
+ }
19312
+ return refuse(`method '${method}' is not proxied \u2014 only GET, and POST to comment endpoints, are allowed`);
19313
+ }
19177
19314
  function injectPrCreateHead(op, branch, args) {
19178
19315
  if (op !== "create") return args;
19179
19316
  if (!branch || branch === "HEAD") return args;
@@ -19451,6 +19588,12 @@ async function executeCloudAction(action, deps) {
19451
19588
  if (action.method.startsWith("gh.pr.")) {
19452
19589
  return runGhPrRpc(action, deps);
19453
19590
  }
19591
+ if (action.method.startsWith("gh.run.")) {
19592
+ return runGhRunRpc(action, deps);
19593
+ }
19594
+ if (action.method === "gh.api") {
19595
+ return runGhApiRpc(action, deps);
19596
+ }
19454
19597
  if (action.method === "git.clone" || action.method === "gh.repo.clone") {
19455
19598
  return {
19456
19599
  exitCode: 64,
@@ -19559,6 +19702,66 @@ async function runGhPrRpc(action, deps) {
19559
19702
  if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
19560
19703
  return runHostGh(["pr", op, ...finalArgs], lookup.workspacePath);
19561
19704
  }
19705
+ async function cloudWriteConfirm(deps, command, cwd, args) {
19706
+ if (!deps.prompts || !deps.subscribers) return null;
19707
+ const ctx = {
19708
+ kind: "confirm",
19709
+ message: `Allow ${command} from cloud box ${deps.boxName ?? deps.boxId}?`,
19710
+ detail: args.join(" ").slice(0, 200),
19711
+ defaultAnswer: "n",
19712
+ context: { command, cwd, argv: args }
19713
+ };
19714
+ const hasSubscriber = deps.subscribers.forBox(deps.boxId).length > 0;
19715
+ if (!hasSubscriber && process.env["AGENTBOX_PROMPT"] !== "off") {
19716
+ const noSubMode = (process.env["AGENTBOX_GH_NO_SUB"] ?? "deny").toLowerCase();
19717
+ if (noSubMode === "deny") {
19718
+ return {
19719
+ exitCode: 10,
19720
+ stdout: "",
19721
+ stderr: "denied automatically \u2014 no attached wrapper to confirm. Attach `agentbox claude` (or similar) and retry, or set AGENTBOX_GH_NO_SUB=allow.\n"
19722
+ };
19723
+ }
19724
+ if (noSubMode === "allow") {
19725
+ deps.log?.(`${command} auto-approved (no subscribers, AGENTBOX_GH_NO_SUB=allow)`);
19726
+ return null;
19727
+ }
19728
+ const verdict2 = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx, {
19729
+ ttlMs: 5 * 60 * 1e3
19730
+ });
19731
+ return verdict2.answer === "y" ? null : { exitCode: 10, stdout: "", stderr: "denied by user\n" };
19732
+ }
19733
+ const verdict = await askPrompt(deps.prompts, deps.subscribers, deps.boxId, ctx);
19734
+ return verdict.answer === "y" ? null : { exitCode: 10, stdout: "", stderr: "denied by user\n" };
19735
+ }
19736
+ async function runGhRunRpc(action, deps) {
19737
+ const op = action.method.slice("gh.run.".length);
19738
+ if (!isGhRunOp(op)) {
19739
+ return { exitCode: 64, stdout: "", stderr: `unknown gh.run.* op: ${op}
19740
+ ` };
19741
+ }
19742
+ const params = action.params ?? {};
19743
+ const args = Array.isArray(params.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
19744
+ const ghReady = await assertGhReady();
19745
+ if (ghReady) return ghReady;
19746
+ const lookup = await lookupCloudBox(deps.boxId);
19747
+ if (!GH_RUN_READ_ONLY_OPS.has(op)) {
19748
+ const denied = await cloudWriteConfirm(deps, `gh run ${op}`, params.path, args);
19749
+ if (denied) return denied;
19750
+ }
19751
+ return runHostGh(["run", op, ...args], lookup.workspacePath);
19752
+ }
19753
+ async function runGhApiRpc(action, deps) {
19754
+ const params = action.params ?? {};
19755
+ const endpoint = typeof params.endpoint === "string" ? params.endpoint : "";
19756
+ if (!isAllowedGhApiEndpoint(endpoint)) return GH_API_ENDPOINT_REFUSAL;
19757
+ const args = Array.isArray(params.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
19758
+ const callRefusal = refuseGhApiCall(endpoint, args);
19759
+ if (callRefusal) return callRefusal;
19760
+ const ghReady = await assertGhReady();
19761
+ if (ghReady) return ghReady;
19762
+ const lookup = await lookupCloudBox(deps.boxId);
19763
+ return runHostGh(["api", endpoint, ...args], lookup.workspacePath);
19764
+ }
19562
19765
  async function runBrowserOpenMirror(action, deps) {
19563
19766
  const params = action.params ?? {};
19564
19767
  const url = typeof params.url === "string" ? params.url.trim() : "";
@@ -20235,6 +20438,29 @@ function createRelayServer(opts) {
20235
20438
  send(res, status2, result);
20236
20439
  return;
20237
20440
  }
20441
+ if (body.method.startsWith("gh.run.")) {
20442
+ const op = body.method.slice("gh.run.".length);
20443
+ if (!isGhRunOp(op)) {
20444
+ send(res, 400, { error: `unknown gh.run.* op: ${op}` });
20445
+ return;
20446
+ }
20447
+ const result = await handleGhRunRpc(
20448
+ op,
20449
+ reg,
20450
+ body.params,
20451
+ prompts,
20452
+ subscribers
20453
+ );
20454
+ const status2 = result.exitCode === 0 ? 200 : 500;
20455
+ send(res, status2, result);
20456
+ return;
20457
+ }
20458
+ if (body.method === "gh.api") {
20459
+ const result = await handleGhApiRpc(reg, body.params);
20460
+ const status2 = result.exitCode === 0 ? 200 : 500;
20461
+ send(res, status2, result);
20462
+ return;
20463
+ }
20238
20464
  if (body.method === "git.clone" || body.method === "gh.repo.clone") {
20239
20465
  send(res, 501, {
20240
20466
  exitCode: 64,
@@ -20712,6 +20938,53 @@ async function handleGhPrRpc(op, reg, params, prompts, subscribers, hostInitiate
20712
20938
  if (prCreateNeedsHead(op, finalArgs)) return PR_CREATE_NO_HEAD_REFUSAL;
20713
20939
  return runHostGh(["pr", op, ...finalArgs], worktree.hostMainRepo);
20714
20940
  }
20941
+ async function handleGhRunRpc(op, reg, params, prompts, subscribers) {
20942
+ const containerPath = params?.path ?? "/workspace";
20943
+ const worktree = resolveWorktree(reg, containerPath);
20944
+ if (!worktree) {
20945
+ return {
20946
+ exitCode: 64,
20947
+ stdout: "",
20948
+ stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`
20949
+ };
20950
+ }
20951
+ const ghReady = await assertGhReady();
20952
+ if (ghReady) return ghReady;
20953
+ const args = Array.isArray(params?.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
20954
+ if (!GH_RUN_READ_ONLY_OPS.has(op)) {
20955
+ const detail = args.join(" ").slice(0, 200);
20956
+ const verdict = await askPrompt(prompts, subscribers, reg.boxId, {
20957
+ kind: "confirm",
20958
+ message: `Allow gh run ${op} from box ${reg.name}?`,
20959
+ detail,
20960
+ defaultAnswer: "n",
20961
+ context: { command: `gh run ${op}`, cwd: containerPath, argv: args }
20962
+ });
20963
+ if (verdict.answer !== "y") {
20964
+ return { exitCode: 10, stdout: "", stderr: "denied by user\n" };
20965
+ }
20966
+ }
20967
+ return runHostGh(["run", op, ...args], worktree.hostMainRepo);
20968
+ }
20969
+ async function handleGhApiRpc(reg, params) {
20970
+ const containerPath = params?.path ?? "/workspace";
20971
+ const worktree = resolveWorktree(reg, containerPath);
20972
+ if (!worktree) {
20973
+ return {
20974
+ exitCode: 64,
20975
+ stdout: "",
20976
+ stderr: `no worktree registered for box ${reg.boxId} matching ${containerPath}`
20977
+ };
20978
+ }
20979
+ const endpoint = typeof params?.endpoint === "string" ? params.endpoint : "";
20980
+ if (!isAllowedGhApiEndpoint(endpoint)) return GH_API_ENDPOINT_REFUSAL;
20981
+ const args = Array.isArray(params?.args) ? params.args.filter((a2) => typeof a2 === "string") : [];
20982
+ const callRefusal = refuseGhApiCall(endpoint, args);
20983
+ if (callRefusal) return callRefusal;
20984
+ const ghReady = await assertGhReady();
20985
+ if (ghReady) return ghReady;
20986
+ return runHostGh(["api", endpoint, ...args], worktree.hostMainRepo);
20987
+ }
20715
20988
  async function handleCpRpc(reg, method, params) {
20716
20989
  const entry = process.env.AGENTBOX_CLI_ENTRY;
20717
20990
  if (!entry) {
@@ -23083,6 +23356,8 @@ var PR_SUBCOMMANDS = [
23083
23356
  },
23084
23357
  { op: "view", description: "Run `gh pr view` on the host (read-only; no prompt)." },
23085
23358
  { op: "list", description: "Run `gh pr list` on the host (read-only; no prompt)." },
23359
+ { op: "diff", description: "Run `gh pr diff` on the host (read-only; no prompt)." },
23360
+ { op: "checks", description: "Run `gh pr checks` on the host (read-only; no prompt)." },
23086
23361
  { op: "comment", description: "Run `gh pr comment` on the host (prompted; visible to others)." },
23087
23362
  { op: "review", description: "Run `gh pr review` on the host (prompted; visible to others)." },
23088
23363
  {
@@ -23144,7 +23419,42 @@ var repoCommand = new Command("repo").description("GitHub repo operations via th
23144
23419
  }
23145
23420
  )
23146
23421
  );
23147
- var ghCommand = new Command("gh").description("GitHub CLI operations routed through the relay (host `gh` runs with host creds; box never sees a token)").addCommand(buildPrCommand("agentbox-ctl gh pr")).addCommand(repoCommand);
23422
+ var RUN_SUBCOMMANDS = [
23423
+ { op: "list", description: "Run `gh run list` on the host (read-only; no prompt)." },
23424
+ { op: "view", description: "Run `gh run view` on the host (read-only; no prompt)." },
23425
+ {
23426
+ op: "rerun",
23427
+ description: "Run `gh run rerun` on the host (prompted; re-triggers CI)."
23428
+ }
23429
+ ];
23430
+ function buildRunCommand(errorPrefix) {
23431
+ const runCommand = new Command("run").description(
23432
+ "GitHub Actions run operations via the host `gh` CLI (requires `gh` installed and `gh auth login` on the host)"
23433
+ );
23434
+ for (const spec of RUN_SUBCOMMANDS) {
23435
+ runCommand.addCommand(
23436
+ new Command(spec.op).description(spec.description).option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument(
23437
+ "[args...]",
23438
+ "extra flags forwarded to `gh run <op>` verbatim (e.g. `--json`, `--limit`, `<run-id>`)."
23439
+ ).action(async (args, opts) => {
23440
+ const params = { path: opts.cwd ?? process.cwd() };
23441
+ if (args.length > 0) params.args = args;
23442
+ const code = await postRpcAndExit(`gh.run.${spec.op}`, params, { errorPrefix });
23443
+ process.exit(code);
23444
+ })
23445
+ );
23446
+ }
23447
+ return runCommand;
23448
+ }
23449
+ var apiCommand = new Command("api").description(
23450
+ "Allowlisted `gh api` (host runs `gh api`): GET on proxied endpoints, plus POST to add a PR review comment. Other methods are rejected."
23451
+ ).option("--cwd <path>", "container path identifying which registered worktree to use").allowExcessArguments(true).allowUnknownOption(true).argument("<endpoint>", "REST endpoint, e.g. repos/:owner/:repo/pulls/:number/comments").argument("[args...]", "extra flags forwarded to `gh api` verbatim (e.g. `--jq`, `-f body=\u2026`).").action(async (endpoint, args, opts) => {
23452
+ const params = { path: opts.cwd ?? process.cwd(), endpoint };
23453
+ if (args.length > 0) params.args = args;
23454
+ const code = await postRpcAndExit("gh.api", params, { errorPrefix: "agentbox-ctl gh api" });
23455
+ process.exit(code);
23456
+ });
23457
+ var ghCommand = new Command("gh").description("GitHub CLI operations routed through the relay (host `gh` runs with host creds; box never sees a token)").addCommand(buildPrCommand("agentbox-ctl gh pr")).addCommand(buildRunCommand("agentbox-ctl gh run")).addCommand(apiCommand).addCommand(repoCommand);
23148
23458
 
23149
23459
  // src/commands/git.ts
23150
23460
  var import_node_child_process11 = require("child_process");
@@ -23172,6 +23482,18 @@ function runLocalGit(args, cwd) {
23172
23482
  });
23173
23483
  });
23174
23484
  }
23485
+ function hasGitIdentity(cwd) {
23486
+ return new Promise((resolve2) => {
23487
+ const child = (0, import_node_child_process11.spawn)("git", ["config", "user.email"], {
23488
+ cwd,
23489
+ stdio: ["ignore", "pipe", "ignore"]
23490
+ });
23491
+ let out = "";
23492
+ child.stdout.on("data", (c3) => out += c3.toString("utf8"));
23493
+ child.on("close", (code) => resolve2(code === 0 && out.trim().length > 0));
23494
+ child.on("error", () => resolve2(false));
23495
+ });
23496
+ }
23175
23497
  var gitCommand = new Command("git").description("Git operations that need host credentials (routed through the agentbox relay)").addCommand(
23176
23498
  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").addOption(hostInitiatedOption()).allowExcessArguments(true).allowUnknownOption(true).argument(
23177
23499
  "[args...]",
@@ -23206,7 +23528,16 @@ var gitCommand = new Command("git").description("Git operations that need host c
23206
23528
  if (fetchCode !== 0) process.exit(fetchCode);
23207
23529
  const remote = opts.remote ?? "origin";
23208
23530
  const cwd = opts.cwd ?? process.cwd();
23209
- const mergeArgs = ["merge"];
23531
+ const mergeArgs = [];
23532
+ if (!await hasGitIdentity(cwd)) {
23533
+ mergeArgs.push(
23534
+ "-c",
23535
+ "user.name=agentbox",
23536
+ "-c",
23537
+ "user.email=agentbox@users.noreply.github.com"
23538
+ );
23539
+ }
23540
+ mergeArgs.push("merge");
23210
23541
  if (opts.ffOnly) mergeArgs.push("--ff-only");
23211
23542
  mergeArgs.push(`${remote}/HEAD`);
23212
23543
  const mergeCode = await runLocalGit(mergeArgs, cwd);
@@ -89,7 +89,7 @@ first_positional() {
89
89
  handle_pr() {
90
90
  local op="${1-}"; shift || true
91
91
  if [ -z "$op" ]; then
92
- die "missing subcommand for 'gh pr'. Supported: view, list, create, comment, review, merge, checkout, close, reopen"
92
+ die "missing subcommand for 'gh pr'. Supported: view, list, diff, checks, create, comment, review, merge, checkout, close, reopen"
93
93
  fi
94
94
  local branch
95
95
  branch="$(box_branch)"
@@ -109,6 +109,20 @@ handle_pr() {
109
109
  fi
110
110
  exec "$CTL" gh pr list -- "$@"
111
111
  ;;
112
+ diff)
113
+ strict_flags "gh pr diff" "--color|--name-only|--patch" "$@"
114
+ if [ -z "$(first_positional "--color" "$@")" ] && [ -n "$branch" ]; then
115
+ set -- "$branch" "$@"
116
+ fi
117
+ exec "$CTL" gh pr diff -- "$@"
118
+ ;;
119
+ checks)
120
+ strict_flags "gh pr checks" "--json|--required" "$@"
121
+ if [ -z "$(first_positional "--json" "$@")" ] && [ -n "$branch" ]; then
122
+ set -- "$branch" "$@"
123
+ fi
124
+ exec "$CTL" gh pr checks -- "$@"
125
+ ;;
112
126
  create)
113
127
  strict_flags "gh pr create" "--fill|--draft|--title|--body|--base" "$@"
114
128
  if ! needle_present "--head" "$@" && [ -n "$branch" ]; then
@@ -162,11 +176,70 @@ handle_pr() {
162
176
  exec "$CTL" gh pr checkout -- "$@"
163
177
  ;;
164
178
  *)
165
- die "'gh pr $op' is not proxied (supported: view, list, create, comment, review, merge, checkout, close, reopen)"
179
+ die "'gh pr $op' is not proxied (supported: view, list, diff, checks, create, comment, review, merge, checkout, close, reopen)"
166
180
  ;;
167
181
  esac
168
182
  }
169
183
 
184
+ handle_run() {
185
+ local op="${1-}"; shift || true
186
+ if [ -z "$op" ]; then
187
+ die "missing subcommand for 'gh run'. Supported: list, view, rerun"
188
+ fi
189
+ case "$op" in
190
+ list)
191
+ strict_flags "gh run list" "--json|--limit|-L|--workflow|-w|--branch|-b|--status|--user" "$@"
192
+ exec "$CTL" gh run list -- "$@"
193
+ ;;
194
+ view)
195
+ strict_flags "gh run view" "--json|--log|--log-failed|--job" "$@"
196
+ # gh run view needs a run-id OR a --job <id>; refuse when neither is
197
+ # present (host-side gh would otherwise try an interactive picker and
198
+ # fail without a TTY).
199
+ if [ -z "$(first_positional "--json|--job" "$@")" ] && ! needle_present "--job" "$@"; then
200
+ die "'gh run view' requires a positional <run-id> (or --job <job-id>)"
201
+ fi
202
+ exec "$CTL" gh run view -- "$@"
203
+ ;;
204
+ rerun)
205
+ strict_flags "gh run rerun" "--failed|--job" "$@"
206
+ if [ -z "$(first_positional "--job" "$@")" ] && ! needle_present "--job" "$@"; then
207
+ die "'gh run rerun' requires a positional <run-id> (or --job <job-id>)"
208
+ fi
209
+ exec "$CTL" gh run rerun -- "$@"
210
+ ;;
211
+ watch)
212
+ die "'gh run watch' is not proxied (it blocks until CI finishes). Poll status with 'gh run view <run-id>' instead."
213
+ ;;
214
+ *)
215
+ die "'gh run $op' is not proxied (supported: list, view, rerun)"
216
+ ;;
217
+ esac
218
+ }
219
+
220
+ handle_api() {
221
+ local endpoint="${1-}"
222
+ if [ -z "$endpoint" ] || [ "${endpoint:0:1}" = "-" ]; then
223
+ die "'gh api' requires a positional <endpoint> (e.g. repos/:owner/:repo/pulls/:number/comments)"
224
+ fi
225
+ shift
226
+ # GET reads are proxied for any allowlisted endpoint; POST is proxied only to
227
+ # the PR review-comment endpoints (the relay enforces the endpoint + method
228
+ # policy). `--input` (stdin/file body) can't cross the relay — the host `gh`
229
+ # runs with stdin ignored — so reject it locally and point at -f/-F fields.
230
+ local arg
231
+ for arg in "$@"; do
232
+ case "$arg" in
233
+ --input|--input=*)
234
+ die "'gh api --input' (stdin/file body) isn't supported through the relay; use -f/-F fields"
235
+ ;;
236
+ esac
237
+ done
238
+ strict_flags "gh api" \
239
+ "--method|-X|-f|-F|--field|--raw-field|--jq|-q|--paginate|--cache|--hostname|-H|--header" "$@"
240
+ exec "$CTL" gh api "$endpoint" -- "$@"
241
+ }
242
+
170
243
  handle_repo() {
171
244
  local op="${1-}"; shift || true
172
245
  if [ "$op" != "clone" ]; then
@@ -218,7 +291,7 @@ handle_repo() {
218
291
 
219
292
  # Top-level dispatch.
220
293
  if [ $# -eq 0 ]; then
221
- die "no subcommand. Supported: pr {view,list,create,comment,review,merge,checkout,close,reopen}, repo clone, auth status, --version"
294
+ die "no subcommand. Supported: pr {view,list,diff,checks,create,comment,review,merge,checkout,close,reopen}, run {list,view,rerun}, api <endpoint>, repo clone, auth status, --version"
222
295
  fi
223
296
 
224
297
  case "$1" in
@@ -232,7 +305,7 @@ case "$1" in
232
305
  ;;
233
306
  --help|-h)
234
307
  printf 'agentbox gh shim — strict subset.\n' >&2
235
- printf 'Supported: pr {view,list,create,comment,review,merge,checkout,close,reopen}, repo clone, auth status, --version\n' >&2
308
+ printf 'Supported: pr {view,list,diff,checks,create,comment,review,merge,checkout,close,reopen}, run {list,view,rerun}, api <endpoint>, repo clone, auth status, --version\n' >&2
236
309
  printf 'Anything else is rejected. Run host `gh --help` for full upstream docs.\n' >&2
237
310
  ;;
238
311
  auth)
@@ -253,11 +326,19 @@ case "$1" in
253
326
  shift
254
327
  handle_pr "$@"
255
328
  ;;
329
+ run)
330
+ shift
331
+ handle_run "$@"
332
+ ;;
333
+ api)
334
+ shift
335
+ handle_api "$@"
336
+ ;;
256
337
  repo)
257
338
  shift
258
339
  handle_repo "$@"
259
340
  ;;
260
341
  *)
261
- die "'gh $1' is not proxied (supported: pr {…}, repo clone, auth status, --version)"
342
+ die "'gh $1' is not proxied (supported: pr {…}, run {…}, api <endpoint>, repo clone, auth status, --version)"
262
343
  ;;
263
344
  esac