Setup
The CLI ships inside the app, so it is always the version of the Sightline you have:
/Applications/Sightline.app/Contents/MacOS/sightline-cliMake it short with an alias in ~/.zshrc, or a link in a folder on your PATH. The CLI follows the link back to the app.
alias sightline-cli='/Applications/Sightline.app/Contents/MacOS/sightline-cli'
# or
mkdir -p ~/bin && ln -s "/Applications/Sightline.app/Contents/MacOS/sightline-cli" ~/bin/sightline-cliThe same rules as for agents apply: Sightline Pro, Screen Recording permission and Allow AI agents to use Sightline in Settings > AI Agents. If Sightline isn't running, the CLI starts it in the background and waits up to 8 seconds.
The first call
The first call opens Sightline's approval dialog. A command you run yourself is shown as coming from your Terminal, such as zsh. Allow This Session lasts while that shell runs; Always Allow keeps the grant, and also allows anything else you run from that shell, so prefer the session grant or tick Limit to ... for one kind of action.
Commands
| Command | What it does |
|---|---|
sightline-cli <tool> --json '{...}' | Call one tool with its arguments as one JSON object and print the result. Leave out --json for a tool without required arguments. |
sightline-cli tools | List every tool, one per line, with its description. |
sightline-cli mcp | Run the MCP server over stdio, for AI agents. See MCP server. |
sightline-cli help | Show the usage. -h and --help work too. |
The tool names and arguments are exactly those of the tool reference. One difference: the CLI sets include_image to false unless you pass it, so no base64 image floods the terminal.
Output and exit codes
On success the CLI prints the tool's MCP result, pretty printed, and exits with 0. The tool's own JSON is a string inside content[0].text:
{
"content": [
{
"text": "{\"height\":1640,\"path\":\"/Users/you/Pictures/Screenshots/Agent/Sightline 2026-10-24 14.03.11.png\",\"scale\":2,\"width\":2560}",
"type": "text"
}
],
"isError": false
}On an error it prints the message to stderr and exits with 1, for example no window matches. An unknown tool (Unknown tool: ...) and a --json value that isn't a JSON object also exit with 1.
A jq helper
This shell function calls a tool and prints only its JSON, ready for jq. Put it in ~/.zshrc:
sl() {
sightline-cli "$@" | jq -e '.content[0].text | fromjson'
}sl capture_window --json '{"app": "Safari", "name": "safari-now"}' | jq -r .path
sl list_windows | jq -r '.[] | "\(.id)\t\(.app)\t\(.title)"'Recipes
Copy the text of a window
Reads a window with on-device text recognition and puts the text on the clipboard.
sl capture_text --json '{"window": {"app": "Preview"}}' | jq -r .text | pbcopyCapture every window of an app
Lists the windows, keeps the ones of one app and captures each by id, named after its title.
sl list_windows | jq -c '.[] | select(.app == "Xcode")' | while read -r w; do
id=$(jq -r .id <<<"$w")
name=$(jq -r '.title | gsub("[^A-Za-z0-9]+"; "-")' <<<"$w")
sl capture_window --json "$(jq -nc --argjson id "$id" --arg n "xcode-$name" '{id: $id, name: $n}')" | jq -r .path
doneBlur every match of a word
Captures a window, finds every line with a token and blurs those boxes. jq builds the annotations from the boxes.
img=$(sl capture_window --json '{"app": "Terminal", "name": "terminal-safe"}' | jq -r .path)
marks=$(sl find_text --json "$(jq -nc --arg p "$img" '{text: "token", path: $p}')" \
| jq -c '[.matches[] | {type: "blur", blur_style: "pixelate", x, y, width, height}]')
sl annotate --json "$(jq -nc --arg p "$img" --argjson m "$marks" '{path: $p, annotations: $m}')" | jq -r .pathRecord a demo and make a GIF
Records the frontmost Simulator window for 12 seconds, then writes a 960 pixel GIF that zooms in on every click.
mp4=$(sl record_screen --json '{"window": {"app": "Simulator"}, "seconds": 12}' | jq -r .path)
sl annotate --json "$(jq -nc --arg p "$mp4" '{path: $p, annotations: [], zoom_on_clicks: true,
pointer: {click_highlight: "ripple"}, export: {format: "gif", width: 960}}')" | jq -r .pathSearch your history
Lists the five newest starred screenshots whose text contains both words, with the date and the matching snippet.
sl search_captures --json '{"query": "invoice total", "favorites_only": true, "limit": 5}' \
| jq -r '.results[] | "\(.date) \(.path)\n \(.snippet // "")"'Wait until the app matches the design
Compares the Simulator with a design every 5 seconds while you work, and prints the differences until there are none.
cp ~/Designs/signup.png ~/Pictures/Screenshots/Agent/signup-design.png
while :; do
n=$(sl compare_with_design --json '{"design": "/Users/you/Pictures/Screenshots/Agent/signup-design.png", "window": {"app": "Simulator"}}' \
| tee /tmp/diff.json | jq '.differences | length')
[ "$n" -eq 0 ] && { echo "matches the design"; break; }
jq -r '.differences[] | "#\(.number) \(.hint) at \(.x),\(.y)"' /tmp/diff.json
sleep 5
doneShrink and convert a recording
Makes a 720p MOV copy of your newest recording for a slide deck. The original stays as it is.
mp4=$(sl last_recording | jq -r .path)
sl annotate --json "$(jq -nc --arg p "$mp4" '{path: $p, annotations: [], export: {format: "mov", size: "720p", quality: "medium"}}')" | jq -r .pathThese use the sl helper from above and set -o pipefail is a good idea in scripts, so a failing tool stops the script. Paths like /Users/you/... stand for your own home folder.
On a Mac build runner
Sightline is a desktop app, so it runs on a self-hosted Mac runner with a logged-in user session, not on a hosted cloud runner. Set that Mac up once by hand:
- Sign in to the Mac App Store, install Sightline and start Sightline Pro.
- Allow Screen Recording, turn on Allow AI agents to use Sightline, and keep Launch at login on in Settings > General.
- Run one tool from the runner's shell and answer the approval dialog. Only choose Always Allow on a machine that runs nothing but your builds.
Then a job can capture the app it just built and fail when it drifts from the design. This GitHub Actions job runs on a runner labeled self-hosted and macOS:
ui-check:
runs-on: [self-hosted, macOS]
steps:
- uses: actions/checkout@v4
- name: Build and launch
run: |
xcodebuild -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17' build
xcrun simctl launch booted com.example.myapp
- name: Compare with the design
shell: bash
run: |
set -euo pipefail
sl() { "/Applications/Sightline.app/Contents/MacOS/sightline-cli" "$@" | jq -e '.content[0].text | fromjson'; }
agent=~/Pictures/Screenshots/Agent
cp design/signup.png "$agent/signup-design.png"
sl compare_with_design --json "{\"design\": \"$agent/signup-design.png\", \"window\": {\"app\": \"Simulator\"}}" > diff.json
cp "$(jq -r .path diff.json)" marked.png
jq -e '.differences | length == 0' diff.json
- uses: actions/upload-artifact@v4
if: always()
with:
name: design-check
path: |
diff.json
marked.pngThe marked capture numbers each difference, so a failed run shows what moved. Copy results out of the Agent folder, because Sightline moves agent captures to the Trash after 7 days by default.