@madarco/agentbox 0.1.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-IDR4HVIC.js +106 -0
  3. package/dist/chunk-IDR4HVIC.js.map +1 -0
  4. package/dist/chunk-J35IH7W5.js +200 -0
  5. package/dist/chunk-J35IH7W5.js.map +1 -0
  6. package/dist/chunk-O5HS3QHW.js +2164 -0
  7. package/dist/chunk-O5HS3QHW.js.map +1 -0
  8. package/dist/chunk-OOOKFFR5.js +496 -0
  9. package/dist/chunk-OOOKFFR5.js.map +1 -0
  10. package/dist/chunk-RWJE6AER.js +515 -0
  11. package/dist/chunk-RWJE6AER.js.map +1 -0
  12. package/dist/chunk-SOMIKEN2.js +1651 -0
  13. package/dist/chunk-SOMIKEN2.js.map +1 -0
  14. package/dist/create-LSSO7H4I-GWNALUMF.js +15 -0
  15. package/dist/create-LSSO7H4I-GWNALUMF.js.map +1 -0
  16. package/dist/index.js +4067 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lifecycle-P4FSKGR2-3466P54Y.js +38 -0
  19. package/dist/lifecycle-P4FSKGR2-3466P54Y.js.map +1 -0
  20. package/dist/state-ZSP3ORXW-WI6KOIG3.js +26 -0
  21. package/dist/state-ZSP3ORXW-WI6KOIG3.js.map +1 -0
  22. package/dist/stats-GZFLPYTU-DBJ2DVBJ.js +19 -0
  23. package/dist/stats-GZFLPYTU-DBJ2DVBJ.js.map +1 -0
  24. package/package.json +73 -0
  25. package/runtime/docker/Dockerfile.box +316 -0
  26. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +188 -0
  27. package/runtime/docker/packages/ctl/dist/bin.cjs +12770 -0
  28. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +52 -0
  29. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +77 -0
  30. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +54 -0
  31. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +21 -0
  32. package/runtime/relay/bin.cjs +11467 -0
  33. package/share/agentbox-setup/SKILL.md +188 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AmbiguousBoxError,
4
+ BoxNotFoundError,
5
+ destroyBox,
6
+ getBoxHostPaths,
7
+ inspectBox,
8
+ listBoxes,
9
+ openBoxInFinder,
10
+ pauseBox,
11
+ pruneBoxes,
12
+ snapshotPresent,
13
+ startBox,
14
+ stopBox,
15
+ unpauseBox
16
+ } from "./chunk-RWJE6AER.js";
17
+ import {
18
+ SNAPSHOTS_ROOT
19
+ } from "./chunk-O5HS3QHW.js";
20
+ import "./chunk-IDR4HVIC.js";
21
+ import "./chunk-SOMIKEN2.js";
22
+ export {
23
+ AmbiguousBoxError,
24
+ BoxNotFoundError,
25
+ SNAPSHOTS_ROOT,
26
+ destroyBox,
27
+ getBoxHostPaths,
28
+ inspectBox,
29
+ listBoxes,
30
+ openBoxInFinder,
31
+ pauseBox,
32
+ pruneBoxes,
33
+ snapshotPresent,
34
+ startBox,
35
+ stopBox,
36
+ unpauseBox
37
+ };
38
+ //# sourceMappingURL=lifecycle-P4FSKGR2-3466P54Y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ STATE_DIR,
4
+ STATE_FILE,
5
+ allocateProjectIndex,
6
+ autoPickProjectBox,
7
+ findBox,
8
+ readState,
9
+ recordBox,
10
+ removeBoxRecord,
11
+ resolveBoxRef,
12
+ writeState
13
+ } from "./chunk-IDR4HVIC.js";
14
+ export {
15
+ STATE_DIR,
16
+ STATE_FILE,
17
+ allocateProjectIndex,
18
+ autoPickProjectBox,
19
+ findBox,
20
+ readState,
21
+ recordBox,
22
+ removeBoxRecord,
23
+ resolveBoxRef,
24
+ writeState
25
+ };
26
+ //# sourceMappingURL=state-ZSP3ORXW-WI6KOIG3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ agentboxHomeBytes,
4
+ allCheckpointVolumesBytes,
5
+ boxResourceStats,
6
+ parseDockerSize,
7
+ projectCheckpointVolumeBytes,
8
+ volumeSizeBytes
9
+ } from "./chunk-J35IH7W5.js";
10
+ import "./chunk-SOMIKEN2.js";
11
+ export {
12
+ agentboxHomeBytes,
13
+ allCheckpointVolumesBytes,
14
+ boxResourceStats,
15
+ parseDockerSize,
16
+ projectCheckpointVolumeBytes,
17
+ volumeSizeBytes
18
+ };
19
+ //# sourceMappingURL=stats-GZFLPYTU-DBJ2DVBJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@madarco/agentbox",
3
+ "version": "0.1.0",
4
+ "description": "Launch Claude Code, Codex, and other coding agents in isolated sandboxes",
5
+ "license": "MIT",
6
+ "author": "Marco D'Alia",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=20.10"
10
+ },
11
+ "bin": {
12
+ "agentbox": "./dist/index.js"
13
+ },
14
+ "keywords": [
15
+ "claude",
16
+ "claude-code",
17
+ "codex",
18
+ "coding-agent",
19
+ "sandbox",
20
+ "docker",
21
+ "cli",
22
+ "isolation",
23
+ "agentbox"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/madarco/agentbox.git",
28
+ "directory": "apps/cli"
29
+ },
30
+ "homepage": "https://github.com/madarco/agentbox#readme",
31
+ "bugs": {
32
+ "url": "https://github.com/madarco/agentbox/issues"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "share",
40
+ "runtime"
41
+ ],
42
+ "dependencies": {
43
+ "@clack/prompts": "^0.9.0",
44
+ "@xterm/headless": "^5.5.0",
45
+ "commander": "^12.1.0",
46
+ "execa": "^9.5.2",
47
+ "supports-hyperlinks": "^3.1.0",
48
+ "yaml": "^2.6.1"
49
+ },
50
+ "optionalDependencies": {
51
+ "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^22.10.1",
55
+ "tsup": "^8.3.5",
56
+ "typescript": "^5.7.2",
57
+ "vitest": "^2.1.8",
58
+ "@agentbox/config": "0.0.0",
59
+ "@agentbox/core": "0.0.0",
60
+ "@agentbox/ctl": "0.0.0",
61
+ "@agentbox/sandbox-docker": "0.0.0"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup",
65
+ "dev": "tsup --watch",
66
+ "lint": "eslint src test",
67
+ "test": "vitest run",
68
+ "typecheck": "tsc --noEmit",
69
+ "clean": "rm -rf dist runtime .turbo",
70
+ "publish:patch": "npm version patch && git push --follow-tags",
71
+ "publish:minor": "npm version minor && git push --follow-tags"
72
+ }
73
+ }
@@ -0,0 +1,316 @@
1
+ # AgentBox base box image.
2
+ #
3
+ # Layered on Microsoft's devcontainers base:ubuntu (arm64-native — the
4
+ # `universal:2-linux` image is amd64-only, which is a non-starter on Apple
5
+ # Silicon hosts). We add the bits the box needs to mount a FUSE overlay over
6
+ # /host-src + /upper, plus a "universal-ish" set of language runtimes
7
+ # (Node.js 22 from NodeSource, Python 3 from apt). Anything heavier (Go, Java,
8
+ # Ruby, .NET, browser tooling, vscode-server) goes in a later iteration.
9
+ #
10
+ # Required runtime flags:
11
+ # --cap-add=SYS_ADMIN --device=/dev/fuse --security-opt=apparmor:unconfined
12
+ FROM mcr.microsoft.com/devcontainers/base:ubuntu
13
+
14
+ USER root
15
+
16
+ ENV DEBIAN_FRONTEND=noninteractive
17
+
18
+ # COLORTERM is the de-facto signal TUI apps (including Claude Code) use to
19
+ # decide whether to emit 24-bit RGB. Without it, claude sees TERM=tmux-256color
20
+ # in panes and falls back to a 256-color palette, which renders the logo and
21
+ # gradients as dim monochrome blocks. tmux is configured to forward RGB to the
22
+ # outer terminal regardless; this just unlocks the in-pane side.
23
+ #
24
+ # DISABLE_AUTOUPDATER follows Anthropic's own devcontainer guidance: there's no
25
+ # point updating Claude Code inside a sandbox that's torn down regularly, and
26
+ # leaving it enabled triggers a "installMethod is native, but .local/bin
27
+ # missing" warning when the host's .claude.json (synced in) reports a native
28
+ # install method while the box uses npm-global.
29
+ #
30
+ # LANG/LC_ALL must be set via ENV (not /etc/environment, which is what the
31
+ # devcontainers base seeds): `docker exec` doesn't source login files, so
32
+ # without this every host-launched tmux/claude inherits an empty LANG, the
33
+ # active locale falls back to POSIX, and tmux 3.x treats the terminal as
34
+ # non-UTF-8 — Claude's rounded prompt box (`╭ ─ ╮ │ ╰ ╯`) renders as ASCII.
35
+ # en_US.UTF-8 is pre-generated by the base image, so no locale-gen needed.
36
+ ENV COLORTERM=truecolor \
37
+ DISABLE_AUTOUPDATER=1 \
38
+ LANG=en_US.UTF-8 \
39
+ LC_ALL=en_US.UTF-8 \
40
+ PATH=/home/vscode/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
41
+
42
+ # The PATH prepend above is load-bearing: Anthropic's native installer drops
43
+ # `claude` at /home/vscode/.local/bin/, which isn't in `docker exec`'s default
44
+ # PATH. Without this, the tmux session started by `startClaudeSession` can't
45
+ # resolve `claude` via /bin/sh -c and the session exits immediately.
46
+
47
+ RUN apt-get update \
48
+ && apt-get install -y --no-install-recommends \
49
+ curl ca-certificates gnupg \
50
+ && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
51
+ && apt-get install -y --no-install-recommends \
52
+ fuse3 \
53
+ fuse-overlayfs \
54
+ rsync \
55
+ nodejs \
56
+ python3 \
57
+ python3-pip \
58
+ python3-venv \
59
+ build-essential \
60
+ git \
61
+ tmux \
62
+ libcap2-bin \
63
+ && rm -rf /var/lib/apt/lists/* \
64
+ && mkdir -p /workspace /host-src /upper /snapshot /run/agentbox /var/log/agentbox \
65
+ && chmod 755 /workspace /host-src /upper /snapshot \
66
+ && chown vscode:vscode /run/agentbox /var/log/agentbox
67
+
68
+ # The in-box supervisor (runs as non-root `vscode`) owns a TCP forwarder that
69
+ # binds container :80 -> the `expose:`-flagged service (see WebProxy /
70
+ # agentbox.yaml `expose:`). Binding a port <1024 needs CAP_NET_BIND_SERVICE;
71
+ # grant it to the node binary so the supervisor can listen on :80 without root.
72
+ # Broad (any in-box node can bind low ports) but acceptable for a single-tenant
73
+ # sandbox — strictly narrower than the existing NET_ADMIN/seccomp=unconfined.
74
+ RUN setcap cap_net_bind_service=+ep "$(readlink -f "$(command -v node)")"
75
+
76
+ # Host repos are bind-mounted in at their identical absolute path (worktree
77
+ # pointer files contain absolute paths to <main>/.git/worktrees/<name>, so both
78
+ # sides have to resolve the same path), and the host owns those `.git/` dirs.
79
+ # git's recent "dubious ownership" guard would refuse to operate without an
80
+ # explicit allowlist. /etc/gitconfig is system-wide and unaffected by the RO
81
+ # bind-mount of ~/.gitconfig from the host.
82
+ RUN git config --system --add safe.directory '*'
83
+
84
+ # Docker-in-Docker. dockerd inside the box (storage-driver=fuse-overlayfs, set
85
+ # in /etc/docker/daemon.json) lets the agent run `docker build`/`docker run`
86
+ # in its own namespace without exposing the host daemon. fuse-overlayfs is
87
+ # required because the kernel `overlay` driver isn't usable from an
88
+ # unprivileged container — but fuse3 + fuse-overlayfs are already installed
89
+ # above for the /workspace overlay, so docker reuses them. iptables is needed
90
+ # for inner-container bridge networking. Adding `vscode` to the `docker` group
91
+ # lets the agent invoke `docker` without sudo once dockerd creates the socket
92
+ # at /var/run/docker.sock at runtime. The matching launch script is COPY'd in
93
+ # below; the daemon is started by the host via `docker exec -d --user root`
94
+ # (see launchDockerdDaemon), mirroring the VNC/ctl daemon pattern.
95
+ RUN apt-get update \
96
+ && apt-get install -y --no-install-recommends \
97
+ docker.io \
98
+ iptables \
99
+ && rm -rf /var/lib/apt/lists/* \
100
+ && mkdir -p /etc/docker \
101
+ && printf '%s\n' '{ "storage-driver": "fuse-overlayfs", "iptables": true }' > /etc/docker/daemon.json \
102
+ && usermod -aG docker vscode
103
+
104
+ # agentbox-ctl: the in-container supervisor + CLI. Build context is the
105
+ # monorepo root, so packages/ctl/dist is the prebuilt bundle from
106
+ # `pnpm --filter @agentbox/ctl build`. The CLI is bundled with all deps
107
+ # inlined (see tsup config), so a single COPY is enough.
108
+ COPY packages/ctl/dist/bin.cjs /usr/local/bin/agentbox-ctl
109
+ RUN chmod +x /usr/local/bin/agentbox-ctl
110
+
111
+ # Setup guide for the first-run wizard. The CLI also installs the same file as
112
+ # a host-side claude skill (~/.claude/skills/agentbox-setup/SKILL.md) the
113
+ # first time the wizard fires; the existing ~/.claude rsync flow propagates
114
+ # it into every box automatically. Keeping a copy baked into the image at a
115
+ # stable path guarantees the wizard's initial prompt can reference the guide
116
+ # even on hosts where the user wiped their ~/.claude/skills.
117
+ COPY apps/cli/share/agentbox-setup/SKILL.md /usr/local/share/agentbox/setup-guide.md
118
+ RUN chmod 0644 /usr/local/share/agentbox/setup-guide.md
119
+
120
+ # Prepare /home/vscode/.claude (the volume mount target) and the .claude.json
121
+ # symlink. The mkdir+chown is load-bearing: when we mount the named
122
+ # `agentbox-claude-config` volume at runtime, Docker seeds the empty volume's
123
+ # perms from this directory on first mount. Without the chown the volume comes
124
+ # up root-owned and Claude Code can't write.
125
+ #
126
+ # The symlink ~/.claude.json -> ~/.claude/_claude.json routes Claude's
127
+ # top-level state file (onboarding, anonymous id, plugin caches, oauth account
128
+ # stub) into the volume, so it persists across box rebuilds and gets refreshed
129
+ # from the host on every `agentbox claude` create. The target is initially
130
+ # dangling — it materializes when `ensureClaudeVolume` copies the host's
131
+ # ~/.claude.json into the volume.
132
+ RUN mkdir -p /home/vscode/.claude \
133
+ && chown vscode:vscode /home/vscode/.claude \
134
+ && ln -s /home/vscode/.claude/_claude.json /home/vscode/.claude.json \
135
+ && chown -h vscode:vscode /home/vscode/.claude.json
136
+
137
+ # Prepare /home/vscode/.vscode-server and /home/vscode/.cursor-server (+ their
138
+ # extensions subdirs) so the named volumes mounted at runtime — per-box
139
+ # `agentbox-{vscode,cursor}-server-<id>` over the server dirs, then shared
140
+ # `agentbox-{vscode,cursor}-extensions` over the extensions subdirs — come up
141
+ # owned by vscode. Same load-bearing reason as the .claude block above: empty
142
+ # named volumes inherit the mount point's perms on first mount; without these
143
+ # chowns, the Dev Containers extension (in either VS Code or Cursor) fails to
144
+ # mkdir <server>/bin/<commit>/ during server install ("Permission denied").
145
+ RUN mkdir -p /home/vscode/.vscode-server/extensions /home/vscode/.cursor-server/extensions \
146
+ && chown -R vscode:vscode /home/vscode/.vscode-server /home/vscode/.cursor-server
147
+
148
+ # Claude Code via Anthropic's native installer — the recommended path per
149
+ # code.claude.com/docs/en/setup (npm-global is under "Advanced installation"
150
+ # now, with a warning against `sudo npm install -g`). Pinned to the `stable`
151
+ # release channel so rebuilds get a predictable ~week-old release.
152
+ # DISABLE_AUTOUPDATER=1 (set above) prevents in-box self-updates. We run as
153
+ # `vscode` so the binary lands at /home/vscode/.local/bin/claude — the path
154
+ # the host's `.claude.json` (with installMethod=native) expects, so the in-box
155
+ # integrity check doesn't fire.
156
+ USER vscode
157
+ RUN curl -fsSL https://claude.ai/install.sh | bash -s stable
158
+ USER root
159
+
160
+ # Browser support for in-box agents: Vercel's agent-browser drives Chrome via
161
+ # CDP. Two things have to happen here:
162
+ #
163
+ # 1. Install Chrome's runtime libs (the well-known set Playwright's
164
+ # `install-deps` installs). Package names are pinned to Ubuntu 24.04
165
+ # (Noble) — the t64 suffix marks libs that flipped to 64-bit time_t in
166
+ # the noble transition. If the base image moves back to jammy these will
167
+ # need the un-suffixed names.
168
+ #
169
+ # 2. Get an actual Chromium binary. `agent-browser install` would normally
170
+ # download Chrome for Testing, but Chrome for Testing has no Linux ARM64
171
+ # build (Apple Silicon hosts run linux/arm64 containers) and Noble's
172
+ # `chromium-browser` apt package is a snap stub that won't run inside a
173
+ # container. The reliable cross-arch source is Playwright's bundled
174
+ # Chromium, so we install playwright globally just for its
175
+ # `playwright install chromium` downloader, then point agent-browser at
176
+ # the resulting binary via AGENT_BROWSER_EXECUTABLE_PATH. agent-browser
177
+ # honours that env var (priority: CLI flag > env > config file >
178
+ # built-in default; see agent-browser's README).
179
+ #
180
+ # Runtime state (sessions, auth cookies under ~/.agent-browser/) lives in the
181
+ # container's writable layer, preserved across pause/unpause/stop/start and
182
+ # wiped on `agentbox destroy`.
183
+ RUN apt-get update \
184
+ && apt-get install -y --no-install-recommends \
185
+ libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 \
186
+ libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
187
+ libgbm1 libdrm2 libpango-1.0-0 libcairo2 libasound2t64 \
188
+ fonts-liberation xdg-utils \
189
+ && rm -rf /var/lib/apt/lists/*
190
+
191
+ RUN npm install -g agent-browser playwright
192
+
193
+ # Download Chromium as `vscode` so the ms-playwright cache lands in vscode's
194
+ # home (the user agent-browser runs as). The downloaded binary lives at
195
+ # `chromium-XXXX/chrome-linux/chrome`, where XXXX is a Playwright-internal
196
+ # revision number that changes between releases — we resolve it once here and
197
+ # write the result to a stable symlink so AGENT_BROWSER_EXECUTABLE_PATH can
198
+ # point at something predictable.
199
+ USER vscode
200
+ RUN playwright install chromium \
201
+ && ln -sf "$(ls /home/vscode/.cache/ms-playwright/chromium-*/chrome-linux/chrome | sort | tail -1)" /tmp/chromium-link \
202
+ && test -x "$(readlink /tmp/chromium-link)"
203
+ USER root
204
+ RUN mv /tmp/chromium-link /usr/local/bin/chromium
205
+
206
+ ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/local/bin/chromium
207
+
208
+ # VNC: Xvnc (TigerVNC, X server + RFB in one binary), noVNC (HTML5 client),
209
+ # websockify (the WS<->RFB proxy noVNC needs). No window manager: a Chromium
210
+ # launched with DISPLAY=:1 fills the screen, and agents drive tabs through the
211
+ # browser itself. Adding a WM would cost another ~10MB and complicate the
212
+ # lifecycle for no win.
213
+ #
214
+ # autocutsel: with no WM, nothing owns the X11 CLIPBOARD/PRIMARY selections, so
215
+ # the text noVNC delivers via RFB cut-text isn't reliably served back to
216
+ # Chromium on Ctrl+V. autocutsel (one daemon per selection, started by
217
+ # agentbox-vnc-start) keeps both selections populated and in sync. xclip is
218
+ # kept alongside it purely for debugging the clipboard round-trip.
219
+ RUN apt-get update \
220
+ && apt-get install -y --no-install-recommends \
221
+ tigervnc-standalone-server tigervnc-common tigervnc-tools \
222
+ novnc websockify \
223
+ autocutsel xclip \
224
+ && rm -rf /var/lib/apt/lists/*
225
+
226
+ # DISPLAY is image-baked so every `docker exec` (the claude tmux session,
227
+ # `agentbox shell`, any agent subprocess) inherits it. Anything launched
228
+ # without an explicit DISPLAY override renders to the VNC session.
229
+ ENV DISPLAY=:1
230
+
231
+ # EXPOSE 6080 is load-bearing for OrbStack. OrbStack maps
232
+ # `<container-name>.orb.local` to the container's single EXPOSE'd port; without
233
+ # this, the auto-DNS responds but nothing is routed. (Docker Desktop doesn't
234
+ # use EXPOSE for routing — it just needs the `-p` flag passed at `docker run`.)
235
+ EXPOSE 6080
236
+ # Web service port. The canonical host-reachable URL is the published loopback
237
+ # port (`-p 127.0.0.1:0:80`, resolved per box); EXPOSE 80 is only an OrbStack
238
+ # bare-domain bonus and is not load-bearing (OrbStack honors a single EXPOSE).
239
+ EXPOSE 80
240
+
241
+ # Pre-create the per-user .vnc dir so it's writable from the runtime user. The
242
+ # password file itself is written at container start by agentbox-vnc-start
243
+ # (derived from AGENTBOX_VNC_PASSWORD).
244
+ RUN mkdir -p /home/vscode/.vnc \
245
+ && chown -R vscode:vscode /home/vscode/.vnc
246
+ COPY packages/sandbox-docker/scripts/agentbox-vnc-start /usr/local/bin/agentbox-vnc-start
247
+ RUN chmod +x /usr/local/bin/agentbox-vnc-start
248
+
249
+ # DinD launcher script. Same exec-d-as-root pattern as agentbox-vnc-start.
250
+ COPY packages/sandbox-docker/scripts/agentbox-dockerd-start /usr/local/bin/agentbox-dockerd-start
251
+ RUN chmod +x /usr/local/bin/agentbox-dockerd-start
252
+
253
+ # tmux config so Claude's true-color output and OSC 8 hyperlinks survive the
254
+ # in-container tmux. `terminal-features` is a no-op on tmux < 3.4. Without
255
+ # this, claude renders without 24-bit color (logo invisible) and hyperlinks
256
+ # get broken across visual wrap boundaries. The WheelUp/Down rebinds drop the
257
+ # copy-mode scroll step from the built-in 5 lines to 2 — a macOS trackpad
258
+ # swipe emits a burst of wheel events, so the default flies past dozens of
259
+ # lines on a single flick.
260
+ RUN printf '%s\n' \
261
+ 'set -g default-terminal "tmux-256color"' \
262
+ 'set -as terminal-overrides ",*:Tc"' \
263
+ 'set -as terminal-overrides ",*:RGB"' \
264
+ 'set -as terminal-features ",*:hyperlinks"' \
265
+ 'set -as terminal-features ",*:RGB"' \
266
+ 'set -g mouse on' \
267
+ 'bind -T copy-mode WheelUpPane send -N2 -X scroll-up' \
268
+ 'bind -T copy-mode WheelDownPane send -N2 -X scroll-down' \
269
+ 'bind -T copy-mode-vi WheelUpPane send -N2 -X scroll-up' \
270
+ 'bind -T copy-mode-vi WheelDownPane send -N2 -X scroll-down' \
271
+ 'set -g history-limit 50000' \
272
+ 'set -g escape-time 0' \
273
+ > /etc/tmux.conf
274
+
275
+ # System-wide Claude Code hint. NOTE: /etc/claude-code/CLAUDE.md is not a
276
+ # documented Claude Code load path today (only /etc/claude-code/managed-settings.json
277
+ # is, and that's settings-only) — kept here so it's discoverable via
278
+ # `cat /etc/claude-code/CLAUDE.md` and forward-compatible if Claude Code
279
+ # adds it to its memory chain. Until then, in-box agents learn the same
280
+ # facts via /etc/agentbox/box.env + AGENTBOX_* env vars. Content lives in
281
+ # scripts/custom-system-CLAUDE.md (edit there, not inline) for maintainability.
282
+ RUN mkdir -p /etc/claude-code
283
+ COPY packages/sandbox-docker/scripts/custom-system-CLAUDE.md /etc/claude-code/CLAUDE.md
284
+ RUN chmod 0644 /etc/claude-code/CLAUDE.md
285
+
286
+ # Enterprise-managed Claude Code settings: activity-reporting hooks. Highest
287
+ # precedence and NOT synced from the host ~/.claude, so the host-hook filter
288
+ # never touches it; hook arrays merge across settings sources, so the user's
289
+ # own hooks still run. Drives `agentbox status/list/inspect` claude activity.
290
+ # (Build context is the monorepo root — see BUILD_CONTEXT_DIR in image.ts.)
291
+ COPY packages/sandbox-docker/scripts/claude-managed-settings.json /etc/claude-code/managed-settings.json
292
+ RUN chmod 0644 /etc/claude-code/managed-settings.json
293
+
294
+ # /etc/agentbox/ holds runtime-injected box.env (written by `agentbox create`
295
+ # via docker exec). Pre-created here so the writable layer starts with the
296
+ # right perms; the file itself appears at create time.
297
+ RUN mkdir -p /etc/agentbox && chmod 0755 /etc/agentbox
298
+
299
+ # Login-shell shim: source /etc/agentbox/box.env so `agentbox shell <box>` and
300
+ # any `bash -l` see AGENTBOX_* even when launched outside `docker run`'s env
301
+ # (e.g. interactive shells). `set -a` exports every sourced var.
302
+ RUN printf '%s\n' \
303
+ '# Auto-loaded by login shells; box.env is written by `agentbox create`.' \
304
+ 'if [ -r /etc/agentbox/box.env ]; then' \
305
+ ' set -a' \
306
+ ' . /etc/agentbox/box.env' \
307
+ ' set +a' \
308
+ 'fi' \
309
+ > /etc/profile.d/agentbox.sh \
310
+ && chmod 0644 /etc/profile.d/agentbox.sh
311
+
312
+ # Default to the vscode user (UID 1000) provided by base:ubuntu. The overlay
313
+ # mount itself happens via `docker exec --user root`, so this is just the
314
+ # default identity for interactive shells.
315
+ USER vscode
316
+ WORKDIR /workspace