Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill vm4a-cli명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | vm4a-cli |
| description | | Use when this capability is needed. |
VM4A ships a single binary vm4a that creates and runs Apple
Virtualization.framework VMs on Apple Silicon from the command line. Each
VM is a directory ("bundle") containing config.json, state.json, a disk
image, and platform identity files. One binary handles the lifecycle, OCI
distribution, and agent channel.
Before running anything:
Apple Silicon + macOS 13+. x86 Macs are not supported.
CLI must be codesigned with the CLI entitlements, otherwise run
fails silently:
swift build
codesign --force --sign - \
--entitlements Sources/VM4ACLI/VM4ACLI.entitlements \
./.build/debug/vm4a
Bridged networking and Rosetta share need the CLI entitlements file specifically — not the App one.
macOS 26 (Tahoe) gotcha: com.apple.vm.networking is a restricted
entitlement. Ad-hoc signing (--sign -) a binary that carries it makes
AMFI SIGKILL the process at launch — every vm4a command dies with no
output (exit 137). If that happens, re-sign with an entitlements file that
drops com.apple.vm.networking (NAT-only; keeps com.apple.security.virtualization):
cp Sources/VM4ACLI/VM4ACLI.entitlements /tmp/nat.entitlements
/usr/libexec/PlistBuddy -c "Delete :com.apple.vm.networking" /tmp/nat.entitlements
codesign --force --sign - --entitlements /tmp/nat.entitlements ./.build/debug/vm4a
./.build/debug/vm4a --version # must print, not get killed
Ad-hoc NAT covers spawn/exec/cp/run-code/expose-port/OCI — but NOT bridged
networking and NOT snapshots.
Snapshots on macOS 26 (verified): saveMachineStateTo is gated on
hardened runtime + a real signing identity. Ad-hoc signing fails with
VZErrorSave "permission denied" (so snapshot save, run --save-on-stop,
and reset all fail). validateSaveRestoreSupport() is YES — it's purely a
signing issue. Re-sign with --options runtime + an Apple Development /
Developer ID identity:
codesign --force --options runtime \
--sign "Apple Development: YOUR NAME (TEAMID)" \
--entitlements /tmp/nat.entitlements ./.build/debug/vm4a
Bridged mode additionally needs an identity provisioned for the managed
com.apple.vm.networking entitlement (ad-hoc cannot grant it).
For bridged mode: vm4a network list should print at least one
interface. If empty, re-check codesigning.
Agent-first primitives (prefer these for any agent-driven flow):
| Intent | Command |
|---|---|
| One-shot create+start, wait for SSH | vm4a spawn NAME --from ghcr.io/you/img:tag --wait-ssh --output json |
| Run a command in the guest, get JSON | vm4a exec /path/to/bundle --output json -- python3 -c 'print(1+1)' |
| Copy a file host→guest | vm4a cp /path/to/bundle ./local.txt :/work/remote.txt |
| Copy a file guest→host | vm4a cp /path/to/bundle :/var/log/syslog ./syslog.txt |
| Fork a bundle, auto-start, wait for SSH | vm4a fork SRC DST --auto-start --from-snapshot clean.vzstate --wait-ssh |
| Reset to a saved snapshot for retry | vm4a reset /path/to/bundle --from clean.vzstate --wait-ip |
| Save a running VM as a named snapshot (stops it) | vm4a snapshot save /path/to/bundle clean |
| Restore a VM to a named snapshot (~1s) | vm4a restore /path/to/bundle clean (alias for vm4a snapshot restore) |
| List / delete named snapshots | vm4a snapshot list /path/to/bundle [--output json], vm4a snapshot rm /path/to/bundle clean |
Classic lifecycle:
| Intent | Command |
|---|---|
| Create a Linux VM bundle | vm4a create NAME [--image ISO.iso] [--network bridged --bridged-interface en0] [--rosetta] |
| Install a macOS VM from IPSW (10–20 min, drives VZMacOSInstaller) | vm4a create NAME --os macOS --image macos-15.ipsw --cpu 4 --memory-gb 8 --disk-gb 80 |
| List bundles in a directory | vm4a list --storage /tmp/vm4a [--output json] |
| Start a VM in the background | vm4a run /path/to/bundle |
| Start in foreground (logs to stdout) | vm4a run /path/to/bundle --foreground |
| Boot macOS recovery | vm4a run /path/to/bundle --recovery |
| Restore from a VZ snapshot (macOS 14+) | vm4a run /path/to/bundle --restore state.vzstate |
| Save state on clean shutdown (macOS 14+) | vm4a run /path/to/bundle --save-on-stop state.vzstate |
| Stop a VM (SIGTERM, then SIGKILL if stuck) | vm4a stop /path/to/bundle --timeout 20 |
| Clone a bundle (APFS clonefile when possible) | vm4a clone SRC DST |
| Show Linux ARM64 ISO catalog | vm4a image list |
| Show host bridged interfaces | vm4a network list |
| Look up VM's NAT IP | vm4a ip /path/to/bundle [--output json] |
| SSH into running VM (NAT) | vm4a ssh /path/to/bundle --user root |
| Push bundle to OCI registry | vm4a push /path/to/bundle ghcr.io/you/name:tag |
| Pull bundle from OCI registry | vm4a pull ghcr.io/you/name:tag --storage /tmp/vm4a |
| Check guest agent heartbeat | vm4a agent status /path/to/bundle |
| Ping guest agent | vm4a agent ping /path/to/bundle |
| Run as MCP server (stdio JSON-RPC) | vm4a mcp |
| Run HTTP API server on localhost |
# 1. First-time bootstrap: pull, start (arm save-on-stop), wait for SSH.
vm4a spawn dev --from ghcr.io/yourorg/python-dev-arm64:latest \
--storage /tmp/vm4a \
--save-on-stop /tmp/vm4a/dev/clean.vzstate \
--wait-ssh --output json
# 2. Install whatever, then stop — VM saves state on shutdown.
vm4a exec /tmp/vm4a/dev -- bash -lc "apt-get install -y ripgrep"
vm4a stop /tmp/vm4a/dev
# 3. Per-task: fork the golden bundle, push code, run, parse JSON.
vm4a fork /tmp/vm4a/dev /tmp/vm4a/task-$JOB_ID \
--auto-start --from-snapshot /tmp/vm4a/dev/clean.vzstate --wait-ssh
vm4a cp /tmp/vm4a/task-$JOB_ID ./step.py :/work/step.py
vm4a exec /tmp/vm4a/task-$JOB_ID --output json --timeout 120 \
-- python3 /work/step.py
# 4. Bad task state? Reset back to the snapshot in <1s.
vm4a reset /tmp/vm4a/task-$JOB_ID --from /tmp/vm4a/dev/clean.vzstate --wait-ip
ISO=~/Downloads/ubuntu-24.04-live-server-arm64.iso
vm4a create demo --storage /tmp/vm4a --image "$ISO" \
--cpu 4 --memory-gb 8 --disk-gb 64
vm4a run /tmp/vm4a/demo
vm4a ip /tmp/vm4a/demo # needs VM to have booted + DHCP'd
vm4a ssh /tmp/vm4a/demo # after cloud-init / user setup
vm4a stop /tmp/vm4a/demo
# Push (requires a PAT with write:packages)
export VM4A_REGISTRY_USER=youruser
export VM4A_REGISTRY_PASSWORD=ghp_xxx
vm4a push /tmp/vm4a/base-ubuntu ghcr.io/youruser/base-ubuntu:24.04
# Pull on another machine (anonymous if package is public)
vm4a pull ghcr.io/youruser/base-ubuntu:24.04 --storage /tmp/vm4a
vm4a run /tmp/vm4a/base-ubuntu
vm4a clone /tmp/vm4a/golden /tmp/vm4a/job-$CI_JOB_ID
vm4a run /tmp/vm4a/job-$CI_JOB_ID
trap "vm4a stop /tmp/vm4a/job-$CI_JOB_ID && rm -rf /tmp/vm4a/job-$CI_JOB_ID" EXIT
clone uses APFS clonefile(2) on the same volume so it's O(directory
entries), not O(disk image size).
/var/db/dhcpd_leases; users must pass --host <ip> to
vm4a ssh or use their router's DHCP view.--rosetta is Linux only and requires
softwareupdate --install-rosetta --agree-to-license before first run.
The CLI warns but doesn't block.vm4a create --os macOS --image foo.ipsw finishes (10–20 min), the
VM lands at Apple's Setup Assistant on first boot. Apple does not
expose a scriptable skip path, so the user needs to click through it
once in VM4A.app: pick region/keyboard, skip Apple ID, create a
user account, then System Settings → General → Sharing → Remote
Login: ON. After that single click-through, every CLI / MCP / HTTP
/ SDK operation works on the macOS bundle exactly like on Linux,
and pulling a published macOS bundle from GHCR skips Setup Assistant
entirely (the user account + Remote Login are baked in).schemaVersion: 1. Old bundles without
the field still load — tolerant decoding treats missing as 1. When
adding new fields, make them optional in Core.swift decoding.vm4a stop requires a running pid. If vm4a list shows stopped
but stale files exist, just re-run vm4a run; the CLI cleans stale
PID files on the next list.--output json is available on create, list, ip, agent status,
and on every v2 primitive (spawn, exec, cp, fork, reset).
Output is one JSON object/array per command invocation (not JSONL).vm4a exec returns JSON with exit_code, stdout, stderr,
duration_ms, timed_out. The exit code is also the process exit code
(so if vm4a exec ... ; then ... works in shell). With --timeout and a
timed-out command, the agent gets timed_out: true and a non-zero exit.1 VM4AError.message Generic failure (legacy path)
2 VM4AError.notFound Bundle or file missing
3 VM4AError.alreadyExists Destination already exists
4 VM4AError.invalidState VM running when it should be stopped (or vice versa)
5 VM4AError.hostUnsupported macOS version / hardware capability missing
5 VM4AError.rosettaNotInstalled
vm4a command exits with code 137 / no output (macOS 26): AMFI
killed the ad-hoc-signed binary because of the restricted
com.apple.vm.networking entitlement. Re-sign NAT-only (see Prerequisites #2).run silently exits (but the binary itself runs): check .vm4a-run.log in
the bundle root.com.apple.vm.networking. Re-run the codesign command from the top.push returns HTTP 401: set VM4A_REGISTRY_USER / VM4A_REGISTRY_PASSWORD
(a PAT for GHCR, a Docker token for Docker Hub).ssh hangs: VM hasn't DHCP'd yet (vm4a ip returns empty). Wait 10-30s
after vm4a run for first-boot initialization.snapshot save / --save-on-stop / reset fail with VZErrorSave "permission denied" (macOS 26): the binary is ad-hoc signed. VZ state save
needs hardened runtime + a real identity — re-sign with --options runtime
and an Apple Development / Developer ID identity (see Prerequisites #2).Redirect to the GUI app for:
Most recent:
Named snapshots:
snapshot save NAME / restore NAME / snapshot list / snapshot rm, plus
top-level vm4a restore — named .vzstate snapshots stored inside the bundle,
no manual path juggling. Requires hardened-runtime + real-identity signing on
macOS 26 (see Prerequisites #2).v2.0 P1 — MCP server:
mcp (stdio JSON-RPC 2.0 server; register in .mcp.json to expose
every primitive as an MCP tool to Claude Code / Cursor / Cline)v2.0 P0 — agent-first primitives:
spawn (one-shot create+start with optional --from <oci-ref>, --wait-ssh)exec (SSH-driven command runner with structured JSON return)cp (SCP host ↔ guest with : prefix convention)fork (clone + re-identify + optional auto-start with snapshot restore)reset (stop + restart from snapshot for retry loops)For any agent / scripted flow, prefer these over the manual
create → run → ssh chain. They handle PID management,
snapshot wiring, and JSON output by default.
Earlier additions (still relevant):
push / pull (OCI registry support, tart-style)network list, image list (host introspection)ip, ssh (NAT convenience)agent status, agent ping (guest-agent channel, scaffold)create --rosetta, create --bridged-interfacerun --restore / run --save-on-stop (macOS 14+ snapshots)Source: everettjf/vm4a — distributed by TomeVault.
vm4a serve --port 7777 |
| Tag operations into a session for replay/inspection | vm4a exec ... --session run-42 |
| List/show recorded sessions | vm4a session list, vm4a session show <id> |
| Define a per-task spawn template | vm4a pool create py --base /path/to/golden --snapshot clean.vzstate |
| Mint a fresh task VM from a pool | vm4a pool spawn py --wait-ssh |
vm4a cp uses : to mark guest pathshost:./local.txt :/work/file.txtvm4a fork re-randomises MachineIdentifier AND the NIC MAC automatically.
Don't hand-roll a clone+identity flow — fork is what clone should have been
for agent loops, with optional --auto-start and --from-snapshot. (Re-randomising
the MAC is what stops parallel forks from colliding on the NAT lease table.)vm4a ip matches the lease by the bundle's persisted NIC MAC, not by
hostname. New bundles store a fixed MAC in config.json; if no lease matches
it yet, ip returns empty / exit 1 (it no longer dumps the whole lease table).
Legacy bundles without a stored MAC still fall back to hostname matching.vm4a snapshot save/restore/list/rm (+ top-level vm4a restore) are the
simple path — named .vzstate snapshots stored in <bundle>/.vm4a-snapshots/,
no path juggling. save captures the running VM then stops it (it signals the
worker via SIGUSR1); restore reboots to that state in ~1s. Prefer these over
hand-managing run --save-on-stop / run --restore / reset paths. Needs
the hardened-runtime + real-identity signing (see Prerequisites #2).vm4a exec parses its options in any position (the command goes after
--): both vm4a exec /b --output json -- cmd and vm4a exec --output json /b -- cmd
work. Same for cluster exec and ssh extra args.vm4a reset requires a .vzstate file, which means macOS 14+. On
older hosts the agent has to fall back to clone + reinstall.ping is implemented. Don't promise
clipboard/shutdown/script execution yet.