@madarco/agentbox 0.7.0 → 0.9.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/_cloud-attach-ZXBCNWJX.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
- package/dist/chunk-BXQMIEHC.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
- package/dist/chunk-GU5LW4B5.js.map +1 -0
- package/dist/chunk-KL36BRN4.js +455 -0
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
- package/dist/chunk-NCJP5MTN.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
- package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
- package/dist/dist-CX5CGVEB.js.map +1 -0
- package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
- package/dist/dist-GDHP34ZK.js.map +1 -0
- package/dist/dist-XML54CNB.js +849 -0
- package/dist/dist-XML54CNB.js.map +1 -0
- package/dist/index.js +3881 -867
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
- package/package.json +7 -5
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/agentbox-vnc-start +15 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1214 -98
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +1146 -63
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +196 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23466 -0
- package/runtime/vercel/custom-system-CLAUDE.md +50 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +274 -0
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
|
@@ -1,37 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
2
|
+
"$comment": "Codex 0.134.0 expects `~/.codex/hooks.json` to be `{ hooks: { Event: [...] } }` (matching the `HooksFile` Rust struct), NOT a top-level event map. The `hooks` feature flag must also be enabled (`codex --enable hooks`) and hook trust must be either persisted via the in-TUI dialog or bypassed at launch (`--dangerously-bypass-hook-trust`). startCodexSession() does both. In practice the hook firing on the JSON-config path is still unreliable in 0.134.0 (TUI mode skips them on at least some startup paths) — the real mechanism that lights up state in production is the tmux-pane scraper in packages/ctl/src/codex-scraper.ts. These hooks remain as a defense-in-depth seed so any future codex build that fixes the firing also lights up state without further work.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"UserPromptSubmit": [
|
|
12
|
+
{
|
|
13
|
+
"hooks": [
|
|
14
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"PreToolUse": [
|
|
19
|
+
{
|
|
20
|
+
"hooks": [
|
|
21
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PermissionRequest": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{ "type": "command", "command": "agentbox-ctl codex-state waiting >/dev/null 2>&1 &", "timeout": 3 }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"PreCompact": [
|
|
33
|
+
{
|
|
34
|
+
"hooks": [
|
|
35
|
+
{ "type": "command", "command": "agentbox-ctl codex-state compacting >/dev/null 2>&1 &", "timeout": 3 }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"PostCompact": [
|
|
40
|
+
{
|
|
41
|
+
"hooks": [
|
|
42
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"SubagentStart": [
|
|
47
|
+
{
|
|
48
|
+
"hooks": [
|
|
49
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"SubagentStop": [
|
|
54
|
+
{
|
|
55
|
+
"hooks": [
|
|
56
|
+
{ "type": "command", "command": "agentbox-ctl codex-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"Stop": [
|
|
61
|
+
{
|
|
62
|
+
"hooks": [
|
|
63
|
+
{ "type": "command", "command": "agentbox-ctl codex-state idle >/dev/null 2>&1 &", "timeout": 3 }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
37
68
|
}
|
|
@@ -65,11 +65,25 @@ if ! pgrep -u "$(id -u)" -x autocutsel >/dev/null; then
|
|
|
65
65
|
DISPLAY=:1 autocutsel -selection PRIMARY -fork >/dev/null 2>&1 || true
|
|
66
66
|
fi
|
|
67
67
|
|
|
68
|
+
# noVNC's static assets live at different paths per base image: Debian/Ubuntu
|
|
69
|
+
# (docker, hetzner) ship them at /usr/share/novnc via apt; the AL2023 bake
|
|
70
|
+
# (vercel) git-clones them to /usr/local/share/novnc. websockify runs
|
|
71
|
+
# os.chdir(--web) at startup, so a wrong path makes it FileNotFoundError and
|
|
72
|
+
# never bind 6080 — pick the first dir that exists.
|
|
73
|
+
NOVNC_WEB=""
|
|
74
|
+
for _d in /usr/share/novnc /usr/local/share/novnc; do
|
|
75
|
+
if [[ -d "$_d" ]]; then NOVNC_WEB="$_d"; break; fi
|
|
76
|
+
done
|
|
77
|
+
if [[ -z "$NOVNC_WEB" ]]; then
|
|
78
|
+
echo "agentbox-vnc-start: noVNC assets not found (looked in /usr/share/novnc, /usr/local/share/novnc)" >&2
|
|
79
|
+
exit 65
|
|
80
|
+
fi
|
|
81
|
+
|
|
68
82
|
# websockify serves noVNC at /vnc.html (--web) and tunnels WS frames to Xvnc's
|
|
69
83
|
# RFB. Bind 0.0.0.0:6080 so both Docker `-p` mappings and OrbStack's
|
|
70
84
|
# <name>.orb.local routing reach it.
|
|
71
85
|
websockify \
|
|
72
|
-
--web
|
|
86
|
+
--web="$NOVNC_WEB" \
|
|
73
87
|
0.0.0.0:6080 \
|
|
74
88
|
127.0.0.1:5901 \
|
|
75
89
|
>/var/log/agentbox/websockify.log 2>&1 &
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "AgentBox enterprise-managed Claude Code settings, baked into the box image at /etc/claude-code/managed-settings.json. Highest precedence and NOT synced from the host ~/.claude, so claude-hooks-filter.ts never touches it; per Claude Code, hook arrays MERGE across settings sources, so the user's own hooks still run. These hooks report Claude's activity to the box supervisor (agentbox-ctl claude-state -> ctl socket -> relay -> ~/.agentbox/boxes/<id>/status.json) so `agentbox status/list/inspect` can show it even when the box is paused. Each command is exit-0 fast and shell-backgrounded so a hook can never block or fail a Claude turn.",
|
|
2
|
+
"$comment": "AgentBox enterprise-managed Claude Code settings, baked into the box image at /etc/claude-code/managed-settings.json. Highest precedence and NOT synced from the host ~/.claude, so claude-hooks-filter.ts never touches it; per Claude Code, hook arrays MERGE across settings sources, so the user's own hooks still run. These hooks report Claude's activity to the box supervisor (agentbox-ctl claude-state -> ctl socket -> relay -> ~/.agentbox/boxes/<id>/status.json) so `agentbox status/list/inspect` can show it even when the box is paused. Each command is exit-0 fast and shell-backgrounded so a hook can never block or fail a Claude turn. The ExitPlanMode / AskUserQuestion entries run SYNCHRONOUSLY (no &) because they consume the hook's stdin payload; the catchall PreToolUse 'working' hook races with them, but the supervisor's sticky-state semantics swallow that race (a 'working' set while in end-plan/question is ignored unless --clear-pending is set, which only the matching PostToolUse hook passes).",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"UserPromptSubmit": [
|
|
5
5
|
{
|
|
@@ -13,6 +13,32 @@
|
|
|
13
13
|
"hooks": [
|
|
14
14
|
{ "type": "command", "command": "agentbox-ctl claude-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
15
15
|
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"matcher": "ExitPlanMode",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{ "type": "command", "command": "agentbox-ctl claude-state end-plan --payload-stdin >/dev/null 2>&1", "timeout": 3 }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "AskUserQuestion",
|
|
25
|
+
"hooks": [
|
|
26
|
+
{ "type": "command", "command": "agentbox-ctl claude-state question --payload-stdin >/dev/null 2>&1", "timeout": 3 }
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PostToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "ExitPlanMode",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{ "type": "command", "command": "agentbox-ctl claude-state working --clear-pending >/dev/null 2>&1 &", "timeout": 3 }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"matcher": "AskUserQuestion",
|
|
39
|
+
"hooks": [
|
|
40
|
+
{ "type": "command", "command": "agentbox-ctl claude-state working --clear-pending >/dev/null 2>&1 &", "timeout": 3 }
|
|
41
|
+
]
|
|
16
42
|
}
|
|
17
43
|
],
|
|
18
44
|
"Stop": [
|
|
@@ -22,6 +48,41 @@
|
|
|
22
48
|
]
|
|
23
49
|
}
|
|
24
50
|
],
|
|
51
|
+
"StopFailure": [
|
|
52
|
+
{
|
|
53
|
+
"hooks": [
|
|
54
|
+
{ "type": "command", "command": "agentbox-ctl claude-state error >/dev/null 2>&1 &", "timeout": 3 }
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"PreCompact": [
|
|
59
|
+
{
|
|
60
|
+
"hooks": [
|
|
61
|
+
{ "type": "command", "command": "agentbox-ctl claude-state compacting >/dev/null 2>&1 &", "timeout": 3 }
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"PostCompact": [
|
|
66
|
+
{
|
|
67
|
+
"hooks": [
|
|
68
|
+
{ "type": "command", "command": "agentbox-ctl claude-state working --clear-pending >/dev/null 2>&1 &", "timeout": 3 }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"SubagentStart": [
|
|
73
|
+
{
|
|
74
|
+
"hooks": [
|
|
75
|
+
{ "type": "command", "command": "agentbox-ctl claude-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"SubagentStop": [
|
|
80
|
+
{
|
|
81
|
+
"hooks": [
|
|
82
|
+
{ "type": "command", "command": "agentbox-ctl claude-state working >/dev/null 2>&1 &", "timeout": 3 }
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
],
|
|
25
86
|
"Notification": [
|
|
26
87
|
{
|
|
27
88
|
"matcher": "permission_prompt",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# AgentBox sandbox
|
|
1
|
+
# AgentBox sandbox (docker provider)
|
|
2
2
|
|
|
3
|
-
You are running inside an AgentBox sandbox: a Linux Docker container
|
|
4
|
-
docker-in-docker (run `docker` directly, no sudo).
|
|
3
|
+
You are running inside an AgentBox sandbox: a Linux Docker container on the
|
|
4
|
+
user's host with docker-in-docker (run `docker` directly, no sudo).
|
|
5
5
|
Your user is `vscode` and you can use passwordless **sudo** to run commands as root.
|
|
6
6
|
`/workspace` is your own per-box git worktree on branch `agentbox/<box-name>`:
|
|
7
7
|
writes there stay in the container's writable layer and don't touch the host's working
|
|
@@ -10,11 +10,22 @@ tree.
|
|
|
10
10
|
You can save the current filesystem state to be reused by future boxes by
|
|
11
11
|
running `agentbox-ctl checkpoint --set-default`. This doesn't need to be confirmed by the user.
|
|
12
12
|
|
|
13
|
-
The main `.git/` is bind-mounted at the same absolute path as on
|
|
13
|
+
The host's main `.git/` is bind-mounted at the same absolute path as on
|
|
14
14
|
the host, so local commits show up in the host's `git log` immediately.
|
|
15
15
|
No SSH creds, no host gitconfig identity. For ops that need the user
|
|
16
16
|
(push, fetch from private remotes), use `agentbox-ctl git push|fetch|pull
|
|
17
17
|
-- <args>` — it RPCs to the host, which runs git with the real SSH agent.
|
|
18
|
+
The wrapper already builds `git push <remote> <branch>` host-side from the
|
|
19
|
+
registered worktree; the `-- <args>` slot is for extra flags only (e.g.
|
|
20
|
+
`--force-with-lease`, `--tags`). Re-passing the remote or branch makes git
|
|
21
|
+
treat them as refspecs and fails with `refs/remotes/origin/HEAD cannot be
|
|
22
|
+
resolved to branch`.
|
|
23
|
+
|
|
24
|
+
For GitHub PR work, use `agentbox-ctl git pr <op> [args...]` — same model,
|
|
25
|
+
relay shells to host `gh`. Ops: `create`, `view`, `list`, `comment`,
|
|
26
|
+
`review`, `merge`, `close`, `reopen`, `checkout`. `view` / `list` are
|
|
27
|
+
read-only and run silently; everything else asks the user to confirm in
|
|
28
|
+
the host wrapper (deny → exit 10).
|
|
18
29
|
|
|
19
30
|
For ad-hoc file transfers between this box and the host, use
|
|
20
31
|
`agentbox-ctl cp toHost <boxPath> <hostPath>` and
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# agentbox `gh` shim — translates a strict subset of `gh` subcommands into
|
|
3
|
+
# `agentbox-ctl gh ...` so the host's authenticated `gh` runs the operation
|
|
4
|
+
# and only the result crosses back into the box. The in-box agent never sees
|
|
5
|
+
# a GitHub token.
|
|
6
|
+
#
|
|
7
|
+
# This shim ships only what Claude Code's PR badge and our documented agent
|
|
8
|
+
# flows need. Anything outside the subset below is rejected with a clear
|
|
9
|
+
# error — better safe than compatible. Add ops deliberately, not by default.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# Paths are constants in production; env overrides exist purely to let unit
|
|
14
|
+
# tests substitute a stub `agentbox-ctl` on PATH without rewriting the shim.
|
|
15
|
+
CTL="${AGENTBOX_CTL_PATH:-/usr/local/bin/agentbox-ctl}"
|
|
16
|
+
REAL_GIT="${AGENTBOX_REAL_GIT_PATH:-/usr/bin/git}"
|
|
17
|
+
|
|
18
|
+
die() {
|
|
19
|
+
printf 'agentbox gh shim: %s\n' "$*" >&2
|
|
20
|
+
exit 2
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# Resolve the in-box current branch once. Used to inject the right ref into
|
|
24
|
+
# `gh pr` commands so the host's `gh` (which runs in the host main repo, not
|
|
25
|
+
# the box's worktree) doesn't fall back to the host's HEAD.
|
|
26
|
+
box_branch() {
|
|
27
|
+
"$REAL_GIT" -C "$PWD" rev-parse --abbrev-ref HEAD 2>/dev/null || true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Returns 0 if any element of "$@" equals "$1" (the needle).
|
|
31
|
+
needle_present() {
|
|
32
|
+
local needle="$1"; shift
|
|
33
|
+
local arg
|
|
34
|
+
for arg in "$@"; do
|
|
35
|
+
if [ "$arg" = "$needle" ]; then return 0; fi
|
|
36
|
+
done
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Walk argv: if any arg starts with `-` and isn't in the allowed set, die.
|
|
41
|
+
# Doesn't try to validate flag _values_ (e.g. `--json number,url`); that's
|
|
42
|
+
# real gh's job — we only block flags we don't expect to see.
|
|
43
|
+
strict_flags() {
|
|
44
|
+
local subcmd="$1"; shift
|
|
45
|
+
local allowed="$1"; shift
|
|
46
|
+
local re="^(${allowed})$"
|
|
47
|
+
local arg
|
|
48
|
+
for arg in "$@"; do
|
|
49
|
+
case "$arg" in
|
|
50
|
+
--) ;;
|
|
51
|
+
-*)
|
|
52
|
+
if ! [[ "$arg" =~ $re ]]; then
|
|
53
|
+
die "unsupported flag '$arg' for '$subcmd'. Allowed: ${allowed//|/, }"
|
|
54
|
+
fi
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Returns the first true positional arg of "$@" (skipping flags AND their
|
|
61
|
+
# values), or '' if none. $1 is a `|`-separated list of value-taking flags
|
|
62
|
+
# for the current subcommand — e.g. "--json|--title|--body|--base" means the
|
|
63
|
+
# token after any of those flags is a value, not a positional. Without this
|
|
64
|
+
# we'd treat `--json number,url`'s field list as the positional and miss
|
|
65
|
+
# branch injection (real bug: PR-badge lookups returned "no PR for main"
|
|
66
|
+
# because the JSON field list looked positional).
|
|
67
|
+
first_positional() {
|
|
68
|
+
local value_taking="$1"; shift
|
|
69
|
+
local re="^(${value_taking})$"
|
|
70
|
+
local arg
|
|
71
|
+
local skip_value=0
|
|
72
|
+
for arg in "$@"; do
|
|
73
|
+
if [ "$skip_value" = "1" ]; then
|
|
74
|
+
skip_value=0
|
|
75
|
+
continue
|
|
76
|
+
fi
|
|
77
|
+
case "$arg" in
|
|
78
|
+
--) ;;
|
|
79
|
+
-*)
|
|
80
|
+
if [[ -n "$value_taking" && "$arg" =~ $re ]]; then
|
|
81
|
+
skip_value=1
|
|
82
|
+
fi
|
|
83
|
+
;;
|
|
84
|
+
*) printf '%s\n' "$arg"; return 0 ;;
|
|
85
|
+
esac
|
|
86
|
+
done
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
handle_pr() {
|
|
90
|
+
local op="${1-}"; shift || true
|
|
91
|
+
if [ -z "$op" ]; then
|
|
92
|
+
die "missing subcommand for 'gh pr'. Supported: view, list, create, comment, review, merge, checkout, close, reopen"
|
|
93
|
+
fi
|
|
94
|
+
local branch
|
|
95
|
+
branch="$(box_branch)"
|
|
96
|
+
|
|
97
|
+
case "$op" in
|
|
98
|
+
view)
|
|
99
|
+
strict_flags "gh pr view" "--json" "$@"
|
|
100
|
+
if [ -z "$(first_positional "--json" "$@")" ] && [ -n "$branch" ]; then
|
|
101
|
+
set -- "$branch" "$@"
|
|
102
|
+
fi
|
|
103
|
+
exec "$CTL" gh pr view -- "$@"
|
|
104
|
+
;;
|
|
105
|
+
list)
|
|
106
|
+
strict_flags "gh pr list" "--json|--state" "$@"
|
|
107
|
+
if ! needle_present "--head" "$@" && [ -n "$branch" ]; then
|
|
108
|
+
set -- "$@" "--head" "$branch"
|
|
109
|
+
fi
|
|
110
|
+
exec "$CTL" gh pr list -- "$@"
|
|
111
|
+
;;
|
|
112
|
+
create)
|
|
113
|
+
strict_flags "gh pr create" "--fill|--draft|--title|--body|--base" "$@"
|
|
114
|
+
if ! needle_present "--head" "$@" && [ -n "$branch" ]; then
|
|
115
|
+
set -- "$@" "--head" "$branch"
|
|
116
|
+
fi
|
|
117
|
+
exec "$CTL" gh pr create -- "$@"
|
|
118
|
+
;;
|
|
119
|
+
comment)
|
|
120
|
+
strict_flags "gh pr comment" "--body" "$@"
|
|
121
|
+
if [ -z "$(first_positional "--body" "$@")" ] && [ -n "$branch" ]; then
|
|
122
|
+
set -- "$branch" "$@"
|
|
123
|
+
fi
|
|
124
|
+
exec "$CTL" gh pr comment -- "$@"
|
|
125
|
+
;;
|
|
126
|
+
review)
|
|
127
|
+
strict_flags "gh pr review" "--approve|--request-changes|--comment|--body" "$@"
|
|
128
|
+
if [ -z "$(first_positional "--body" "$@")" ] && [ -n "$branch" ]; then
|
|
129
|
+
set -- "$branch" "$@"
|
|
130
|
+
fi
|
|
131
|
+
exec "$CTL" gh pr review -- "$@"
|
|
132
|
+
;;
|
|
133
|
+
merge)
|
|
134
|
+
strict_flags "gh pr merge" "--squash|--merge|--rebase|--delete-branch" "$@"
|
|
135
|
+
if [ -z "$(first_positional "" "$@")" ] && [ -n "$branch" ]; then
|
|
136
|
+
set -- "$branch" "$@"
|
|
137
|
+
fi
|
|
138
|
+
exec "$CTL" gh pr merge -- "$@"
|
|
139
|
+
;;
|
|
140
|
+
close)
|
|
141
|
+
strict_flags "gh pr close" "--delete-branch" "$@"
|
|
142
|
+
if [ -z "$(first_positional "" "$@")" ] && [ -n "$branch" ]; then
|
|
143
|
+
set -- "$branch" "$@"
|
|
144
|
+
fi
|
|
145
|
+
exec "$CTL" gh pr close -- "$@"
|
|
146
|
+
;;
|
|
147
|
+
reopen)
|
|
148
|
+
strict_flags "gh pr reopen" "" "$@"
|
|
149
|
+
if [ -z "$(first_positional "" "$@")" ] && [ -n "$branch" ]; then
|
|
150
|
+
set -- "$branch" "$@"
|
|
151
|
+
fi
|
|
152
|
+
exec "$CTL" gh pr reopen -- "$@"
|
|
153
|
+
;;
|
|
154
|
+
checkout)
|
|
155
|
+
# gh pr checkout takes a required ref (number / URL / branch). No
|
|
156
|
+
# auto-inject — the relay also refuses checkout by default
|
|
157
|
+
# (AGENTBOX_GH_PR_CHECKOUT=allow opt-in).
|
|
158
|
+
strict_flags "gh pr checkout" "" "$@"
|
|
159
|
+
if [ -z "$(first_positional "" "$@")" ]; then
|
|
160
|
+
die "'gh pr checkout' requires a positional ref (PR number, URL, or branch)"
|
|
161
|
+
fi
|
|
162
|
+
exec "$CTL" gh pr checkout -- "$@"
|
|
163
|
+
;;
|
|
164
|
+
*)
|
|
165
|
+
die "'gh pr $op' is not proxied (supported: view, list, create, comment, review, merge, checkout, close, reopen)"
|
|
166
|
+
;;
|
|
167
|
+
esac
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
handle_repo() {
|
|
171
|
+
local op="${1-}"; shift || true
|
|
172
|
+
if [ "$op" != "clone" ]; then
|
|
173
|
+
die "'gh repo $op' is not proxied (supported: clone)"
|
|
174
|
+
fi
|
|
175
|
+
# gh repo clone <repo> [<dir>] [--branch <n>] [--depth <n>]
|
|
176
|
+
strict_flags "gh repo clone" "--branch|--depth" "$@"
|
|
177
|
+
# Split argv into (repo, dir, flags). Pass positionals BEFORE options to the
|
|
178
|
+
# ctl so commander parses --branch/--depth as real options; using a `--`
|
|
179
|
+
# separator would force commander to treat them as extra positionals (the
|
|
180
|
+
# ctl `gh repo clone` command doesn't allowExcessArguments).
|
|
181
|
+
local repo='' dir=''
|
|
182
|
+
local -a flags=()
|
|
183
|
+
local pos=0
|
|
184
|
+
local skip_value=0
|
|
185
|
+
local arg
|
|
186
|
+
for arg in "$@"; do
|
|
187
|
+
if [ "$skip_value" = "1" ]; then
|
|
188
|
+
flags+=("$arg")
|
|
189
|
+
skip_value=0
|
|
190
|
+
continue
|
|
191
|
+
fi
|
|
192
|
+
case "$arg" in
|
|
193
|
+
--branch|--depth)
|
|
194
|
+
flags+=("$arg")
|
|
195
|
+
skip_value=1
|
|
196
|
+
;;
|
|
197
|
+
-*)
|
|
198
|
+
flags+=("$arg")
|
|
199
|
+
;;
|
|
200
|
+
*)
|
|
201
|
+
if [ $pos -eq 0 ]; then repo="$arg"
|
|
202
|
+
elif [ $pos -eq 1 ]; then dir="$arg"
|
|
203
|
+
else die "too many positionals for 'gh repo clone' (got '$arg' after repo + dir)"
|
|
204
|
+
fi
|
|
205
|
+
pos=$((pos+1))
|
|
206
|
+
;;
|
|
207
|
+
esac
|
|
208
|
+
done
|
|
209
|
+
if [ -z "$repo" ]; then
|
|
210
|
+
die "'gh repo clone' requires a positional <repo> (owner/name or full URL)"
|
|
211
|
+
fi
|
|
212
|
+
if [ -n "$dir" ]; then
|
|
213
|
+
exec "$CTL" gh repo clone "$repo" "$dir" ${flags[@]+"${flags[@]}"}
|
|
214
|
+
else
|
|
215
|
+
exec "$CTL" gh repo clone "$repo" ${flags[@]+"${flags[@]}"}
|
|
216
|
+
fi
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Top-level dispatch.
|
|
220
|
+
if [ $# -eq 0 ]; then
|
|
221
|
+
die "no subcommand. Supported: pr {view,list,create,comment,review,merge,checkout,close,reopen}, repo clone, auth status, --version"
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
case "$1" in
|
|
225
|
+
--version|-v)
|
|
226
|
+
# Real gh prints something like:
|
|
227
|
+
# gh version 2.40.0 (2023-10-26)
|
|
228
|
+
# https://github.com/cli/cli/releases/tag/v2.40.0
|
|
229
|
+
# Tools that sniff "gh version" succeed with our shim line too.
|
|
230
|
+
printf 'gh version 2.0.0 (agentbox-shim)\n'
|
|
231
|
+
printf 'https://github.com/cli/cli\n'
|
|
232
|
+
;;
|
|
233
|
+
--help|-h)
|
|
234
|
+
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
|
|
236
|
+
printf 'Anything else is rejected. Run host `gh --help` for full upstream docs.\n' >&2
|
|
237
|
+
;;
|
|
238
|
+
auth)
|
|
239
|
+
shift
|
|
240
|
+
case "${1-}" in
|
|
241
|
+
status)
|
|
242
|
+
# Real auth state is verified host-side on the next real RPC (relay's
|
|
243
|
+
# assertGhReady returns exit 4 if the host is logged out). We don't
|
|
244
|
+
# round-trip on every refresh-driven poll Claude Code does.
|
|
245
|
+
printf 'agentbox gh shim: logged in to github.com (via agentbox host relay)\n' >&2
|
|
246
|
+
;;
|
|
247
|
+
*)
|
|
248
|
+
die "'gh auth ${1-}' is not proxied (supported: status)"
|
|
249
|
+
;;
|
|
250
|
+
esac
|
|
251
|
+
;;
|
|
252
|
+
pr)
|
|
253
|
+
shift
|
|
254
|
+
handle_pr "$@"
|
|
255
|
+
;;
|
|
256
|
+
repo)
|
|
257
|
+
shift
|
|
258
|
+
handle_repo "$@"
|
|
259
|
+
;;
|
|
260
|
+
*)
|
|
261
|
+
die "'gh $1' is not proxied (supported: pr {…}, repo clone, auth status, --version)"
|
|
262
|
+
;;
|
|
263
|
+
esac
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# agentbox `git` shim — intercepts the four network ops (`push`, `pull`,
|
|
3
|
+
# `fetch`, `clone`) and routes them through `agentbox-ctl git ...` so the
|
|
4
|
+
# host runs the credential-touching part. Everything else (commit, status,
|
|
5
|
+
# log, diff, add, checkout, branch, ...) falls through to the real
|
|
6
|
+
# `/usr/bin/git` with zero overhead and zero shim output.
|
|
7
|
+
#
|
|
8
|
+
# Strict per-op flag whitelist — better safe than compatible. Adding a flag
|
|
9
|
+
# is a deliberate decision, not a default.
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# Paths are constants in production; env overrides exist purely to let unit
|
|
14
|
+
# tests substitute a stub `agentbox-ctl` on PATH without rewriting the shim.
|
|
15
|
+
# NEVER `exec git` — would loop on this shim. Always exec the resolved $REAL_GIT.
|
|
16
|
+
CTL="${AGENTBOX_CTL_PATH:-/usr/local/bin/agentbox-ctl}"
|
|
17
|
+
REAL_GIT="${AGENTBOX_REAL_GIT_PATH:-/usr/bin/git}"
|
|
18
|
+
|
|
19
|
+
die() {
|
|
20
|
+
printf 'agentbox git shim: %s\n' "$*" >&2
|
|
21
|
+
exit 2
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Walk argv: any arg matching `-*` must be in $allowed (regex alternation).
|
|
25
|
+
# Doesn't validate flag _values_ (e.g. `--branch main`); that's git's job.
|
|
26
|
+
strict_flags() {
|
|
27
|
+
local subcmd="$1"; shift
|
|
28
|
+
local allowed="$1"; shift
|
|
29
|
+
local re="^(${allowed})$"
|
|
30
|
+
local arg
|
|
31
|
+
for arg in "$@"; do
|
|
32
|
+
case "$arg" in
|
|
33
|
+
--) ;;
|
|
34
|
+
-*)
|
|
35
|
+
if ! [[ "$arg" =~ $re ]]; then
|
|
36
|
+
die "unsupported flag '$arg' for '$subcmd'. Allowed: ${allowed//|/, }"
|
|
37
|
+
fi
|
|
38
|
+
;;
|
|
39
|
+
esac
|
|
40
|
+
done
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# Network ops (push/pull/fetch): refuse positional remote/branch. The ctl
|
|
44
|
+
# rebuilds them from the registered worktree; re-passing them as positionals
|
|
45
|
+
# makes git treat them as refspecs and fail with
|
|
46
|
+
# `refs/remotes/origin/HEAD cannot be resolved to branch` (documented at
|
|
47
|
+
# packages/ctl/src/commands/git.ts:131).
|
|
48
|
+
refuse_positionals() {
|
|
49
|
+
local subcmd="$1"; shift
|
|
50
|
+
local arg
|
|
51
|
+
for arg in "$@"; do
|
|
52
|
+
case "$arg" in
|
|
53
|
+
--) ;;
|
|
54
|
+
-*) ;;
|
|
55
|
+
*)
|
|
56
|
+
die "positional '$arg' not allowed for '$subcmd' (the box's remote and branch are taken from the registered worktree)"
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if [ $# -eq 0 ]; then
|
|
63
|
+
exec "$REAL_GIT"
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
case "$1" in
|
|
67
|
+
push)
|
|
68
|
+
shift
|
|
69
|
+
strict_flags "git push" "--force-with-lease" "$@"
|
|
70
|
+
refuse_positionals "git push" "$@"
|
|
71
|
+
exec "$CTL" git push -- "$@"
|
|
72
|
+
;;
|
|
73
|
+
pull)
|
|
74
|
+
shift
|
|
75
|
+
strict_flags "git pull" "--ff-only" "$@"
|
|
76
|
+
refuse_positionals "git pull" "$@"
|
|
77
|
+
exec "$CTL" git pull -- "$@"
|
|
78
|
+
;;
|
|
79
|
+
fetch)
|
|
80
|
+
shift
|
|
81
|
+
strict_flags "git fetch" "--prune" "$@"
|
|
82
|
+
refuse_positionals "git fetch" "$@"
|
|
83
|
+
exec "$CTL" git fetch -- "$@"
|
|
84
|
+
;;
|
|
85
|
+
clone)
|
|
86
|
+
shift
|
|
87
|
+
strict_flags "git clone" "--branch|--depth" "$@"
|
|
88
|
+
# Walk argv: split into (url, dir, flags) so we can hand them to ctl
|
|
89
|
+
# in commander's expected shape (positionals first, then options).
|
|
90
|
+
url=''
|
|
91
|
+
dir=''
|
|
92
|
+
flags=()
|
|
93
|
+
pos=0
|
|
94
|
+
skip_value=0
|
|
95
|
+
for arg in "$@"; do
|
|
96
|
+
if [ "$skip_value" = "1" ]; then
|
|
97
|
+
flags+=("$arg")
|
|
98
|
+
skip_value=0
|
|
99
|
+
continue
|
|
100
|
+
fi
|
|
101
|
+
case "$arg" in
|
|
102
|
+
--branch|--depth)
|
|
103
|
+
flags+=("$arg")
|
|
104
|
+
skip_value=1
|
|
105
|
+
;;
|
|
106
|
+
-*)
|
|
107
|
+
flags+=("$arg")
|
|
108
|
+
;;
|
|
109
|
+
*)
|
|
110
|
+
if [ $pos -eq 0 ]; then url="$arg"
|
|
111
|
+
elif [ $pos -eq 1 ]; then dir="$arg"
|
|
112
|
+
else die "too many positionals for 'git clone' (got '$arg' after url + dir)"
|
|
113
|
+
fi
|
|
114
|
+
pos=$((pos+1))
|
|
115
|
+
;;
|
|
116
|
+
esac
|
|
117
|
+
done
|
|
118
|
+
if [ -z "$url" ]; then
|
|
119
|
+
die "'git clone' requires a positional <url> (github URL or owner/name shorthand)"
|
|
120
|
+
fi
|
|
121
|
+
# `set -u` + empty array: use the ${arr[@]+"${arr[@]}"} guard.
|
|
122
|
+
if [ -n "$dir" ]; then
|
|
123
|
+
exec "$CTL" git clone "$url" "$dir" ${flags[@]+"${flags[@]}"}
|
|
124
|
+
else
|
|
125
|
+
exec "$CTL" git clone "$url" ${flags[@]+"${flags[@]}"}
|
|
126
|
+
fi
|
|
127
|
+
;;
|
|
128
|
+
*)
|
|
129
|
+
exec "$REAL_GIT" "$@"
|
|
130
|
+
;;
|
|
131
|
+
esac
|