Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ljagiello avatar

Utmapp

  • 5 installs
  • Updated August 3, 2026
  • ljagiello/agent-skills

Helps with ai & agent building tasks.

About

utmapp is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.

  • utmapp
  • AI & Agent Building
  • AI-coding skill

Utmapp by the numbers

  • 5 all-time installs (skills.sh)
  • Ranked #13,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ljagiello/agent-skills --skill utmapp

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs5
Last updatedAugust 3, 2026
Repositoryljagiello/agent-skills

What it does

Helps with ai & agent building tasks.

Files

SKILL.mdMarkdownGitHub ↗

Using the UTM virtualization app

UTM is a macOS/iOS GUI for running virtual machines. On macOS it has two backends:

  • QEMU — full emulation of 30+ architectures (x86_64, ARM64, RISC-V, PPC, …) plus HVF acceleration when host and guest architectures match. Required for Windows, BSDs, classic OSes, and anything cross-architecture. Supports USB pass-through, snapshots, port forwarding, custom QEMU args, and full automation (input, exec, files, IP).
  • Apple Virtualization (Virtualization.framework) — native, very fast, but limited to macOS guests on Apple Silicon and modern Linux guests. No USB pass-through, no scripted input, no guest-agent file/exec.

A VM is stored as a .utm bundle (a directory). User VMs live in ~/Library/Containers/com.utmapp.UTM/Data/Documents/. Backups are just file copies of the bundle.

There are three ways to drive UTM from a script:

1. `utmctl` — bundled CLI at /Applications/UTM.app/Contents/MacOS/utmctl (also linked as utmctl in some installs). Best for shell automation. See references/utmctl.md. 2. AppleScript / JXA — full scripting dictionary in UTM.sdef. Needed for input automation, configuration edits, and creating VMs. See references/applescript.md. 3. Bundle / config edits — when UTM is closed you can read or rewrite config.plist inside a .utm bundle directly. See references/configuration.md.

For end-user workflows (installing Linux, Windows, macOS guests; networking; file sharing) see references/workflows.md. For known gotchas and performance tuning see references/troubleshooting.md.

Gotchas — read these before automating

  • No SSH / no headless. UTM scripting goes through the AppleScript bridge. utmctl and osascript only work inside a logged-in graphical session. From SSH you will get permission errors. Workarounds: run a launchd agent, use caffeinate, or use Screen Sharing first.
  • VM identifier is a name OR a UUID. Pass either; UTM resolves both. Names with spaces must be quoted: utmctl start "Ubuntu 24.04".
  • `delete` has no confirmation. Always check with utmctl list first.
  • `stop` defaults to `--force` (sends a stop request to the QEMU/VZ backend). Use --request to ask the guest OS to power down cleanly, or --kill only as a last resort.
  • Apple-backend VMs do not support `input keystroke`, `input mouse click`, `input scan code`, USB connect/disconnect, or QEMU guest-agent commands (exec, file pull, file push, ip-address). Detect the backend before calling these — see the recipe below. In particular, do not call `utmctl ip-address` on an Apple-backend VM even speculatively — it always fails with Operation not supported by the backend. Use ARP on bridge100 or mDNS (<host>.local) instead; see Finding a guest's IP.
  • `exec`, `file`, and `ip-address` need the QEMU guest agent. Install qemu-guest-agent in Linux (apt install qemu-guest-agent && systemctl enable --now qemu-guest-agent) or virtio-win Guest Tools on Windows. Without it these commands time out or return "no agent".
  • Never `utmctl clone` a macOS guest. utmctl clone is a bundle deep-copy: the duplicate keeps the original's AuxiliaryStorage and HardwareModel, so two VMs end up sharing one Apple machine identity. The clone may still boot, but iCloud / Apple ID / FaceTime / activation will misbehave on one or both copies, and AuxiliaryStorage cannot be regenerated without a fresh restore. For macOS guests use AppleScript duplicate instead — it regenerates the auxiliary blob. Linux/Windows/QEMU guests are safe to utmctl clone. See the macOS-clone recipe below and references/workflows.md.
  • Always name clones/duplicates with a unique identifier. Append a timestamp or date — macOS-test-2026-05-08, ubuntu-base-clone-20260508-1530, <base>-<purpose>-<YYYYMMDD> — never just -test or -clone. UTM allows duplicate names, so two <base> Clone VMs will silently collide in scripts that resolve by name; a unique identifier also makes it obvious which copy to delete later.
  • `OSStatus error -2700` from utmctl is overloaded — disambiguate by command and trailing line, then check `utmctl status`. -2700 is the generic AppleScript "event failed" code. UTM emits it for two very different conditions, distinguished by the trailing message:
  • Operation not available. — usually cosmetic on utmctl start against an Apple-backend VM (especially a freshly duplicated macOS guest). The scripting bridge races its own state check against data.run(), so it raises after the VM has already begun starting. The VM still transitions to started. Verify with utmctl status "<vm>" (or utmctl list) — if status is starting or started, treat the error as noise and continue. Do not retry the start or recreate the VM.
  • Operation not supported by the backend.real failure. The Apple Virtualization backend genuinely cannot service the request (e.g. utmctl exec, utmctl file pull/push, utmctl ip-address, or --disposable start). No retry will help; use the documented alternative (SSH, ARP/mDNS, QEMU backend).

Treat utmctl status — not utmctl's exit code or stderr text — as the source of truth for whether the requested transition happened.

  • Bridged networking + macOS Sequoia require granting UTM the "Local Network" privacy permission, otherwise the guest gets no IP.
  • JIT on iOS is a separate world — see references/troubleshooting.md (UTM SE, AltStore, jailbreak workarounds). All scripting in this skill is macOS only.

Quick start: utmctl

Use utmctl for nearly all routine operations. The binary lives inside the app bundle:

# Make it accessible (one-time; needs sudo because /usr/local/bin is root-owned)
sudo ln -sf /Applications/UTM.app/Contents/MacOS/utmctl /usr/local/bin/utmctl

# List all VMs (UUID, status, name)
utmctl list

# Start / suspend / stop
utmctl start "Ubuntu"
utmctl suspend "Ubuntu" --save-state
utmctl stop "Ubuntu" --request    # ask guest to power off
utmctl stop "Ubuntu"               # default = force stop backend
utmctl stop "Ubuntu" --kill        # last resort

# Status of one VM
utmctl status "Ubuntu"             # → stopped | starting | started | paused | …

# Disposable / recovery boot
utmctl start "Ubuntu" --disposable # discard all changes on stop
utmctl start "macOS Sonoma" --recovery

# Clone, delete, version
utmctl clone "Ubuntu" --name "Ubuntu-test-$(date +%Y%m%d)"  # Linux/Windows/QEMU only; always tag with a date/ID
utmctl delete "Ubuntu-test-20260508"   # NO confirmation
utmctl version

Guest-agent operations (QEMU backend, agent installed):

# Run a command, capture stdout/stderr/exit code
utmctl exec "Ubuntu" -- /bin/bash -c "uname -a"
utmctl exec "Ubuntu" --env LANG=C -- ls /etc

# Push host stdin to guest file, pull guest file to host stdout
echo "hello" | utmctl file push "Ubuntu" /tmp/hello.txt
utmctl file pull "Ubuntu" /var/log/syslog > syslog.txt

# Get guest IPs (IPv4 first, then IPv6)
utmctl ip-address "Ubuntu"

USB pass-through (QEMU backend):

utmctl usb list                                 # discover devices
utmctl usb connect "Windows" 046D:C016          # by VID:PID (hex)
utmctl usb connect "Windows" 4                  # by location id
utmctl usb disconnect 4

utmctl --help and utmctl <command> --help print full usage. Full reference with every flag, exit code semantics, and edge cases is in references/utmctl.md.

utmctl does not cover VM creation, configuration edits, keystroke/mouse injection, or registry edits — those require AppleScript. See the next section.

Choosing utmctl vs AppleScript

NeedUse
start/stop/suspend/list/status/deleteutmctl
Clone a Linux/Windows/QEMU guestutmctl clone
Clone a macOS guestAppleScript duplicate (see recipe below) — utmctl clone shares the machine identity
exec, file pull/push, ip-address, USB connect/disconnectutmctl
Send keystrokes / text / mouse clicks into the guestAppleScript (input keystroke, input mouse click, input scan code)
Create a new VM from scratchAppleScript (make new virtual machine)
Read or update VM configuration (RAM, CPU, drives, network, ports)AppleScript (configuration of …, update configuration)
Wait for a VM to reach a state, build retry loopsshell + utmctl status polling, OR AppleScript
Rebind shared host directoriesAppleScript (update registry — replaces ALL shares; see references/applescript.md#registry-suite)
Mount/unmount a removable ISO at runtimeNot exposed via scripting — requires the GUI

If you need both kinds of operations in one script, drive everything from osascript -l JavaScript (JXA) — it is the only place where input automation, configuration, and lifecycle commands all coexist.

Backend-aware automation pattern

Many commands are silently a no-op or error on the Apple backend. Always branch:

backend=$(osascript -e 'tell application "UTM" to get backend of virtual machine named "MyVM" as text')
case "$backend" in
  qemu)  utmctl exec "MyVM" -- /bin/sh -c 'whoami' ;;
  apple) echo "Apple backend: skipping guest-agent exec" ;;
  *)     echo "VM unavailable" >&2; exit 1 ;;
esac

Or in JXA (osascript -l JavaScript):

const utm = Application("UTM");
const vm = utm.virtualMachines.byName("MyVM");
if (vm.backend() === "qemu") {
    // safe to call input/exec/USB
}

Duplicating a macOS guest

utmctl clone is a bundle deep-copy. For macOS guests on the Apple backend that breaks Apple-services identity (see gotcha above). Use AppleScript duplicate instead — it tells UTM to regenerate the auxiliary identity blob. Always tag the new name with a timestamp / date so you can tell duplicates apart and avoid name collisions:

NEW_NAME="macOS-test-$(date +%Y%m%d-%H%M)"
osascript -e "tell application \"UTM\" to duplicate virtual machine named \"macOS-base\" with properties {configuration:{name:\"$NEW_NAME\"}}"

Or in JXA:

osascript -l JavaScript -e "
const utm = Application('UTM');
const stamp = new Date().toISOString().slice(0,10);
utm.duplicate(utm.virtualMachines.byName('macOS-base'), { withProperties: { configuration: { name: 'macOS-test-' + stamp } } });
"

Before duplicating, confirm the source VM is on the Apple backend and is fully shut down (utmctl status "macOS-base"stopped). Even with duplicate, Apple's macOS licence only permits two macOS guests running concurrently per host. See references/applescript.md for the full duplicate signature.

Wait-until-ready recipe

utmctl start returns once the backend has launched, not when the guest is booted. It may also print a cosmetic OSStatus error -2700 / Operation not available on Apple-backend VMs (see Gotchas) — check utmctl status rather than the exit code. To wait until the guest is reachable, poll either status or — better — the guest agent:

utmctl start "Ubuntu" || true   # ignore cosmetic -2700; verify via status below
for i in $(seq 1 60); do
    if utmctl ip-address "Ubuntu" 2>/dev/null | grep -qE '^[0-9]+\.'; then
        echo "Guest up after ${i}s"; break
    fi
    sleep 2
done

For Apple-backend VMs there is no guest agent, so utmctl ip-address is unusable — see the next section for the right approach.

Finding a guest's IP

Pick the path by backend — do not just call utmctl ip-address and hope:

  • QEMU backend with `qemu-guest-agent` installedutmctl ip-address "<vm>". This is the only path that returns the IP directly. Returns IPv4 first, then IPv6, one per line.
  • Apple backend (always) and QEMU backend without the guest agentutmctl ip-address will fail with Operation not supported by the backend (Apple) or time out with "no agent" (QEMU). Do not run it. Use ARP or mDNS instead.

Detect the backend first:

backend=$(osascript -e 'tell application "UTM" to get backend of virtual machine named "MyVM" as text')

UTM's default Shared (NAT) network on macOS lives on host interface bridge100 with subnet 192.168.64.0/24. Both Apple-backend and QEMU-backend "Shared" guests appear here; bridged-mode guests appear on the host's primary LAN instead.

Scope the ARP lookup to bridge100 so you get UTM guests only — a bare arp -a returns every neighbor on every interface:

# All running UTM Shared-network guests, by IP and MAC
arp -a -n -i bridge100

# Pick the only guest IP (skip the bridge's own .1 gateway and incomplete entries)
arp -a -n -i bridge100 \
  | awk '$2 != "(192.168.64.1)" && $4 != "incomplete" && $2 ~ /^\(/ { gsub(/[()]/, "", $2); print $2 }'

If the guest advertises mDNS (most Linux distros and macOS guests do by default):

# Resolve a known hostname
dscacheutil -q host -a name myhost.local

# Browse all SSH-advertising guests on the local link
dns-sd -B _ssh._tcp local.

Hovering over the network icon in UTM's status bar shows the IP for the focused VM and is the simplest fallback when scripting is overkill.

Common shapes of work

  • "Run command X in VM Y, return output"utmctl exec. See references/utmctl.md#exec.
  • "Spin up a fresh VM from a template"utmctl clone --name, then utmctl start --disposable for ephemeral runs. macOS guests only: use AppleScript duplicate instead of utmctl clone — see the macOS-clone recipe above.
  • "Type something into the login screen" → AppleScript input keystroke / input scan code. See references/applescript.md.
  • "Change VM configuration" → AppleScript update configuration (VM must be stopped).
  • "Rebind a shared host directory" → AppleScript update registry (replaces every shared dir at once; this command does NOT cover removable-media swaps — those require the GUI).
  • "Backup a VM" → stop it, cp -R "MyVM.utm" /backup/. Nothing else is required.
  • "Install Ubuntu / Windows / macOS guest" → see references/workflows.md.

Map of the references directory

FileWhen to read it
references/utmctl.mdAuthoring or debugging shell automation, mapping flags, exit codes
references/applescript.mdSending input, creating VMs, editing configuration, JXA examples
references/configuration.mdHand-editing .utm/config.plist while UTM is closed; understanding bundle layout
references/workflows.mdWalking a user through installing Linux, Windows ARM, Windows x86, macOS, or wiring up file sharing and networking
references/troubleshooting.md"Why doesn't this work?" — JIT/iOS, performance, network, snapshots, GPU

Read each on demand. Do not preload them.

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.