Generic window capture helper for agents, automation tools, and evidence pipelines — macOS (ScreenCaptureKit) and Linux/X11 (XComposite + ffmpeg).
On macOS, capture-helper is a small Swift CLI built on ScreenCaptureKit: it discovers windows, captures exact window targets, streams H.264 Annex B bytes, and records MP4 evidence with a native AVFoundation writer (no ffmpeg). On Linux it reproduces the same CLI/protocol over X11 using an XComposite grabber plus ffmpeg — see Linux support.
Early standalone extraction from Farmslot's internal tools/capture-helper.
The CLI is intentionally generic. Product-specific concepts such as Farmslot slots, iOS simulator aliases, MetaMask runners, or recipe semantics belong in the caller.
- macOS 13.0+ (Apple Silicon or Intel)
- Screen Recording permission for the terminal or parent app
- Optional:
ffmpegfor external workflows that still want it; macOSrecordandrecord --framedsession snapshots are native. - Swift/Xcode is only required when building from source — the published npm package and Homebrew formula ship a prebuilt universal binary.
On Linux the same CLI/protocol is implemented by a Node backend that drives a small
C grabber (XComposite per-window capture) and ffmpeg for H.264 encoding. The
capture-helper command auto-dispatches to it on Linux; commands, flags, the framed
stream protocol, and JSON events are identical to macOS (see docs/protocol.md).
Requirements:
-
X11 (Xorg) session. GNOME-on-Wayland per-window capture is portal-gated and not automatable — log into an Xorg session (e.g. "Ubuntu on Xorg", or set
WaylandEnable=falsein/etc/gdm3/custom.conf). A graphical desktop must be logged in (the GDM greeter has no app windows; use autologin for headless nodes). -
Captured apps must be X11 clients (Wayland-native surfaces are invisible to the X path). Force X11 where needed:
GDK_BACKEND=x11,QT_QPA_PLATFORM=xcb, Electron--ozone-platform=x11. -
Build/runtime deps (
ffmpegis required on Linux):sudo apt install -y gcc ffmpeg libx11-dev libxcomposite-dev libxdamage-dev libxfixes-dev libxext-dev # or run the helper: bash scripts/setup-linux-node.sh
Hardware H.264 encoding is opt-in via --encoder h264_nvenc (or
CAPTURE_HELPER_ENCODER=h264_nvenc); the default libx264 works everywhere.
Verify readiness with capture-helper doctor --json. On Linux, id values are X11
window ids (XIDs).
Use with npx without installing globally:
npx -y @siteed/capture-helper@latest doctor --json
npx -y @siteed/capture-helper@latest list --jsonInstall globally with npm (primary path used by Farmslot and MetaMask farm installers):
npm install -g @siteed/capture-helper
capture-helper doctorInstall with Homebrew (auto-taps deeeed/tap; no separate tap step):
brew install deeeed/tap/capture-helper
capture-helper doctordoctor prints human-readable output by default. Use doctor --json when a script or
installer needs machine-readable output (Farmslot and farm installers pass --json).
Download the native release binary directly:
curl -L https://github.com/deeeed/capture-helper/releases/latest/download/capture-helper-darwin-universal \
-o /usr/local/bin/capture-helper
chmod +x /usr/local/bin/capture-helper
xattr -d com.apple.quarantine /usr/local/bin/capture-helper 2>/dev/null || trueIf macOS Gatekeeper blocks a curl-downloaded binary, prefer npm install -g or
brew install deeeed/tap/capture-helper — those paths do not attach quarantine. For a
manual download, remove quarantine with xattr -d (above) or approve once in System
Settings → Privacy & Security.
swift build -c release
# or
npm run build:nativeThe npm build script copies the release binary to:
native/capture-helper
When installed as an npm package, postinstall verifies the bundled native binary
(smoke test + SHA256 checksum). On macOS, if the prebuilt binary is missing or broken
and Swift is available, it rebuilds from source; otherwise install fails loudly with
the next command to run (brew install deeeed/tap/capture-helper or retry npm). On
Linux it compiles the X11 grabber (native/x11-grabber) via gcc when possible. Set
SITEED_CAPTURE_HELPER_SKIP_POSTINSTALL=1 to skip postinstall.
# Human default: list likely capturable windows
capture-helper
capture-helper -l
# Version / provenance
capture-helper version
capture-helper --version
# Environment readiness and permissions diagnostics (human-readable by default)
capture-helper doctor
capture-helper doctor --json
# includes stable codes like screen_recording_denied, window_server_unavailable, ffmpeg_missing
# Request/open macOS Screen Recording permissions where possible
capture-helper permissions
capture-helper doctor --open-permissions --json
# List windows as a machine-readable JSON object
capture-helper list --json
# Human-readable table
capture-helper list --human
capture-helper -l
capture-helper list -h
capture-helper list --on-screen --capturable --human
capture-helper list --all --human
# Legacy JSON-lines listing
capture-helper --list-windows
capture-helper list --json-lines
# Capture a specific target as raw H.264 Annex B
capture-helper capture --window-id 12345 > /tmp/capture.h264
capture-helper capture --pid 12345 > /tmp/capture.h264
capture-helper capture --app-name Simulator --window-name "mm-1" > /tmp/capture.h264
# Legacy capture syntax remains supported
capture-helper --window-name "Simulator" > /tmp/capture.h264
# Framed multi-window stream with stdin control
capture-helper stream --framed --window-id 12345 > /tmp/windows.h264
# Record MP4 evidence (macOS: native AVFoundation, no ffmpeg; Linux: ffmpeg)
capture-helper record --window-id 12345 --duration 5 --output evidence.mp4 --open
# Record MP4 plus PNG proof frames from the same macOS native recording stream
{ sleep 1; echo "snapshot step-1.png"; echo stop; } | \
capture-helper record --framed --window-id 12345 --output evidence.mp4MP4 recording defaults to a 1440-pixel maximum dimension at up to 30 fps. Live
capture and streaming retain their lower-bandwidth 720-pixel and 15 fps defaults.
Override either mode with --max-size and --max-fps.
resolve lets agents debug target selection before starting video capture:
capture-helper resolve --app-name "Google Chrome" --window-name "MetaMask"It returns the selected window, selector type, and all candidates considered for that selector.
snapshot captures a one-frame PNG using the same target selectors:
capture-helper snapshot --window-id 12345 --output screenshot.pngPrefer selectors in this order:
--window-idfromcapture-helper list --jsonfor exact capture.--pidwhen the caller owns the process tree and wants the largest suitable window.--app-name+--window-namefor human-friendly fallback matching.--window-namealone only for ad hoc use.
This package exposes a Node wrapper so JavaScript-based agents can call the native binary through a normal bin entry:
node bin/capture-helper.js doctor --jsonThe wrapper resolves the binary in this order:
SITEED_CAPTURE_HELPER_BINnative/capture-helper.build/release/capture-helper/opt/homebrew/bin/capture-helper/usr/local/bin/capture-helper
- raw capture stdout: H.264 Annex B byte stream
- 4-byte start codes:
00 00 00 01 - SPS/PPS emitted before keyframes
- baseline profile, no B-frames
- 4-byte start codes:
doctor: human-readable by default;--jsonfor machine-readable outputlist/version: JSON on stdout by default- streaming/capture diagnostics: JSON lines on stderr
- command failures: JSON error lines on stderr with stable
codevalues such astarget_requiredandwindow_not_found - signal handling: the npm wrapper forwards
SIGINT,SIGTERM, andSIGHUP; a repeated signal forces termination if cleanup stalls;record --durationstops automatically
See docs/protocol.md for the framed stream and event contract.
Grant Screen Recording permission to the terminal app, IDE, or agent host that launches the helper:
System Settings → Privacy & Security → Screen & System Audio Recording
Open that pane from the CLI with:
capture-helper doctor --request-permission --open-permissionsmacOS grants Screen Recording to the app that launched capture-helper. If a desktop UI or agent host is failing but Terminal passes doctor, enable the desktop UI or agent host in System Settings, then restart that app.
After granting permission, restart the launching app. Use this to check readiness:
capture-helper doctor --jsonIf you see Code=-3801 / screen_recording_denied, especially over SSH, see docs/troubleshooting.md.
Keep this tool generic:
- good: window IDs, PIDs, app names, window titles, capture formats, diagnostics
- bad: Farmslot slots, project resources, simulator naming conventions, MetaMask-specific selectors
Higher-level tools should resolve their domain objects to a concrete macOS window target, then call capture-helper.
swift build -c release
swift test # includes subprocess CLI/error-shape tests
npm run build:native
npm run doctor
npm pack --dry-runSee docs/release.md.
MIT