Screenshot, record, and OCR native macOS windows, displays, and screen regions from the command line.
Its defining capability: occluded windows capture correctly. A window sitting entirely behind another renders complete, with no raising, focusing, or rearranging. That is what makes it usable from a script or an agent — capture never disturbs what you are doing.
Built on ScreenCaptureKit. Requires macOS 15+.
swift build -c release
make install # copies to ~/local/bin/
# or place it anywhere on PATH yourself:
cp .build/release/macosrec /usr/local/bin/Screen Recording permission is required. Grant it under System Settings → Privacy & Security → Screen & System Audio Recording. Without it, commands exit 4 with an actionable message rather than returning empty results.
macosrec list [--hidden] [--app NAME]
macosrec shot <selector> [-o PATH] [--out-dir DIR]
macosrec record <selector> [--duration SECS] [--fps N] [--format mov|gif] [-o PATH]
macosrec stop [--abort]
macosrec ocr <selector | --input FILE> [--clipboard] [--interactive]
macosrec transcribe --input FILE [--locale LOCALE] # not yet implemented
macosrec version
Every subcommand accepts --json / --no-json and --quiet. macosrec --version and macosrec version both report the version. Run macosrec help <subcommand> for detail.
Exactly one per invocation:
| Selector | Meaning |
|---|---|
--window <id> |
Exact window ID. Unambiguous; preferred for scripting |
--app <name> |
Case-insensitive app name; prefers on-screen windows |
--title-match <substring> |
Narrows --app when several windows remain |
--display <index> |
Whole display, zero-based index from list |
--region <x,y,w,h> |
Screen-coordinate rectangle |
A bare numeric positional argument is a window ID; other text is an app name.
# List windows and displays
macosrec list
macosrec list --hidden
# Screenshot
macosrec shot --app Finder
macosrec shot --window 15251 -o /tmp/window.png
macosrec shot --display 0 -o /tmp/screen.png
macosrec shot --region 100,100,800,600 -o /tmp/region.png
# Record for a fixed duration, then exit on its own
macosrec record --app Finder --duration 10 -o /tmp/clip.mov
macosrec record --app Finder --duration 3 --fps 10 --format gif -o /tmp/clip.gif
# Open-ended recording, stopped from another invocation.
# stop reports the finalized file, so the destination need not be chosen up front.
(macosrec record --window 15251 --out-dir /tmp > /tmp/rec.json 2>&1 &)
macosrec stop # finalize; prints the path (JSON carries path, bytes, sha256)
macosrec stop --abort # discard instead
# OCR
macosrec ocr --app Ghostty
macosrec ocr --window 15251 --clipboard
macosrec ocr --input /tmp/window.png
macosrec ocr --interactive # select a region by handFiles default to ~/Pictures/macosrec/, with precedence -o → --out-dir → MACOSREC_OUT_DIR →
that default. Generated names look like 2026-07-31T00-43-10Z-finder-kloccis1.png: UTC timestamp,
lowercase slug, length-capped, and never containing a colon or a space.
Pixel dimensions account for the display's backing scale factor, so a 1280×1050-point window on a Retina panel yields a 2560×2100 image.
stdout is JSON automatically when it is not a TTY, so a piped invocation needs no flag:
$ macosrec shot --app Finder | jq -r .path
/Users/you/Pictures/macosrec/2026-07-31T00-43-10Z-finder.pngSuccess carries path, width, height, bytes, sha256, and target; stop reports path,
bytes, and sha256 for the finalized recording. Failure carries error.code, error.kind,
error.message, and error.candidates where relevant. An explicit -o into a missing directory
creates the parent, matching --out-dir.
Branch on the exit code rather than parsing messages:
| Exit | Meaning |
|---|---|
| 0 | Success |
| 1 | Runtime failure |
| 2 | Ambiguous target — see error.candidates, or add --title-match |
| 3 | Target not found — window IDs are volatile, re-run list |
| 4 | Screen Recording / Microphone permission denied |
| 5 | Recording-state conflict |
| 64 | Usage error |
- Captures are refused while a recording is active (exit 5). Any ScreenCaptureKit call from
another process truncates an in-flight recording, so refusing is deliberate — previously the
screenshot succeeded and the recording was silently destroyed.
ocr --input <file>touches no capture API and keeps working. - Window IDs are volatile. List immediately before capturing.
- Tabbed apps report a window per tab, sometimes more, and only the active tab is
isOnScreen. Inactive tabs are still capturable. - Windows orphaned by a display disconnect are filtered out. macOS retains surfaces for windows that lived on a removed display; they cannot be captured and are dropped from listings.
- GIF is converted after recording ends, so it adds wall-clock time proportional to frame count
and pixel area — roughly 19s for 5s of a full Retina display at 30fps. Prefer a window target and
modest
--fps. transcribeis present in the interface but not implemented yet; it exits 1 with a structurednot-implementederror.
Recorded GIFs can get large. gifsicle compresses them well:
gifsicle -i /tmp/clip.gif -O3 --colors 64 -o /tmp/clip-small.gifmacosrec's primary consumer is an autonomous agent: JSON when stdout is not a TTY, a stable exit-code contract, and bounded operations that fail fast rather than hang.
- skills/macosrec/SKILL.md — a drop-in Agent Skill (Claude Code / Pi / compatible harnesses) documenting the enumerate-then-capture workflow, selectors, and the exit-code contract.
- AGENTS.md — contributor guidance and the measured ScreenCaptureKit constraints
(also read by Claude Code via the
CLAUDE.mdstub).
A fork of xenodium/macosrec, which is effectively
unmaintained. It has since been rewritten and diverges substantially:
- ScreenCaptureKit throughout, replacing deprecated
CGWindowList*APIs - Recording via
SCRecordingOutput, which writes incrementally and hardware-encoded. The original buffered every frame uncompressed in RAM — a one-minute recording held several GB before writing a byte - Subcommands rather than a single command with mutually exclusive flags. The original shape came from its Emacs integration, which this fork does not target
- JSON output, a documented exit-code contract, display and region targets, bounded
record --duration, and non-interactive OCR - macOS 15 floor, Swift 6 strict concurrency, and a test suite
Original author: Álvaro Ramírez (xenodium.com).
GPLv3. See LICENSE. This rewrite remains a derived work of the original.