agent-dev-env 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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/bridge/bridge.js +202 -0
  3. package/dist/assets/guest/guest-agent-mac.js +586 -0
  4. package/dist/assets/guest/guest-agent-ubuntu.js +481 -0
  5. package/dist/assets/guest/guest-agent-windows.js +466 -0
  6. package/dist/assets/images/mac/CHANGELOG.md +265 -0
  7. package/dist/assets/images/mac/README.md +63 -0
  8. package/dist/assets/images/mac/sandbox.pkr.hcl +468 -0
  9. package/dist/assets/images/mac/vars/sandbox-macos-tahoe.pkrvars.hcl +39 -0
  10. package/dist/assets/images/ubuntu-arm64-vmware/CHANGELOG.md +239 -0
  11. package/dist/assets/images/ubuntu-arm64-vmware/README.md +177 -0
  12. package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/meta-data +2 -0
  13. package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/user-data +34 -0
  14. package/dist/assets/images/ubuntu-arm64-vmware/sandbox.pkr.hcl +776 -0
  15. package/dist/assets/images/ubuntu-arm64-vmware/vars/sandbox-ubuntu-24-04-arm64-vmware.pkrvars.hcl +71 -0
  16. package/dist/assets/images/windows-arm64-qemu/CHANGELOG.md +209 -0
  17. package/dist/assets/images/windows-arm64-qemu/README.md +142 -0
  18. package/dist/assets/images/windows-arm64-qemu/autounattend.xml +325 -0
  19. package/dist/assets/images/windows-arm64-qemu/qemu-with-tpm.sh +170 -0
  20. package/dist/assets/images/windows-arm64-qemu/sandbox.pkr.hcl +1012 -0
  21. package/dist/assets/images/windows-arm64-qemu/vars/sandbox-windows-11-arm64-qemu.pkrvars.hcl +83 -0
  22. package/dist/assets/images/windows-arm64-vmware/CHANGELOG.md +270 -0
  23. package/dist/assets/images/windows-arm64-vmware/README.md +171 -0
  24. package/dist/assets/images/windows-arm64-vmware/autounattend.xml +311 -0
  25. package/dist/assets/images/windows-arm64-vmware/sandbox.pkr.hcl +1036 -0
  26. package/dist/assets/images/windows-arm64-vmware/vars/sandbox-windows-11-arm64-vmware.pkrvars.hcl +83 -0
  27. package/dist/assets/rules/agent-rules-linux.md +68 -0
  28. package/dist/assets/rules/agent-rules.md +71 -0
  29. package/dist/assets/watchdog/watch-build-ocr.swift +40 -0
  30. package/dist/assets/watchdog/watch-build.py +275 -0
  31. package/dist/cli.js +52 -0
  32. package/dist/commands/delete.js +179 -0
  33. package/dist/commands/doctor.js +165 -0
  34. package/dist/commands/list.js +38 -0
  35. package/dist/commands/not-yet.js +15 -0
  36. package/dist/commands/register.js +150 -0
  37. package/dist/commands/run.js +65 -0
  38. package/dist/commands/status.js +142 -0
  39. package/dist/commands/stop.js +186 -0
  40. package/dist/commands/sync.js +98 -0
  41. package/dist/lib/exec.js +250 -0
  42. package/dist/lib/ghcr.js +82 -0
  43. package/dist/lib/git.js +100 -0
  44. package/dist/lib/logger.js +87 -0
  45. package/dist/lib/network.js +63 -0
  46. package/dist/lib/paths.js +116 -0
  47. package/dist/lib/platform.js +87 -0
  48. package/dist/lib/prompt.js +66 -0
  49. package/dist/lib/qemu.js +373 -0
  50. package/dist/lib/regex.js +9 -0
  51. package/dist/lib/ssh.js +230 -0
  52. package/dist/lib/tart.js +210 -0
  53. package/dist/lib/template.js +35 -0
  54. package/dist/lib/vars.js +99 -0
  55. package/dist/lib/vmrun.js +334 -0
  56. package/dist/lifecycle/build-macos.js +23 -0
  57. package/dist/lifecycle/build-qemu.js +204 -0
  58. package/dist/lifecycle/build-shared.js +355 -0
  59. package/dist/lifecycle/build-ubuntu.js +184 -0
  60. package/dist/lifecycle/build-watchdog.js +146 -0
  61. package/dist/lifecycle/build-windows-vmware.js +112 -0
  62. package/dist/lifecycle/build.js +55 -0
  63. package/dist/lifecycle/catalog.js +165 -0
  64. package/dist/lifecycle/deploy.js +161 -0
  65. package/dist/lifecycle/tag.js +77 -0
  66. package/dist/lifecycle/watch-build.js +64 -0
  67. package/dist/runners/bridges.js +153 -0
  68. package/dist/runners/framework.js +66 -0
  69. package/dist/runners/macos-bridges.js +143 -0
  70. package/dist/runners/macos-guest.js +76 -0
  71. package/dist/runners/macos-rules.js +77 -0
  72. package/dist/runners/macos-summary.js +145 -0
  73. package/dist/runners/macos.js +258 -0
  74. package/dist/runners/openchamber.js +60 -0
  75. package/dist/runners/options.js +56 -0
  76. package/dist/runners/qemu-image.js +183 -0
  77. package/dist/runners/rules.js +49 -0
  78. package/dist/runners/ubuntu-bridges.js +154 -0
  79. package/dist/runners/ubuntu-guest.js +136 -0
  80. package/dist/runners/ubuntu-image.js +26 -0
  81. package/dist/runners/ubuntu-rules.js +86 -0
  82. package/dist/runners/ubuntu-shared.js +65 -0
  83. package/dist/runners/ubuntu-summary.js +141 -0
  84. package/dist/runners/ubuntu.js +117 -0
  85. package/dist/runners/vmware-common.js +158 -0
  86. package/dist/runners/vmware-image.js +242 -0
  87. package/dist/runners/windows-autologon.js +119 -0
  88. package/dist/runners/windows-bridges.js +136 -0
  89. package/dist/runners/windows-guest.js +213 -0
  90. package/dist/runners/windows-image.js +23 -0
  91. package/dist/runners/windows-qemu-summary.js +85 -0
  92. package/dist/runners/windows-qemu.js +189 -0
  93. package/dist/runners/windows-shared.js +68 -0
  94. package/dist/runners/windows-summary.js +104 -0
  95. package/dist/runners/windows.js +96 -0
  96. package/dist/settings/common.js +107 -0
  97. package/dist/settings/macos-copy.js +223 -0
  98. package/dist/settings/macos.js +41 -0
  99. package/dist/settings/ubuntu-copy.js +223 -0
  100. package/dist/settings/ubuntu.js +68 -0
  101. package/package.json +52 -0
@@ -0,0 +1,468 @@
1
+ packer {
2
+ required_plugins {
3
+ tart = {
4
+ version = ">= 1.20.0"
5
+ source = "github.com/cirruslabs/tart"
6
+ }
7
+ }
8
+ }
9
+
10
+ # ===== Variables =====
11
+ #
12
+ # Each sandbox image is a single macOS version, described by a variables file
13
+ # in vars/ named after the image (`sandbox-macos-<macos-version>`). See
14
+ # DEVELOPMENT.md for how to add a new macOS version.
15
+
16
+ variable "macos_version" {
17
+ type = string
18
+ }
19
+
20
+ variable "xcode_version" {
21
+ type = string
22
+ }
23
+
24
+ variable "disk_size" {
25
+ type = number
26
+ default = 160
27
+ # VM disk size in GB. Must be >= the Cirrus base image disk (140 GB): tart
28
+ # can only grow a disk, never shrink it.
29
+ }
30
+
31
+ variable "cpu_count" {
32
+ type = number
33
+ default = 4
34
+ # CPU count of the VM.
35
+ }
36
+
37
+ variable "memory_gb" {
38
+ type = number
39
+ default = 8
40
+ # RAM of the VM in GB.
41
+ }
42
+
43
+ variable "ssh_username" {
44
+ type = string
45
+ default = "admin"
46
+ # SSH user used for provisioning (fixed in the Cirrus Labs base images).
47
+ }
48
+
49
+ variable "ssh_password" {
50
+ type = string
51
+ default = "admin"
52
+ # SSH password used for provisioning (fixed in the Cirrus Labs base images).
53
+ }
54
+
55
+ variable "openchamber_ui_password" {
56
+ type = string
57
+ default = "sandbox"
58
+ # Password protecting the OpenChamber web UI. OpenChamber refuses to bind
59
+ # the server to a network interface without a UI password; the sandbox
60
+ # listens on 0.0.0.0:<openchamber_port> so the host can reach it (see
61
+ # docs/macos.md).
62
+ }
63
+
64
+ variable "openchamber_port" {
65
+ type = number
66
+ default = 4000
67
+ # TCP port the OpenChamber web UI listens on inside the guest. Not 3000 —
68
+ # that is the default Vite dev-server port and would collide with frontend
69
+ # dev servers in the guest. The CLI's SANDBOX_OPENCHAMBER_PORT default
70
+ # (lib/platform.ts) must stay in sync with this.
71
+ }
72
+
73
+ variable "image_version" {
74
+ type = string
75
+ # Semantic version this image is published under; bump it and add a
76
+ # CHANGELOG.md entry for every release.
77
+ }
78
+
79
+ variable "node_version" {
80
+ type = string
81
+ # Node.js version installed via nvm and set as the default (see the
82
+ # "Node.js via nvm" provisioner below).
83
+ }
84
+
85
+ variable "python_version" {
86
+ type = string
87
+ # Homebrew Python version, e.g. "3.14"; also used for the unversioned
88
+ # python/python3/pip/pip3 aliases.
89
+ }
90
+
91
+ variable "brew_formulas" {
92
+ type = list(string)
93
+ default = [
94
+ # Core CLI tools
95
+ "bash", "git", "gh", "jq", "ripgrep", "coreutils", "curl", "wget",
96
+ # SSH agent bridging (docs/ssh-agent.md)
97
+ "socat",
98
+ # Node.js version manager (Keg-only; the Node runtime itself is installed
99
+ # via nvm — see the "Node.js via nvm" provisioner below)
100
+ "nvm",
101
+ # The required programming languages (Python is installed separately by
102
+ # the toolchain provisioner, pinned via python_version)
103
+ "ruby",
104
+ # Docker CLI + plugins (client only — the sandbox is a macOS VM and cannot
105
+ # run a local container engine, see docs/macos.md "Docker (remote engine)";
106
+ # the compose/buildx plugins are wired up in the provisioner below)
107
+ "docker", "docker-compose", "docker-buildx",
108
+ ]
109
+ }
110
+
111
+ variable "extra_brew_formulas" {
112
+ type = list(string)
113
+ default = []
114
+ }
115
+
116
+ # ===== Builder =====
117
+ #
118
+ # Derives the sandbox VM from Cirrus Labs' pre-built macOS image with Xcode.
119
+ # The base images are published to GHCR, see:
120
+ # https://github.com/orgs/cirruslabs/packages?tab=packages&q=macos-
121
+ #
122
+ # The resulting VM boots to a desktop with auto-login enabled and can be used
123
+ # both with graphics (tart run) and headless (tart run --no-graphics).
124
+ #
125
+ # Audio pass-through to the host (guest sound to host speakers, host
126
+ # microphone to the guest) is a per-run Tart flag, not a persisted VM
127
+ # setting — the sandbox stays audio-isolated from the host by running with
128
+ # `tart run --no-audio` (see docs/macos.md). The build honors the same
129
+ # policy via run_extra_args.
130
+
131
+ source "tart-cli" "tart" {
132
+ vm_base_name = "ghcr.io/cirruslabs/macos-${var.macos_version}-xcode:${var.xcode_version}"
133
+ # The image name is fixed per macOS version: sandbox-macos-<macos-version>.
134
+ # The Xcode version selects the base image only and is not part of the name.
135
+ vm_name = "sandbox-macos-${var.macos_version}"
136
+ cpu_count = var.cpu_count
137
+ memory_gb = var.memory_gb
138
+ disk_size_gb = var.disk_size
139
+ headless = true
140
+ ssh_password = var.ssh_password
141
+ ssh_username = var.ssh_username
142
+ ssh_timeout = "120s"
143
+ # No audio pass-through with the host during the build either (Tart attaches
144
+ # the VirtIO sound device even when running headless).
145
+ run_extra_args = ["--no-audio"]
146
+ }
147
+
148
+ # ===== Provisioners =====
149
+
150
+ build {
151
+ sources = ["source.tart-cli.tart"]
152
+
153
+ # Base system setup: Xcode license, SSH + Screen Sharing (for tart run --vnc),
154
+ # auto-login as admin (boots straight into the desktop and creates an
155
+ # unlocked login.keychain, which is required for headless VMs on macOS 15+).
156
+ provisioner "shell" {
157
+ inline = [<<-END
158
+ set -e -x
159
+ # Xcode (already pre-installed in the Cirrus Labs base image)
160
+ sudo xcodebuild -license accept || true
161
+ sudo xcode-select -p || true
162
+
163
+ # Remote Login (SSH) and Screen Sharing (VNC) — used by `tart run --vnc`
164
+ # and by `tart ip`-based SSH from the host.
165
+ sudo systemsetup -setremotelogin on || true
166
+ sudo launchctl enable system/com.apple.screensharing || true
167
+ sudo launchctl kickstart -k system/com.apple.screensharing || true
168
+
169
+ # Auto-login as admin: boot lands straight on the desktop.
170
+ sudo sysadminctl -autologin set -userName admin -password admin || true
171
+
172
+ # Friendly hostname for the VM
173
+ sudo scutil --set ComputerName "Agent Sandbox" || true
174
+ sudo scutil --set HostName "agent-sandbox" || true
175
+ sudo scutil --set LocalHostName "agent-sandbox" || true
176
+
177
+ # Tart Guest Agent (pre-installed in Cirrus base images) powers clipboard
178
+ # sharing in GUI mode and `tart exec`. Check that it is present.
179
+ if pgrep -x tart-guest-agent >/dev/null; then
180
+ echo "tart-guest-agent: OK"
181
+ else
182
+ echo "tart-guest-agent: MISSING" >&2
183
+ fi
184
+ END
185
+ ]
186
+ }
187
+
188
+ # Homebrew toolchain: nvm (Node version manager), python, ruby and CLI
189
+ # utilities. Node.js itself is installed via nvm in the provisioner below.
190
+ provisioner "shell" {
191
+ inline = [<<-END
192
+ set -e -x
193
+ touch ~/.zprofile
194
+ eval "$(/opt/homebrew/bin/brew shellenv)"
195
+
196
+ brew update
197
+ brew install python@${var.python_version}
198
+ brew install ${join(" ", concat(var.brew_formulas, var.extra_brew_formulas))}
199
+
200
+ # Make Homebrew available to non-interactive shells
201
+ grep -qxF 'eval "$(/opt/homebrew/bin/brew shellenv)"' ~/.zprofile || \
202
+ echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
203
+
204
+ # Unversioned python/pip aliases pointing at brew's python@${var.python_version}
205
+ sudo ln -sf /opt/homebrew/bin/python${var.python_version} /opt/homebrew/bin/python3
206
+ sudo ln -sf /opt/homebrew/bin/python${var.python_version} /opt/homebrew/bin/python
207
+ sudo ln -sf /opt/homebrew/bin/pip${var.python_version} /opt/homebrew/bin/pip3
208
+ sudo ln -sf /opt/homebrew/bin/pip${var.python_version} /opt/homebrew/bin/pip
209
+
210
+ source ~/.zprofile
211
+ python3 --version && pip3 --version
212
+ ruby --version
213
+ END
214
+ ]
215
+ }
216
+
217
+ # Node.js via nvm — brew's nvm formula is keg-only, so we wire it into
218
+ # ~/.zprofile and install the node_version from the vars file as the
219
+ # default version for both interactive and non-interactive shells.
220
+ provisioner "shell" {
221
+ inline = [<<-END
222
+ set -e -x
223
+ # Load nvm in every shell (brew's nvm is keg-only). nvm use default picks the
224
+ # alias created below; the || true keeps non-interactive shells happy before
225
+ # the default alias exists.
226
+ grep -qxF 'export NVM_DIR="$HOME/.nvm"' ~/.zprofile || \
227
+ cat >> ~/.zprofile <<'NVM'
228
+ export NVM_DIR="$HOME/.nvm"
229
+ [ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh"
230
+ nvm use default >/dev/null 2>&1 || true
231
+ NVM
232
+
233
+ source ~/.zprofile
234
+ nvm install ${var.node_version}
235
+ nvm alias default ${var.node_version}
236
+
237
+ node --version && npm --version
238
+ nvm --version
239
+ END
240
+ ]
241
+ }
242
+
243
+ # Visual Studio Code
244
+ provisioner "shell" {
245
+ inline = [<<-END
246
+ set -e -x
247
+ source ~/.zprofile
248
+ curl -fsSL -o /tmp/vscode.zip "https://update.code.visualstudio.com/latest/darwin-universal/stable"
249
+ unzip -q /tmp/vscode.zip -d /tmp/vscode
250
+ sudo rm -rf "/Applications/Visual Studio Code.app"
251
+ sudo mv "/tmp/vscode/Visual Studio Code.app" /Applications/
252
+ rm -rf /tmp/vscode /tmp/vscode.zip
253
+
254
+ # Drop the quarantine attribute so the app launches without Gatekeeper prompts
255
+ sudo xattr -dr com.apple.quarantine "/Applications/Visual Studio Code.app" || true
256
+
257
+ # `code` CLI on PATH
258
+ sudo ln -sf "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" /opt/homebrew/bin/code
259
+
260
+ code --version
261
+ END
262
+ ]
263
+ }
264
+
265
+ # Browsers — Google Chrome and Mozilla Firefox (latest stable universal
266
+ # macOS builds, via Homebrew casks). Quarantine is stripped explicitly so
267
+ # they launch without Gatekeeper prompts (the VM boots straight to the
268
+ # desktop and may run headless; same choice as the xattr call for VS Code
269
+ # above — brew's own --no-quarantine flag was removed in newer Homebrew).
270
+ provisioner "shell" {
271
+ inline = [<<-END
272
+ set -e -x
273
+ source ~/.zprofile
274
+ brew install --cask google-chrome firefox
275
+
276
+ # Drop the quarantine attribute so the apps launch without Gatekeeper prompts
277
+ sudo xattr -dr com.apple.quarantine "/Applications/Google Chrome.app" || true
278
+ sudo xattr -dr com.apple.quarantine "/Applications/Firefox.app" || true
279
+
280
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version
281
+ "/Applications/Firefox.app/Contents/MacOS/firefox" --version
282
+ END
283
+ ]
284
+ }
285
+
286
+ # Sublime Text — the text editor (Homebrew cask, current stable build).
287
+ # The cask also links the `subl` CLI into the Homebrew bin dir.
288
+ provisioner "shell" {
289
+ inline = [<<-END
290
+ set -e -x
291
+ source ~/.zprofile
292
+ brew install --cask sublime-text
293
+
294
+ # Drop the quarantine attribute so the app launches without Gatekeeper prompts
295
+ sudo xattr -dr com.apple.quarantine "/Applications/Sublime Text.app" || true
296
+
297
+ "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl" --version
298
+ END
299
+ ]
300
+ }
301
+
302
+ # OpenCode — the AI coding agent
303
+ provisioner "shell" {
304
+ inline = [<<-END
305
+ set -e -x
306
+ source ~/.zprofile
307
+ # The recommended Homebrew tap, see https://opencode.ai/docs/
308
+ brew install anomalyco/tap/opencode
309
+ opencode --version
310
+ END
311
+ ]
312
+ }
313
+
314
+ # OpenCodeReview — the AI-powered code review CLI
315
+ # (https://github.com/alibaba/open-code-review). Installed via npm (the
316
+ # image ships Node.js via nvm), provides the `ocr` command; its global
317
+ # config (~/.opencodereview/config.json) is synced from the host by the
318
+ # user-settings copy (settings/macos.ts).
319
+ provisioner "shell" {
320
+ inline = [<<-END
321
+ set -e -x
322
+ source ~/.zprofile
323
+ npm install -g @alibaba-group/open-code-review
324
+ ocr --version
325
+ END
326
+ ]
327
+ }
328
+
329
+ # OpenChamber — the native macOS desktop app (https://openchamber.dev),
330
+ # installed alongside the web UI below. Distributed as the `openchamber`
331
+ # Homebrew cask (the arm64 build on Apple Silicon); the cask pins the DMG
332
+ # checksum and stays in sync with the GitHub releases. The app bundles its
333
+ # own OpenCode CLI and manages its own server by default — the sandbox's
334
+ # web UI service on port 4000 below remains the main server; docs/macos.md
335
+ # explains how to pair the app with it so both share sessions.
336
+ provisioner "shell" {
337
+ inline = [<<-END
338
+ set -e -x
339
+ source ~/.zprofile
340
+ brew install --cask openchamber
341
+
342
+ # Drop the quarantine attribute so the app launches without Gatekeeper
343
+ # prompts (same choice as the browsers above)
344
+ sudo xattr -dr com.apple.quarantine "/Applications/OpenChamber.app" || true
345
+
346
+ test -d "/Applications/OpenChamber.app"
347
+ defaults read "/Applications/OpenChamber.app/Contents/Info.plist" CFBundleShortVersionString
348
+ END
349
+ ]
350
+ }
351
+
352
+ # OpenChamber — web UI for OpenCode (https://openchamber.dev). Installed via
353
+ # npm (requires Node.js 22+, the image ships 26 via nvm, and the opencode
354
+ # CLI on PATH). Registered as a login service (LaunchAgent
355
+ # dev.openchamber.web) that listens on 0.0.0.0:${var.openchamber_port}, so
356
+ # the host can open the UI at http://$(tart ip <vm>):${var.openchamber_port}
357
+ # — see docs/macos.md.
358
+ provisioner "shell" {
359
+ inline = [<<-END
360
+ set -e -x
361
+ source ~/.zprofile
362
+ npm install -g @openchamber/web
363
+ openchamber --version
364
+
365
+ # Pin the opencode binary the service will run: `startup enable` snapshots
366
+ # the environment into the LaunchAgent, so resolving the absolute path here
367
+ # (instead of relying on PATH lookup inside the login session) guarantees
368
+ # OpenChamber uses exactly the opencode this image ships.
369
+ opencode_bin="$(command -v opencode)"
370
+ test -x "$opencode_bin"
371
+ export OPENCODE_BINARY="$opencode_bin"
372
+ echo "OpenChamber will run opencode at: $OPENCODE_BINARY"
373
+
374
+ # Auto-start at login, bind to 0.0.0.0 for host access. --lan refuses to
375
+ # start without a UI password, hence openchamber_ui_password.
376
+ # During image builds the admin user may not have a GUI login session yet
377
+ # (auto-login applies on the next boot), so a failed immediate start is OK:
378
+ # the plist is installed and RunAtLoad starts the service at first login.
379
+ openchamber startup enable --port ${var.openchamber_port} --lan --ui-password "${var.openchamber_ui_password}" \
380
+ || echo "WARNING: OpenChamber service installed but not started yet (expected during image builds); it will start at first login"
381
+
382
+ openchamber startup status
383
+
384
+ # Health check if the service already came up during the build.
385
+ if curl -fsS --max-time 5 http://127.0.0.1:${var.openchamber_port}/health >/dev/null 2>&1; then
386
+ echo "OpenChamber: OK (http://127.0.0.1:${var.openchamber_port}/health)"
387
+ else
388
+ echo "WARNING: OpenChamber not reachable yet; it will start at first login"
389
+ fi
390
+ END
391
+ ]
392
+ }
393
+
394
+ # Docker CLI — client only. The sandbox is a macOS VM, and Apple's
395
+ # Virtualization.framework does not support nested virtualization for macOS
396
+ # guests (only Linux guests on M3+ with macOS 15+), so no container engine
397
+ # (Docker Desktop, Colima, ...) can run inside it. The CLI is meant to be
398
+ # pointed at a remote engine — e.g. the host's Docker Desktop over SSH; see
399
+ # docs/macos.md "Docker (remote engine)". The brew docker-compose and
400
+ # docker-buildx formulas install the compose/buildx plugins into
401
+ # $HOMEBREW_PREFIX/lib/docker/cli-plugins; the CLI only discovers them via
402
+ # cliPluginsExtraDirs in ~/.docker/config.json.
403
+ provisioner "shell" {
404
+ inline = [<<-END
405
+ set -e -x
406
+ source ~/.zprofile
407
+
408
+ # Wire Homebrew's cli-plugins dir into the docker CLI config so `docker
409
+ # compose` and `docker buildx` are found. docker preserves this key when the
410
+ # user later runs `docker context use ...` (the file never pre-exists in a
411
+ # fresh image; the jq branch is only defensive).
412
+ mkdir -p ~/.docker
413
+ if [ -f ~/.docker/config.json ]; then
414
+ jq '.cliPluginsExtraDirs += ["/opt/homebrew/lib/docker/cli-plugins"] | .cliPluginsExtraDirs |= unique' \
415
+ ~/.docker/config.json > /tmp/docker-config.json
416
+ mv /tmp/docker-config.json ~/.docker/config.json
417
+ else
418
+ cat > ~/.docker/config.json <<'DOCKER'
419
+ {
420
+ "cliPluginsExtraDirs": [
421
+ "/opt/homebrew/lib/docker/cli-plugins"
422
+ ]
423
+ }
424
+ DOCKER
425
+ fi
426
+
427
+ docker --version
428
+ docker compose version
429
+ docker buildx version
430
+ END
431
+ ]
432
+ }
433
+
434
+ # Final verification and cleanup
435
+ provisioner "shell" {
436
+ inline = [<<-END
437
+ set -e -x
438
+ source ~/.zprofile
439
+ brew cleanup
440
+
441
+ echo "===== Agent Sandbox image contents ====="
442
+ sw_vers
443
+ xcodebuild -version
444
+ brew --version | head -1
445
+ node --version
446
+ npm --version
447
+ nvm --version
448
+ python3 --version
449
+ pip3 --version
450
+ ruby --version
451
+ git --version
452
+ gh --version | head -1
453
+ code --version | head -1
454
+ subl --version
455
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version
456
+ "/Applications/Firefox.app/Contents/MacOS/firefox" --version
457
+ opencode --version
458
+ ocr --version | head -1
459
+ openchamber --version
460
+ defaults read "/Applications/OpenChamber.app/Contents/Info.plist" CFBundleShortVersionString
461
+ docker --version
462
+ docker compose version
463
+ docker buildx version
464
+ echo "========================================"
465
+ END
466
+ ]
467
+ }
468
+ }
@@ -0,0 +1,39 @@
1
+ # macOS 26 (Tahoe) + Xcode 26.4.1
2
+ #
3
+ # The default sandbox image. macos_version + xcode_version must point at a
4
+ # tag that exists in the Cirrus Labs base images on GHCR:
5
+ # https://github.com/orgs/cirruslabs/packages?tab=packages&q=macos-
6
+ macos_version = "tahoe"
7
+ xcode_version = "26.4.1"
8
+
9
+ # Node.js version installed via nvm and set as the default.
10
+ node_version = "26"
11
+
12
+ # Homebrew Python version (also used for the unversioned python/pip aliases).
13
+ python_version = "3.14"
14
+
15
+ # VM resources
16
+ # disk_size must be >= the Cirrus base image disk (140 GB): tart can only
17
+ # grow a disk, never shrink it.
18
+ disk_size = 160
19
+ cpu_count = 4
20
+ memory_gb = 8
21
+
22
+ # SSH credentials for provisioning (fixed in the Cirrus Labs base images).
23
+ ssh_username = "admin"
24
+ ssh_password = "admin"
25
+
26
+ # OpenChamber web UI password (host access: http://<vm-ip>:4000, see
27
+ # docs/macos.md). OpenChamber refuses to serve on the network without it.
28
+ openchamber_ui_password = "sandbox"
29
+
30
+ # TCP port the OpenChamber web UI listens on inside the guest (host access:
31
+ # http://<vm-ip>:4000). 4000 — not the Vite dev-server default 3000 — so
32
+ # frontend dev servers don't collide with it. Keep
33
+ # the CLI's SANDBOX_OPENCHAMBER_PORT default (lib/platform.ts) in sync.
34
+ openchamber_port = 4000
35
+
36
+ # Semantic version this image is published under (also the GHCR push tag,
37
+ # besides :latest). For every release: bump it, add a CHANGELOG.md entry,
38
+ # and create the mac-v<version> git tag (agent-dev-env tag <image>).
39
+ image_version = "1.6.0"