# SmoothCapture CLI Reference

SmoothCapture can be driven by scripts and agents through `SmoothCapture cli`. The app binary doubles as the CLI client: it sends one JSON command over a local Unix socket to the running app, streams responses, and exits. Recording and export happen inside the app process (it holds the Screen Recording permission and the Metal render pipeline).

## Setup

The control server is **off by default**. Enable it one of three ways:

```bash
# One-time, persistent (also togglable in Settings → General → "Command-line control")
"/Applications/SmoothCapture.app/Contents/MacOS/SmoothCapture" cli enable

# Or per-launch, session-only
open -g -a SmoothCapture --args --enable-cli
```

Optional convenience symlink:

```bash
"/Applications/SmoothCapture.app/Contents/MacOS/SmoothCapture" cli install-symlink   # → /usr/local/bin/smoothcapture
smoothcapture cli ping
```

If the app isn't running, any command auto-launches it in the background (requires the setting to be enabled) and retries for up to 20 s.

## Output contract

- The final **result** is a single JSON line on **stdout**: `{"id":"…","type":"result","data":{…}}`
- **Progress/events** go to stderr as human-readable text; pass `--json` to get them as JSON lines on stdout instead: `{"type":"event","event":"progress","data":{"progress":0.42,"fps":118}}`
- **Errors** are JSON on stderr (`{"type":"error","error":{"code":"…","message":"…","hint":"…"}}` with `--json`, readable text otherwise).

### Exit codes

| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Bad usage / unknown device or preset |
| 3 | Control server disabled (`SERVER_DISABLED`) |
| 4 | Missing macOS permission (`PERMISSION_*`) |
| 5 | Recording-state conflict (`ALREADY_RECORDING`, `NOT_RECORDING`) |
| 6 | Project/export failure |
| 7 | Could not connect / launch timeout |

### Error codes

`SERVER_DISABLED`, `PERMISSION_SCREEN_RECORDING`, `PERMISSION_ACCESSIBILITY`, `ALREADY_RECORDING`, `NOT_RECORDING`, `DEVICE_NOT_FOUND`, `PROJECT_NOT_FOUND`, `PROJECT_OPEN_IN_EDITOR`, `EXPORT_IN_PROGRESS`, `SESSION_BUSY`, `EXPORT_FAILED`, `BAD_REQUEST`, `INTERNAL`. Every error includes a `hint` when there's a useful next step.

`PERMISSION_SCREEN_RECORDING` means a **human** must grant Screen Recording to SmoothCapture once in System Settings → Privacy & Security; the CLI cannot do it.

## Commands

### ping
```bash
smoothcapture cli ping
# → {"version":"1.2.51","build":"57","bundleID":"…","protocolVersion":1,"pid":12345}
```

### status
Recording state, screen-recording permission, and the project currently open in the editor (if any).

### devices
Lists capture sources. Run this before `record start` to get real ids.
```bash
smoothcapture cli devices --json
# → {"displays":[{"id":3,"width":1920,"height":1080,"frame":{…}}],
#    "windows":[{"id":5533,"title":"…","app":"Safari","bundleID":"com.apple.Safari","frame":{…}}],
#    "cameras":[{"id":"…","name":"MacBook Pro Camera"}],
#    "microphones":[{"id":"…","name":"MacBook Pro Microphone"}]}
```

### record start
```bash
smoothcapture cli record start --display 3                    # full display
smoothcapture cli record start --window 5533                  # one window
smoothcapture cli record start --display 3 --area 100,100,800,600   # region (display-local points)
```
Options: `--microphone <id|false>` `--camera <id|false>` `--system-audio` `--keyboard true|false` `--countdown <seconds>` (default 0 — starts immediately) `--plain-window` (Simulator windows only, see below).

Defaults are deterministic: no microphone, no camera, no system audio, keyboard capture on (silently skipped with a `warnings` entry if Accessibility permission is missing). Omitting a target records the first display.

**iOS Simulator windows:** if `--window` targets an iOS Simulator window (flagged `"simulatorCapture": true` in `devices`), SmoothCapture records the matching booted simulator's device-native framebuffer via `simctl` instead of the on-screen window — clean device-resolution video with no window chrome, bezel, notch overlay, or macOS cursor (same as selecting the window in the app UI). The result's `source` gains a `simulator` object: `{"window":5533,"simulator":{"id":"<udid>","name":"iPhone 16"}}`. System audio and keyboard capture don't apply to simulator recordings (a `warnings` entry is returned if requested). Landscape simulators are rejected with `BAD_REQUEST` — rotate to portrait, or pass `--plain-window` to capture the window with ScreenCaptureKit (chrome and all). If no booted simulator matches the window title, recording falls back to plain window capture with a `warnings` entry. During simctl recordings the simulator status bar is set to demo-friendly values (9:41, full battery, full Wi-Fi/cellular signal) and restored when the recording stops.

Cursor positions and clicks are always captured — they feed smart zoom.

### record stop
Stops, converts to a `.smoothcapture` project bundle, and returns its path. Emits a `finalizing` event first; conversion can take a few seconds.
```bash
smoothcapture cli record stop --json
# → {"bundlePath":"/Users/you/Movies/SmoothCapture/….smoothcapture","videoPath":"…/screen.mp4","duration":12.4}
```

### record status / record cancel
`status` → `{isRecording, isStarting, isStopping, duration}`. `cancel` stops and **discards** all output.

### screenshot
Captures a still image at native pixel scale (retina) through the app's Screen Recording permission — no extra permission grant needed for the calling terminal. `--input <image>` styles an existing file instead of capturing (no permission needed at all).
```bash
smoothcapture cli screenshot --output shot.png                      # first display
smoothcapture cli screenshot --window 5533 --output window.png      # one window, tight bounds
smoothcapture cli screenshot --display 3 --area 100,100,800,600 --output region.png
smoothcapture cli screenshot --pretty --output hero.png             # marketing-ready treatment
smoothcapture cli screenshot --input raw.png --device-frame "3D/MacBook_Pro.usdc" --output hero.png
# → {"outputPath":"/…/hero.png","width":3840,"height":2160,"fileSize":1929919,"pretty":true}
```
Raw capture defaults: cursor hidden (`--cursor` to include it), window captures have tight bounds with transparent rounded corners (use PNG to keep them; JPEG flattens onto white).

`--pretty` renders the capture through the editor's Metal pipeline, so styled screenshots match video exports pixel-for-pixel. Passing any styling flag implies `--pretty`:

**Canvas & background**
- `--background <name>`: bundled background (e.g. `Gradient/20250901_Mac`, `Zen/1` — run `backgrounds` for the list), `Solid/RRGGBB` or `#RRGGBB` for a solid color, an absolute image path, or `none` for a transparent canvas (use PNG). Default `Gradient/20250901_Mac`; with a 3D device frame the default backdrop is its environment instead.
- `--padding 0.9`: image scale within the canvas — same semantics as `backgroundPadding` in project.json (`0.9` ≈ 10% margin). Default 0.9.
- `--corner-radius 0.06`: content corner rounding, editor scale 0–1. Default 0.06.
- `--edge-padding 0.08`: minimum background reveal at the edges when content is zoomed (`revealFraction`), 0–0.2.
- `--no-shadow` / `--shadow-opacity 0.4`: disable or tune the drop shadow (default on at 0.4).
- `--aspect source|16:9|19.5:9|4:3|1:1|9:16|9:19.5`: canvas aspect ratio (default matches the capture).
- `--size WxH`: exact output pixel size (default: derived from capture size and aspect, capped at 8192).
- `--crop x,y,w,h`: normalized 0–1 crop of the capture before styling.

**Device frames**
- `--device-frame <name>`: wrap the capture in hardware — 2D bezel PNGs (iPhones, MacBook Pro) or full 3D models (`3D/MacBook_Pro.usdc`, `3D/iPhone_17_Pro_*.usdc`). Run `device-frames` for names, kinds, and each frame's intended content aspect; a mismatched capture aspect returns a `warnings` array in the result.
- `--screen-rect x,y,w,h`: override the normalized screen area inside a 2D frame (defaults are per-device, listed by `device-frames`).

**Border** (drawn around the content frame)
- `--border` plus `--border-width <px>` (default 4), `--border-color RRGGBB`, `--border-style solid|gradient|glow`, `--border-gradient C1,C2` (implies gradient style), `--border-glow 0..1` (implies glow style).

**3D environments** (only with a 3D `--device-frame`)
- `--environment <name>`: HDR/EXR backdrop lighting the model — run `environments` for the list.
- `--env-background true|false`: use the environment as the backdrop (default true for 3D frames unless `--background` is given).
- `--env-exposure 0..4`, `--env-lighting 0..4` (model lighting), `--env-rotation <deg>`, `--env-pitch <deg>`, `--env-reflection 0..2` (screen reflection), `--env-dof 0..1` (depth-of-field amount, 0 disables).

Other options: `--format png|jpg` (also inferred from the output extension; default png), `--jpeg-quality 0.9`. Omitting `--output` writes to `~/Pictures/SmoothCapture/Screenshot <timestamp>.png`.

`--keep-project <bundle>` also saves the styled capture as a `.smoothcapture` project (returned as `projectPath`), so the screenshot can be re-edited in the editor or re-exported later. Styled screenshots are rendered by building exactly such a project internally and rendering one frame through the headless export path.

Licensing matches export: without a valid license, styled screenshots carry the watermark.

### backgrounds
Lists background names (bundled + shared custom) for `screenshot --background` and project.json's `selectedBackgroundImage`.
```bash
smoothcapture cli backgrounds --json
# → {"backgrounds":[{"name":"Gradient/20250901_Mac","displayName":"September 1, 2025"},…]}
```

### device-frames
Lists device frame names for `screenshot --device-frame`, with kind (`2d`/`3d`), intended content aspect, and the default screen rect used for 2D bezels.
```bash
smoothcapture cli device-frames --json
# → {"deviceFrames":[{"name":"3D/MacBook_Pro.usdc","kind":"3d","contentAspect":"laptop","defaultScreenRect":{…}},…]}
```

### environments
Lists 3D environment names (bundled + shared custom) for `screenshot --environment` and project.json's `selectedEnvironmentImage`.
```bash
smoothcapture cli environments --json
# → {"environments":[{"name":"glasshouse_interior_2k.hdr","displayName":"Glasshouse Interior"},…]}
```

### project import
Wraps any plain video file (`mp4`, `mov`, `m4v`) or image (`png`, `jpg`, `jpeg`, `heic`, `heif`, `tiff`, `tif`, `bmp`, `gif`, `webp`) into a new `.smoothcapture` project so every editing surface — `project.json` edits, presets, editor UI, export — works on media SmoothCapture didn't record.
```bash
smoothcapture cli project import --input screencast.mp4
# → {"bundlePath":"/…/screencast.smoothcapture","duration":129.1,"hasAudio":true}
smoothcapture cli project import --input hero.png
# → {"bundlePath":"/…/hero.smoothcapture","duration":5,"isStillImage":true}
```
- Default destination is next to the source file, named after it (uniquified if taken); `--output <bundle>` sets it explicitly (errors if the path exists).
- The video is **copied** into the bundle; the original is untouched.
- Imported projects start with the default background/padding treatment and no cursor: there is no cursor/click/keyboard capture data, so smart zoom (`project autozoom`) fails with `BAD_REQUEST` and cursor/keystroke effects have nothing to render. Zooms can still be added manually in `project.json` or the editor.
- Embedded audio plays and exports (reported as `hasAudio`).
- Images are encoded once into a small lossless still-video inside the bundle and placed as a segment with `"isStillImage": true` (default 5s). Still segments stretch to **any** duration — set the segment's `endTime` to the desired length (or drag the trim handle in the editor); it is not clamped to the file. All effects (zoom, backgrounds, device frames, borders…) apply to image clips exactly like video. Segment `speed` is ignored for stills.

### project autozoom
Generates cinematic zoom effects from recorded clicks — the main "make it look good" step. Runs headless and saves the bundle.
```bash
smoothcapture cli project autozoom --path demo.smoothcapture
# → {"zoomEffectsGenerated":4,"totalZoomEffects":4}
```
Options: `--clear-existing true|false` (default true), `--peak-zoom 2.0`, `--click-lead 0.3`, `--click-tail 2.5`, `--fill-gap 2.5`.
Returns 0 generated if the recording contained no clicks (nothing to zoom to).

### project apply-preset / project presets
Applies a saved editor preset (background, padding, cursor, webcam layout…) headlessly. Presets are created by a human in the editor's preset menu; `project presets` lists their names.

### project open / project reload
`open --path <bundle>` opens the editor UI on a project; passing a video file imports it first (the result carries `importedFrom` alongside `opened`). `reload` forces the editor to re-read a project it already has open — required after editing `project.json` externally, otherwise the editor's autosave overwrites your changes.

### export
Headless export through the same Metal pipeline as the editor — output is identical to an in-app export.
```bash
smoothcapture cli export --path demo.smoothcapture --output demo.mp4 --preset 1080p30
# progress on stderr … then:
# → {"outputPath":"/…/demo.mp4","fileSize":645628,"duration":12.4}
```
- `--preset`: `Original`, `Original 30fps`, `1080p60`, `1080p30`, `4K60`, `4K30`
- Overrides: `--resolution WxH`, `--fps <n>`, `--bitrate <bps>`, `--codec h264|hevc|proRes422|proRes4444`, `--format mp4|mov|gif` (also inferred from the output extension), `--transparent`
- Fails with `PROJECT_OPEN_IN_EDITOR` if the same bundle is open in the editor (close it first).
- Subtitles are not rendered in headless exports.

## Rules of engagement

1. One recording and one headless project operation at a time.
2. Never edit `project.json` while the project is open in the editor without calling `project reload` immediately after.
3. Fine-grained effect edits (backgrounds, aspect ratio, individual zooms…) are done by editing `project.json` directly — see [project-json-reference.md](project-json-reference.md).
4. The end-to-end recipe lives in [demo-video-workflow.md](demo-video-workflow.md).
